第一章 对象的概念

      计算机革命的起源来自机器。编程语言就像是那台机器。它不仅是我们思维放大的工具与另一种表达媒介,更像是我们思想的一部分。编程语言就是创建应用程序的思想结构。面向对象的程序设计(Object-oriented Programming,OOP)便是这种以计算机作为表达媒体的大趋势中的组成部分,它是一种编程思维方式和编码架构。随着科技的发展,计算机能做的事情越来越多。作为IT从业者,我们要勤于学习和实践,为了更好地生活,好好利用这个工具。

      Java 是一门派生语言,早期语言设计者为了不想在项目中使用 C++ 而创造了这种看起来很像 C++,却比 C++ 有了改进的新语言(原始的项目并未成功)。Java 最核心的变化就是加入了“虚拟机”和“垃圾回收机制”,这两个概念在之后的章节会有详细描述。 此外,Java 还在其他方面推动了行业发展。例如,现在绝大多数编程语言都支持文档注释语法和 HTML 文档生成工具。

摘自:Java核心技术      

Java并不只是一种语言,Java是一个完整的平台,有一个庞大的库,其中包含了很多可重用的代码和一个提供诸如安全性、跨操作系统的可移植性以及自动垃圾收集等服务的执行环境。在设计Java语言时,还是尽可能的接近c++,以便系统更易于理解,也剔除了c++中许多很少使用、难以理解、易混淆的特性。可以说,Java语法,是c++语法的一个“纯净”版本。当然,在设计Java语言时,由于相容性这个严峻的问题确实存在于现实中,所以,或多或少还是有一些“累赘”被加到语言里,这就导致Java没有想象中那么完美无瑕。Java和c++最大的不同在于Java采用的指针模型可以消除重写内存和损坏数据的可能性。并且,Java适用于网络/分布式环境。在Java中,数据类型具有固定的大小,这消除了代码移植时令人头疼的主要问题。二进制数据以固定的格式进行存储和传输,消除了字节顺序的困扰。字符串是用标准的Unicode格式存储的。Java对多线程的支持也非常友好,多线程可以带来更好的交互相应和实时行为。

抽象

        所有的编程语言都提供抽象机制。所谓的“类型”是指“所抽象的是什么?”汇编语言是对底层机器的轻微抽象,接着所谓的“命令式”语言(如BASIC,C等)都是对汇编语言的抽象。这些语言所作的主要抽象仍要求在解决问题时要基于计算机的结构,而不是基于所要解决的问题的结构来考虑。对机器建模的另一种方法就是只针对待解决问题建模,如早期的编程语言LISP和APL。

      面向对象的方式通过向程序员提供表示问题空间中的元素的工具而更近了一步。这种表示方式非常通用,使得程序员不再受限于任何特定类型的问题。我们将问题空间中的元素及其在解空间中的表示,成为“对象”。这种思想的实质为:程序可以通过添加新类型的对象使自身适用于某个特殊问题。oop允许根据问题来描述问题,而不是根据运行解决方案的计算机来描述问题。

      程序是对象的集合,他们通过发送消息来告知彼此所要做的。每个对象都有自己的由其他对象所构成的存储。每个对象都有其类型。某一特定类型的所有对象都可以接收同样的消息。一个对象具有自己的状态,行为和标识。这意味着对象有自己的内部数据(提供状态)、方法 (产生行为),并彼此区分(每个对象在内存中都有唯一的地址)。

接口

        如何利用对象完成真正有用的工作呢?必须有一种办法能向对象发出请求,令其解决一些实际的问题。每个对象仅能接受特定的请求。我们向对象发出的请求是通过它的“接口”(Interface)定义的,对象的“类型”或“类”则规定了它的接口形式。“类型”与“接口”的对应关系是面向对象程序设计的基础。这些“接口”,我们也可以称之为“函数”或“方法”;

服务提供

         在开发或理解程序设计时,我们可以将对象看成是“服务提供者”。在良好的面向对象设计中,每个对象功能单一且高效。这样的程序设计可以提高我们代码的复用性,同时也方便别人阅读和理解我们的代码。只有让人知道你提供什么服务,别人才能更好地将其应用到其他模块或程序中。

封装

       我们可以把编程的侧重领域划分为研发和应用。应用程序员调用研发程序员构建的基础工具类来做快速开发。研发程序员开发一个工具类,该工具类仅向应用程序员公开必要的内容,并隐藏内部实现的细节。使用访问控制的原因有以下两点:让应用程序员不要触摸他们不应该触摸的部分;使类库的创建者(研发程序员)在不影响后者使用的情况下完善更新工具库。

        因为类是描述了具有相同特性(数据元素)和行为(功能)的对象集合,所以一个类实际上就是一个数据类型。每个对象都有接口,每个对象都提供服务。java访问权限,默认包访问权限,没有访问指定词。private,私有访问权限,类内部访问权限。protect,受保护访问权限,继承类可以访问。public,公共访问权限。

复用

implements)多个接口 (interface)。单根继承结构使垃圾回收期的实现变得容易很多,垃圾自动回收机制也是java的优势之一。

        组合与聚合。聚合关系中,整件不会拥有部件的生命周期,所以整件删除时,部件不会被删除。再者,多个整件可以共享同一个部件。组合关系中,整件拥有部件的生命周期,所以整件删除时,部件一定会跟着删除。而且,多个整件不可以同时共享同一个部件。这个区别可以用来区分某个关联关系到底是组合还是聚合。两个类生命周期不同步,则是聚合关系,生命周期同步就是组合关系。使用“组合”关系给我们的程序带来极大的灵活性。在创建新类时首先要考虑“组合”,因为它更简单灵活,而且设计更加清晰。

继承

        创建基类以表示思想的核心。从基类中派生出其他类型来表示实现该核心的不同方式。从现有类型继承创建新类型。这种新类型不仅包含现有类型的所有成员(尽管私有成员被隐藏起来并且不可访问),而且更重要的是它复制了基类的接口。也就是说,基类对象接收的所有消息也能被派生类对象接收。根据类接收的消息,我们知道类的类型,因此派生类与基类是相同的类型。通过继承的类型等价性是理解面向对象编程含义的基本门槛之一。

多态

       我们在处理类的层次结构时,通常把一个对象看成是它所属的基类,而不是把它当成具体类。通过这种方式,我们可以编写出不局限于特定类型的代码。这样的代码不会受添加的新类型影响,并且添加新类型是扩展面向对象程序以处理新情况的常用方法。方法的参数可以采用泛型或基类类型,方法编写时不需要考虑具体的对象类型,直到代码运行时才需要知道参数类型。这种称为后期绑定,也称作动态绑定。通过继承,程序直到运行时才能确定代码的地址。在 Java 中,动态绑定是默认行为,不需要额外的关键字来实现多态性。

        发送消息给对象时,如果程序不知道接收的具体类型是什么,但最终执行是正确的,这就是对象的“多态性”(Polymorphism)。面向对象的程序设计语言是通过“动态绑定”的方式来实现对象的多态性的。编译器和运行时系统会负责对所有细节的控制;我们只需知道要做什么,以及如何利用多态性来更好地设计程序。

单继承结构

        在 Java 中,所有的类都应该默认从一个基类继承,这个最终基类的名字就是 Object。Java 的单继承结构有很多好处。由于所有对象都具有一个公共接口,因此它们最终都属于同一个基类。单继承的结构使得垃圾收集器的实现更为容易。这也是 Java 在 C++ 基础上的根本改进之一。由于运行期的类型信息会存在于所有对象中,所以我们永远不会遇到判断不了对象类型的情况。这对于系统级操作尤其重要,例如异常处理。同时,这也让我们的编程具有更大的灵活性

集合

       容器,也称作集合。“集合”这种类型的对象可以存储任意类型、数量的其他对象。它能根据需要自动扩容,我们可以不去关心过程是如何实现的。不同类型的集合对应不同的需求:常见的有 List,常用于保存序列;Map,也称为关联数组,常用于将对象与其他对象关联;Set,只能保存非重复的值;其他还包括如队列(Queue)、树(Tree)、栈(Stack)、堆(Heap)等等。集合多采用参数化类型(泛型),这样可以很方便的使用集合,并减少出错。泛型是 Java 5 的主要特性之一。之所以选择集合有以下两个原因:

1,集合可以提供不同类型的接口和外部行为。堆栈、队列的应用场景和集合、列表不同,它们中的一种提供的解决方案可能比其他灵活得多。

2,不同的集合对某些操作有不同的效率。例如,List 的两种基本类型:ArrayList 和 LinkedList。虽然两者具有相同接口和外部行为,但是在某些操作中它们的效率差别很大。在 ArrayList 中随机查找元素是很高效的,而 LinkedList 随机查找效率低下。反之,在 LinkedList 中插入元素的效率要比在 ArrayList 中高。由于底层数据结构的不同,每种集合类型在执行相同的操作时会表现出效率上的差异。

对象创建和生命周期

       我们在使用对象时要注意的一个关键问题就是对象的创建和销毁方式。每个对象的生存都需要资源,尤其是内存。为了资源的重复利用,当对象不再被使用时,我们应该及时释放资源,清理内存。Java 使用动态内存分配。每次创建对象时,使用 new

异常处理

       异常处理机制将程序错误直接交给编程语言甚至是操作系统。“异常”(Exception)是一个从出错点“抛出”(thrown)后能被特定类型的异常处理程序捕获(catch)的一个对象。它不会干扰程序的正常运行,仅当程序出错的时候才被执行。这让我们的编码更简单:不用再反复检查错误了。另外,异常不像方法返回的错误值和方法设置用来表示发生错误的标志位那样可以被忽略。异常的发生是不会被忽略的,它终究会在某一时刻被处理。最后,“异常机制”提供了一种可靠地从错误状况中恢复的方法,使得我们可以编写出更健壮的程序。有时你只要处理好抛出的异常情况并恢复程序的运行即可,无需退出。Java 从一开始就内置了异常处理,因此你不得不使用它。这是 Java 语言唯一接受的错误报告方法。如果没有编写适当的异常处理代码,你将会收到一条编译时错误消息。这种有保障的一致性有时会让程序的错误处理变得更容易。值得注意的是,异常处理并不是面向对象的特性。尽管在面向对象的语言中异常通常由对象表示,但是在面向对象语言之前也存在异常处理。

       并发编程。分布式和高并发,是解决海量请求的方法。Java的并发是内置于语言中的,有很多类库支持我们使用多线程处理并发问题。

       Java与Internet。现在很多网站和系统都使用了java的技术,用java在服务器端编程,由很大的优势,结合javascript、html等技术,使得java在web领域越来越多的被使用。

 

第三章 一切都是对象

        Java 最主要的概念之一“对象”来自 SmallTalk 语言。SmallTalk 语言恪守“对象”(在下一章中描述)是编程的最基本单元。于是,万物皆对象。历经时间的检验,人们发现这种信念太过狂热。固执地要求所有东西都是一个对象(特别是一直到最底层级别)是一种设计错误;相反,完全逃避“对象”的概念似乎同样太过苛刻。

        虽万物皆可为对象,但我们所操纵的标识符实际上只是对对象的“引用”。“引用”用来关联“对象”。在 Java 中,通常我们使用 new 操作符来创建一个新对象。new 关键字代表:创建一个新的对象实例。

数据存储

       Java对象存储在堆中。堆是一种通用的内存池,位于RAM区。用new创建对象,当执行这段代码时,会自动在堆里进行内存分配。常量值通常存放在程序代码内部。虽然在栈内存上存在一些 Java 数据(如对象引用),但 Java 对象本身的数据却是保存在堆内存的。非 RAM 存储(Non-RAM storage)数据完全存在于程序之外,在程序未运行以及脱离程序控制后依然存在。两个主要的例子:(1)序列化对象:对象被转换为字节流,通常被发送到另一台机器;(2)持久化对象:对象被放置在磁盘上,即使程序终止,数据依然存在。这些存储的方式都是将对象转存于另一个介质中,并在需要时恢复成常规的、基于 RAM 的对象。Java 为轻量级持久化提供了支持。而诸如 JDBC 和 Hibernate 这些类库为使用数据库存储和检索对象信息提供了更复杂的支持。

基本类型的存储

       特例:基本类型。基本类型在 Java 中使用频率很高,它们需要特殊对待。通常 new 出来的对象都是保存在堆内存中的,以此方式创建小而简单的变量往往是不划算的。因此,基本类型不用new来创建变量,而是使用一个“自动”变量。 这个变量直接存储"值",并置于栈内存中,因此更加高效。java的基本类型所占的存储空间是不变的,是约定好的。基本类型具有包装器类,使得可以在堆中创建一个非基本对象,用来表示对应的基本类型。Java的自动装箱功能可以自动的基本类型转变为包装器类型,并可以自动反向转换(自动拆箱)。

高精度数值

        在 Java 中有两种类型的数据可用于高精度的计算。它们是 BigInteger 和 BigDecimal。尽管它们大致可以划归为“包装类型”,但是它们并没有对应的基本类型。这两个类包含的方法提供的操作,与对基本类型执行的操作相似。也就是说,能对 int 或 float 做的运算,在 BigInteger 和 BigDecimal 这里也同样可以,只不过必须要通过调用它们的方法来实现而非运算符。此外,由于涉及到的计算量更多,所以运算速度会慢一些。诚然,我们牺牲了速度,但换来了精度。BigInteger 支持任意精度的整数。可用于精确表示任意大小的整数值,同时在运算过程中不会丢失精度。 BigDecimal 支持任意精度的定点数字。

数组的存储

        Java的主要目标之一是安全性,在 Java 中,数组使用前需要被初始化,并且不能访问数组长度以外的数据。这种范围检查,是以每个数组上少量的内存开销及运行时检查下标的额外时间为代价的,但由此换来的安全性和效率的提高是值得的。(并且 Java 经常可以优化这些操作)。当创建一个数组对象时,实际上就是创建了一个引用数组,并且每个引用都会自动被初始化为一个特定值,该值拥有自己的关键字null。一旦Java中看到null,就知道这个引用还没有指向某个对象。在使用任何引用前,必须为其指定一个对象;如果试图使用一个还是null的引用,在运行时将会报错。我们还可创建基本类型的数组。编译器通过将该数组的内存全部置零来保证初始化。

在以后的开发中,如果不注意对数组或集合进行非空判断和边界判断,你会遇到很多次的空指针异常和数组越界异常,对数组和集合的初始化与判断,是一个良好的编程习惯。

对象创建与清理       

        在Java中,作用域由花括号“{}”的位置确定。在作用域里定义的变量,只可用于作用域结束之前。Java 的变量只有在其作用域内才可用。用class创建新的类,每个类有一个类名,类里面可以设置字段(属性,数据成员)和方法(成员函数)。字段可以是任何类型的对象,可以通过引用与其进行通信;也可以是基本类型的一种。如果字段是某个对象的引用,那么必须初始化该引用,以便使其与实际的对象相关联。基本类型的字段都有其初始值,但对于程序来说可能是不正确的,甚至是非法的,所以最好明确的对变量进行初始化。对于局部变量(即并非某个类的字段),如果没有初始化,程序会在编译时报错。

        Java 对象与基本类型具有不同的生命周期。当我们使用 new 关键字来创建 Java 对象时,它的生命周期将会超出作用域,因为该对象可能通过方法调用被其他作用域的对象使用。那么何时这些对象会被清理呢?Java 的垃圾收集器会检查所有 new

        如果类的成员变量(字段)是基本类型,那么在类初始化时,这些类型将会被赋予一个初始值。char的初始值是null,boolean类型初始值是false,其余类型是符合本类型格式的0。在许多语言(如 C 和 C++)中,使用术语 函数 (function) 用来命名子程序。在 Java 中,我们使用术语 方法(method)来表示“做某事的方式”。在 Java 中,方法决定对象能接收哪些消息。方法的基本组成部分包括名称、参数、返回类型、方法体。      方法的返回类型表明了当你调用它时会返回的结果类型。参数列表则显示了可被传递到方法内部的参数类型及名称。方法名和参数列表统称为方法签名(signature of the method)。签名作为方法的唯一标识。Java 中的方法只能作为类的一部分创建。它只能被对象所调用 ,并且该对象必须有权限来执行调用。若对象调用错误的方法,则程序将在编译时报错。

        方法调用:对象.方法名(参数列表)。对于static方法,它是针对类调用的,可以表示为:类名.方法名(参数列表)。调用方法的行为,通常被称为发送消息给对象。面向对象的程序设计通常简单的归纳为“向对象发送消息”。return关键字,表示“已经做完,离开此方法”。如果返回类型是具体类型,则返回此类型的返回值。若返回类型为void,则只用来表示退出方法。

static关键字

      当类的字段使用static关键字修饰时,一个static字段对每个类来说只有一分存储空间,而非static字段则是对每个对象有一个存储空间。static修饰的方法,则是可以直接使用类名调用它。

      对于一个类文件,类名必须和文件名一致,否则编译器报错。在使用Java语言编程时,可以使用它庞大的标准类库集,只需要使用import导入即可,非常方便。类名首字母大写,建议驼峰式写法。Java类要进行适当的注释,这样既增加可读性,也方便后期的维护。可以使用javadoc提取注释文档,生成类似java api那样的html文档。