Java核心技术——卷I——对象与类

面向对象程序设计(object-oriented-programming,OOP)是当今主流的程序设计范型,它取代了20世纪70年代的“结构化”或过程式编程技术。面向对象程序是由对象组成的,每个对象包含对用户公开的特定功能和隐藏的实现部分。传统的结构化程序设计通过一系列的过程(即算法)来求解问题,即算法是第一位的,数据结构是第二位的。而OOP却相反,将数据放在第一位,然后再考虑操作数据的算法。

类(class)是构造对象的模板或蓝图。由类构造(construct)对象的过程称为创建类的实例(instance)。封装(encapsulation,有时又称为数据隐藏)是将数据和行为组合在一个包中,并对对象的使用者隐藏具体的实现方式。对象中数据称为实例字段,操作数据的过程称为方法。实现封装的关键在于,绝对不能让类中的方法直接访问其他类的实例字段。程序只能通过对象的方法与对象数据进行交互。

OOP的另一个原则就是:可以通过扩展其他类来构建新类。在扩展一个已有的类时,这个扩展后的新类具有被扩展的类的全部属性和方法。通过扩展一个类来建立另外一个类的过程称为继承(inheritance)

对象的三个主要特性如下:

1)对象的行为(behavior)——可以对对象完成哪些操作,或者可以对对象应用哪些方法?

2)对象的状态(state)——当调用那些方法时,对象会如何相应?

3)对象的标识(identify)——如何区分具有相同行为与状态的不同对象?

对象的行为是用可调用的方法来定义的。此外每个对象都保存着描述当前状况的信息,这就是对象的状态。对象的状态可能会随着时间而发生改变,但这种改变不会是自发的。对象状态的改变必须通过调用方法实现。每个对象都有一个唯一的的标识。需要注意的是,作为同一个类的实例,每个对象的标识总是不同的,状态也往往存在着差异。

传统的过程式程序中,必须从顶部的main函数开始编写程序。而OOP是首先从识别类开始,然后再为各个类添加方法。识别类的一个简单经验是在分析问题的过程中寻找名词,而方法对应着动词。

在类之间的,最常见的关系有:依赖(“uses-a”)、聚合(“has-a”)、继承(“is-a”)。如果一个类的方法使用或操纵另一个类的对象,就说一个类依赖于另一个类;如果类A的对象包含类B的对象,就说它们是聚合;而继承表示一个更特殊的类与一个更一般的类之间的关系。

表达类关系的UML符号如下所示:

one

在Java中,要使用构造器(constructtor,或称为构造函数)构造新实例。构造器是一种特殊的方法,用来构造并初始化对象。构造器的名字应该与类名相同。要认识到重要一点:对象变量并没有实际包含一个对象,它只是引用一个对象。在Java中,任何对象变量的值都是存储在另外一个地方的某个对象的引用。new操作符的返回值也是一个引用。所有的对象都存储在堆中。Java中,可以使用clone方法获得对象的完整副本。

只访问对象而不修改对象的方法被称为访问器方法,相反访问对象且改变对象的方法称为更改器方法。而在C++中,带有const后缀的方法是访问器方法;没有声明为const的方法默认为更改器方法。

LocalDate 相关API如下:

two

Java中,最简单的类定义形式为:

three

在一个源文件中,只能有一个公共类,但可以有任意数目的非公共类。

构造器与类同名。构造器与其他方法有一个重要的不同。构造器总是结合new运算符来调用。不能对一个已经存在的对象调用构造器来达到重新设置实例字段的目的。总结,有关构造器的内容如下:

1)构造器与类同名

2)每个类可以有一个以上的构造器

3)构造器可以有0个、1个或多个参数

4)构造器没有返回值

5)构造器总是伴随着new操作符一起调用。

要特别注意,不要在构造器中定义与实例字段同名的局部变量。

var关键字只能用于方法中的局部变量。参数和字段的类型必须声明。

如果对null值应用一个方法,会产生一个NullPointerException异常。

方法用于操作对象以及存取它们的实例字段。方法一般伴随着两种参数,隐式参数与显示参数。隐式参数是出现在方法名前所引用方法的对象名(有人也把隐式参数称为方法调用的目标或接收者)。显示参数是位于方法名后面括号中的数值。在每一个方法中,关键字this指示隐式参数。

只返回实例字段值的方法,又称为字段访问器方法。特别注意,不要编写返回可变对象引用的访问器方法。如果实在要返回一个可变对象的引用,应该对它进行克隆。对象克隆是指存放在另一个新位置上的对象副本。

一个方法可以访问所属类的所有对象的私有数据。

可以将实例字段定义为final。这样的字段必须在构造对象时初始化。也就是说,必须确保在每一个构造器执行后,这个字段的值已经设置,并且以后不能再修改这个字段。

静态方法是不在对象上执行的方法,可以认为静态方法是没有this参数的方法(一个非静态方法中,this参数指示这个方法的隐式参数)。可以使用对象调用静态方法,这是合法的。但是建议还是使用类名而不是对象来调用静态方法。以下两种情况可以使用静态方法:

1)方法不需要访问对象状态,因为它需要的所有参数都通过显示参数提供。

2)方法只需要访问类的静态字段。

需要注意,可以调用静态方法而不需要任何对象。那就是说main方法也是一个静态方法。

CUE:每一个类可以有一个main方法。这是常用于对类进行单元测试的一个技巧。

Objects相关API 如下:

four

five

按值调用表示方法接受的是调用者提供的值;按引用调用表示方法接受的是调用者提供的变量地址。方法可以修改按引用传递的变量的值,而不能修改按值传递的变量的值。Java总是采用按值调用。Java中对方法参数可以采取何种操作如下所示:

1)方法可以改变对象参数的状态。

2)方法不能修改基本数据类型的参数(即数值型和布尔型)

3)方法不能让一个对象参数引用一个新的对象。

如果多个方法有相同的名字、不同的参数,这种情况便称为重载。Java允许重载任何方法,而不仅仅是构造器方法。因此,要完整地描述一个方法,需要指定方法名以及参数类型。这叫做方法的签名。注意,返回类型不是方法签名的一部分。也就是,不能有两个名字相同、参数类型也相同却有不同返回类型的方法。

如果在构造器中没有显式地为字段设置初值,那么就会被自动地赋值为默认值:数值为0、布尔值为false、对象引用为null。这是字段与局部变量的一个重要区别。方法中的局部变量必须明确地初始化。无参构造器会将所有的实例字段设置为默认值。当类没有任何其他构造器的时候,程序才会得到一个默认的无参数的构造器。

关键字this除了指示一个方法的隐式参数外,还有另外一个含义:如果构造器的第一个语句形式如this(….),这个构造器将调用同一个类的另外一个构造器。

初始化数据字段的方法除了在构造器中设置以及在声明中赋值外,还有第三种机制。称作初始化块。在一个类的声明中,可以包含任意多个代码块。只要构造这个类的对象,这些块就会被执行。可以在初始化块中设置字段,即使这些字段在类后面才定义,这也是合法的。但是,为了避免循环定义,不允许读取在后面初始化的字段。

Random相关API如下:

six

Java允许使用包(package)将类组织在一个集合中。使用包的主要原因是确保类名的唯一性。事实上,为了保证包名的绝对唯一性要用一个因特网域名以逆序的形式作为包名,然后对于不同的工程使用不同的子包。从编译器的角度来看,嵌套的包之间没有任何关系。每一个包都是独立的类的集合。

一个类可以使用所属包中所有类,以及其他包中的公共类(public class)。可以采用两种方式访问另一个包中的公共类,第一种方式就是使用完全限定名,就是包名后面跟着类名;第二种方式就是使用import语句。import语句是一种引用包中各个类的简捷方式。一旦使用了import语句,在使用类时,就不必写出类的全名了。import语句应该位于源文件的顶部(但位于package语句的后面)。需要注意的是,可以使用星号(*)导入一个包,但不能使用import java.*或import java.*.*导入以java为前缀的所有包。在包中定位类是编译器的工作。类文件中的字节码总是使用完整的包名引用其他类。import语句的唯一好处就是简捷,可以使用简短的名字而不是完整的包名来引用一个类。有一种import语句运行导入静态方法和静态字段,而不只是类。另外,还可以导入特定的方法或字段。

要想将类放入包中,就必须将包的名字放在源文件的开头,即放在定义这个包中各个类的代码之前。如果没有在源文件中放置package语句,这个源文件中的类就属于无名包。无名包没有包名。注意,编译器在编译源文件的时候不检查目录结构。如果包与目录不匹配,虚拟机就找不到类。

类存储在文件系统的子目录中。类的路径必须与包名匹配。另外,类文件也可以存储在JAR(Java归档)文件中。JAR文件使用ZIP格式组织文件和子目录。类路径是所有包含类文件的路径的集合。在UNIX环境中,类路径中的各项之间用冒汗(:)分隔,而在Windows环境中,则以分号(;)分隔,不管是UNIX还是Windows,都用句点(.)表示当前目录从。从Java 6 开始,可以在JAR文件目录中指定通配符,不过在UNIX中,*必须转义以防止shell扩展。Java编译器总是在当前的目录查找文件,但Java虚拟机仅在类路径中包含”.”目录的时候才查看当前目录。

最好使用 -classpath(或 -cp,或者Java 9中的 –class-path)选项指定类路径。整个指令必须写在一行中。在Java 9中,还可以从模块路径加载类

一个JAR文件既可以包含类文件,也可以包含诸如图象和声音等其他类型的文件。可以使用jar工具制作JAR文件(在默认的JDK安装下,该工具位于bin目录下)。创建一个新JAR文件最常用的命令使用以下语法:

jar cvf jarFileName file1 file2 ……

通常jar命令的格式如下:

jar options file1 file2 ……

jar程序所有选项如下所示:

seven

eight

除了类文件以及其他资源文件以外,每个JAR文件还包含一个清单文件(manifest),用于描述归档文件的特殊特性。清单文件被命名为MANIFEST.MF,它位于JAR文件的一个特殊的META-INF子目录中。符合标准的最小清单文件为:

Manifest -Version: 1.0

而复杂的清单文件可能包含更多条目。这些条目被分成更多个节。第一节被称为主节。它作用于整个JAR文件。随后的条目用来指定命名实体的属性。它们都必须以Name条目开始。节于节之间用空行分开。若要编辑清单文件,需要将希望添加到清单文件的行放到文本文件中,然后运行:jar cfm jarFileName manifestFileName ……;而若要更新一个已有的JAR文件的清单,则需要将增加的部分放置到一个文本文件中,然后运行:

jar ufm MyArchive.jar manifest -additions.mf

可以使用jar命令中的e选项指定程序的入口点,即通常需要在调用Java程序启动器时指定的类。或者,可以在清单文件中指定程序的主类。但注意,不要为主类名添加扩展名.class。清单文件的最后一行必须以换行符结束。否则,清单文件将无法被正确地读取。

在控制台端启动jar文件命令为:java -jar MyProgram.jar 。而在OS中采取的操作方式为:

1)Windows平台下,Java运行时安装程序将为“.jar”扩展名创建一个文件关联,会用javaw -jar命令启动文件(与java命令不同的是该命令不打开shell窗口)

2)在MAC OS平台下,OS能够识别“.jar”扩展名文件。双击便会执行。

Java 9引入了多版本JAR(multi-release JAR),其中可以包含面向不同Java版本的类文件。为了保证向后兼容,额外的类文件放在META-INF/versions目录中。要增加不同版本的类文件,可以使用 –release标志:

jar uf MyProgram.jar –release 9 Application.class

要从头构建一个多版本JAR文件,可以使用 -C选项,对应每个版本要切换到一个不同的类文件目录:

jar cf MyProgram.jar -C bin/8 . –release 9 -C bin/9 Application.class

面向不同版本编译时,要使用 –release标志和-d 标志来指定输入目录:

javac -d bin/8 –release 8 ……

在Java 9中,-d选项会创建这个目录(如果原先改目录不存在的话)。 –release标志也是Java 9新增的。

JDK的命令行选项一直以来都使用单个短横线加多字母选项名的形式,但jar命令除外,这个命令遵循经典的tar命令选项格式,而没有短横线。从Java 9开始,Java工具开始转向一种更常用的选项格式,多字母选项名前面加两个短横线,另外对于常用的选项可以使用单字母快捷方式。带 – 和多字母的选项的参数用空格或者一个等于号(=)分隔。单字母选项的参数可以用空格分隔,或者之间跟在选项后面。无参数的单字母选项可以组合在一起,但目前不能使用这种方式,因为这会带来混淆的。jar 命令的长选项:

jar –create –verbose –file jarFileName file1 file2 ……

对于单字母选项,不组合的话,也是可以使用:jar -c -v -f jarFileName file1 file2 ……

JDK中包含一个很有用的工具——javadoc,它可以由源文件生成一个HTML文档。javadoc工具从以下几项中抽取信息:

1)模块 ;2)包;3)公共类与接口;4)公共和受保护的字段;5)公共的和受保护的构造器及方法。可以(而且应该)为上述各个特性编写注释。注释放置在所描述特性的前面。注释以/**开始,并以*/结束。每个/**……*/文档注释包含标记以及之后紧跟着的自由格式文本。标记以@开始,如@since或@param。自由格式文本的第一句应该是一个概要性的句子。类注释必须放在import语句之后,类定义之前。每个方法注释必须放在所描述的方法之前。除了通用标记外,还可以使用如下标记:

nine

标记@since text会建立一个“since”(始于)条目。text(文本)可以是引入这个特性的版本的任何描述。如下标记也可以用在类文档注释中:

ten

通过@see和@link标记,可以使用超链接,链接到javadoc文档的相关部分或外部文档。标记@see reference将在“see also”(参见)部分增加一个超链接。需要注意的是,一定要使用井号(#),而不要使用句号(.)分隔类名与方法名,或类名与变量名。如果@see标记后面有一个<字符,就需要指定一个超链接。可以超链接到任何URL。如果@see标记后面有一个双引号(“)字符,文本就会显示在“see also”部分。可以为一个特性添加多个@see标记,但必须将它们放在一起。最后,在Java 9中,可以使用{@index entry}标记为搜索框增加一个条目。

要想产生包注释,就需要在每一个包目录中添加一个单独的文件。有如下两个选择:

eleven

类设计技巧:

1)一定要保证数据私有

2)一定要对数据进行初始化

3)不要在类中使用过多的基本类型

4)不是所有的字段都需要单独的字段访问器和字段更改器。

5)分解有过多的职责类

6)类名和方法名要能够体现它们的职责。类名应当是一个名词,或者前面有形容词修饰的名词。或者是有动名词修饰的名词。对于方法来说,要遵循标准惯例:访问器方法用小写get开头,更改器方法用小写的set开头。

7)优先使用不可变的类。