Core-Java 阅读笔记 -3
2021-07-10 09:45:44 # Java

Core-Java第四章对象与类

一、面向对象程序设计概述

面向对象程序设计(object-oriented programming,OOP)是当今主流的程序设计范型,取代了结构化或过程式编程技术。

面向对象的程序是由对象组成的,每个对象包含对用户公开的特定功能部分和隐藏的实现部分。

程序中的很多对象来自标准库,还有一些是自定义的。

在以往的程序开发中,通过设计一些列的过程来求解问题,一旦确定了这些过程,就需要考虑数据存储的方式,这也是Pascal语言开发者说过的算法+数据结构=程序。

但是在面向对象语言中,需要先考虑数据,然后再考虑对数据的算法。

image-20210710100116067

1.1 类

类(class)是构造对象的模板或蓝图。由类构造(construct)对象的过程称为创建类的实例(instance)

标准Java库提供了几千个类,可以用于各种目的,如用户界面设计、日期、日历和网络编程。

封装(encapsulation,有时称为数据隐藏)是处理对象的一个重要概念。

从形式上看,封装就是将数据和行为组合在一个包中,并对对象的使用者隐藏具体的实现方式。

对象中的数据称为实例字段(instance field),操作数据的过程称为方法(method)。

作为一个类的实例,特定对象都有一组特定的实例字段值。这些值的集合称为对象的当前状态。

实现封装的关键在于,绝对不能让类中的方法直接访问其他类的实例字段。

程序只能通过对象的方法与对象的数据进行交互。

1.2 对象

对象的三个重要特性:

  • 对象行为(behavior)--可以对对象完成哪些操作,或者可以对对象应用哪些方法
  • 对象状态(state)--当调用那些方法时,对象会如何相应
  • 对象标识(identity)--如何区分具有相同行为和状态的不同对象

1.3 识别类

识别类的一个简单经验就是在分析问题的过程中寻找名词,而方法对应着动词

1.4 类之间的关系

在类之间,最常见的关系有:

  • 依赖(“uses-a”)
  • 聚合(“has-a”)
  • 继承(“is-a”)

依赖(dependence),是一种最明显也是最常见的关系,例如,Order类使用Account类是因为Order对象需要访问Account对象查看信用状态。如果一个类的方法使用或操纵另一个类的对象,我们就说一个类依赖于另一个类。应该尽可能减少类之间的依赖,用软件工程的思想即减少类之间的耦合。

聚合(aggregation),即“has-a”关系,如一个Order对象中有一些Item对象。包容关系意味着类A的对象包含类B的对象。

继承(inheritance),即“is-a”关系,表示一个更特殊的类和一个更一般的类之间的关系。例如,RushOrder类由Order类继承而来。在更特殊的RushOrder类中包含了一些用于优先处理的特殊方法,还提供了一个计算运费不同的方法,而其他方法都是从Order类中继承而来。一般来说,如果类A扩展类B,类A不但包含从类B继承而来的方法,还会有一些额外的功能。

二、使用预定义类

在Java中,并不是所有的类都表现出面向对象的典型特性,如Math类等,只需要知道方法名和参数即可。

2.1 对象与对象变量

在Java程序设计语言中,要使用构造器(constructor,或称构造函数)构造新实例。构造器是一种特殊的方法,用来构造并初始化对象。

构造器的名字与类名相同,要想构造一个对象,需要在构造器前面加上new操作符

new Date()

在Java中,任何对象变量的值都是对存储在另外一个地方的某个对象的引用。new操作符的返回值也是一个引用。

1
Date deadline = new Date();

表达式new Date()构造了一个Date类型对象,它的值是对新创建对象的一个引用。这个引用存储在变量deadline中。

Java中的对象变量不等于C++中的引用,相当于对象指针,如

Date birthday在C++中类似于Date* birthday

如果把变量复制到另一个变量,两个变量就指向同一个日期,即它们是同一个对象的指针。

所有的Java对象都存储在内存的堆区,当一个对象包含另一个对象变量时,它只是包含着另一个堆对象的指针。

2.2 Java类库中的LocalDate类

Date类的实例有一个状态,即特定的时间点。

Date类用于处理人类记录日期的日历不是很有用,所以类设计者决定把时间点命名和保存时间分开。

在Java中有用于表示时间点的Date类,有日历表示法表示日期的LocalDate类。

不要使用构造器来构造LocalDate对象,实际上,应该使用静态工厂方法(factory method),它会代表你调用构造器。

LocalDate.now();

会构造一个新对象,表示构造这个对象时的时间。

也可以提供年月日来构造

LocalDate.of(2021,7,10);

LocalDate类封装了实例字段来维护所设置的日期。如果不查看源代码,就不可能知道类内部的日期表示。

访问对象会修改对象的方法称为更改器方法(mutator method)

只访问对象而不修改对象的方法有时称为访问器方法(accessor method)。

  • static LocalDate now()

构造一个表示当前日期的对象

  • static LocalDate of(int year,int month,int dat)

构造一个表示给定日期的对象

  • int getYear()

  • int getMonthValue()

  • int getDayOfMonth()

得到当前日期的年月日

  • DayOfWeek getDayOfWeek

得到当前日期是星期几,作为DayOfWeek类的一个实例返回。调用getValue来得到1~7之间的一个数,表示这是星期几,1表示星期一,7表示星期日。

  • LocalDate plusDays(int n)

  • LocalDate minusDays(int n)

生产当前日期之前或之后n天的日期

三、 用户自定义类

3.1 Employee类

3.2 多个源文件使用

3.3 剖析Employee类

3.4 构造器

关键字private确保只有Employee类自身的方法可以访问这些实例字段,而其他类无法访问到。

  • 构造器与类同名
  • 每个类可以有一个以上的构造器
  • 构造器可以有0个、1个或多个参数
  • 构造器没有返回值
  • 构造器总是伴随着new操作符一起调用

3.5 用var声明局部变量

在Java10中,如果可以从变量的初始值推导出他们的类型,那么可以用var关键字声明局部变量,而无须指定类型。

这样可以避免重复写类型名,var关键字只能用于方法中的局部变量。

3.6 使用null引用

3.7 隐式参数与显式参数

隐式参数是出现在方法名前的Employee类型的对象,又称为方法调用者,方法调用目标,接收者

显式参数是方法名括号中的变量,显式地列在方法声明中

3.8 封装的优点

确保了私有字段不受到外界的修改。

注意不要编写返回可变对象引用的访问器方法。如果需要返回一个可变对象的引用,首先应该对它进行克隆(clone)。对象克隆是指存放在另一个新位置上的对象副本。

3.9 基于类的访问权限

方法可以访问所属类任何对象的私有特性,而不限于隐式参数this。在每一个方法中,关键字this指示隐式参数。

3.10 私有方法

如果一个复杂的方法需要通过几个辅助方法实现,而又不希望这些辅助方法称为公共接口的一部分,最好将这样的方法设置为私有方法。

3.11 final实例字段

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

四、静态字段与静态方法

4.1 静态字段

如果将一个字段定义为static,每个类只能由一个这样的字段。

4.2 静态常量

维护了一个属于类的常量,避免每个对象需要创建一个常量实例。

4.3 静态变量

静态方法是不在对象上执行的方法,即没有隐式参数。静态方法不能访问实例字段,因为静态方法无法在对象上执行操作。但是静态方法可以访问静态字段。

当方法不需要访问对象状态,需要的所有参数都由显示参数提供且方法只需要访问类的静态字段,可使用静态方法。

4.4 工厂方法

静态方法还可以用于静态工厂方法来构建对象,这里的好处在于可以重命名构造器,如果使用构造器,则名称必须与类名相同,如果希望有不同名的构造器时,无法解决;使用构造器也无法改变构造对象的类型,而工厂方法实际上将返回其他类型的对象。

4.5 main方法

main方法不对任何对象进行操作。事实上,在启动程序时还没有任何对象,静态的main方法将执行并构造程序所需要的对象。

  • static void requireNonNull(T obj);
  • static void requireNonNull(T obj, String message);
  • static void requireNonNull(T obj, Supplier messageSupplier);

如果obj为null, 这些方法会抛出一个NullPointerException异常而没有消息或者给定的消息。

  • static T requireNonNullElse(T obj, T defaultObj);
  • static T requireNonNullElseGet(T obj, Supplier defaultSupplier);

如果obj部位null则返回obj, 或者如果obj为null则返回默认对象

五、方法参数

按值调用(call by value)表示方法接收的是调用者提供的值。

按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。

在Java中总是按值调用,方法得到的是所有参数值的一个副本,方法不能修改传递给它任何参数变量的内容。

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

  • 方法可以修改对象参数的状态

  • 方法不能让一个对象参数引用一个新的对象

六、对象构造

6.1 重载

多个方法有相同的名字,不同的参数,称为重载(overload)。编译器查找匹配的过程称为重载解析(overloading resolution)。Java允许重载任何方法,要完整的描述一个方法,需要指定方法名以及参数类型。这叫做方法的签名(signature)。

返回值不是方法签名的一部分,不能有两个方法名,参数列表相同却返回值不同的方法!

6.2 默认字段初始化

如果在构造器中没有显示地为字段设置初始值,那么就会自动地赋为默认值

数值型为0;

布尔值为false;

对象引用为null;

6.3 无参数的构造器

如果没有编写构造器,编译器会为我们提供这样一个无参构造器。这个构造器将会把所有实例字段设置为默认值。

6.4 显式字段初始化

通过重载类的构造器方法,可以采用多种形式设置类的实例字段的初始状态。初始值不一定为常量值,也可以利用方法调用初始化一个字段。

6.5 参数名

参数变量会遮蔽同名的实例字段,所以可以在实例字段前加this关键字表示隐式参数调用,也就是所需要构造的对象。

6.6 调用另一个构造器

this不仅可以表示隐式参数,还可以用于其他构造器的第一个语句,表示这个构造器将会调用同一个类的另一个构造器。

6.7 初始化块

初始化数据字段有两种方法:

  • 在构造器中设置值;
  • 在声明中赋值;

实际上,还可以通过初始化块(initialization block)。在一个类的声明中,可以有多个代码块,只要构造这个类的对象,这些块就会执行。

静态的初始化块会在类第一次加载的时候,将会进行静态字段的初始化

6.8 对象析构与finalize方法

在C++中,有显式的析构器方法,其中放置一些当对象不再使用时需要执行的清理代码。在析构器中,最常见的操作时回收分配给对象的存储空间。由于Java会自动地完成垃圾回收,所以不需要人工回收内存。

不要使用finalize方法,这个方法原本要在垃圾回收器清理对象之前调用。

七、包

Java允许使用包(package)来将类组织在一个集合中。借助包可以方便地组织自己的代码,并将自己的代码与别人提供的代码库分开管理。

7.1 包名

使用包的主要原因时要确保类名的唯一性。假如两个程序员都建立了Employee类,只要把类放置在不同的包中,就不会产生冲突。事实上,为了保证包名的绝对唯一性,要用一个因特网域名以逆序的形式的作为包名,然后对不同的工程使用不同的子包。

完全限定名 : 如com.horstmann.corejava.Employee称为类的全限定名

7.2 类的导入

一个类可以使用所属包中的所有类,以及其他包中的公共类。

可以使用完全限定名(fully qualified name);就是包名后跟着类名。

java.time.LocalDate today = java.time.LocalDate.now();

更简单的方式是使用import语句。import是一种引用包中各个类的简捷方式。

7.3 静态导入

有一种import语句允许导入静态方法和静态字段,而不只是类。

如在源文件顶部添加import static java.lang.System.*;

就可以使用System类的静态方法和静态字段,而不必加前缀名。

7.4 在包中增加类

要想把类放入包中,就必须将包的名字放在源文件的开头,即放在定义这个包中各个类的代码之前。

如果没有在源文件中放置package语句,那么这个类就属于无名包(unnamed package)。

7.5 包访问

标记为public的部分可以由任意类使用;

标记为private的部分只能由定义他们的类使用;

7.6 类路径

类存储在文件系统的子目录中。类路径必须和包名匹配。

类也可以存储在Jar文件中,jar以zip形式组织文件和子目录,可以使用任何zip工具查看jar文件

7.7 设置类路径

可以使用-classpath选择指定类路径

八、JAR文件

一个jar文件既可以包含类文件,也可以包含诸如图像和声音等其他类型的文件。jar文件是压缩的,它使用了我们常见的zip压缩格式。

8.1 创建JAR文件

可以使用jar工具来制作JAR文件(在默认的JDK安装中,这个工具位于jdk/bin目录下)。创建一个新的JAR文件最常见的命令使用如下语法:

1
2
jar cvf jarFileName file1 file2 ...
jar options file1 file2...

jar程序选项

选项 说明
c 创建一个新的或者空的存档文件并加入文件。如果指定的文件名是目录,jar程序将会对它们进行递归处理
C 临时改变目录,例如:jar cvf jarFile.jar -C classes *.class,切换到classes目录以便增加类文件
e 在清单文件中创建一个入口点
f 指定JAR文件名作为第二个命令行参数。如果没有这个参数,jar命令会将结果写至标准输出(在创建jar文件时)或者从标准输入读取(在解压或者列出JAR文件内容时)
i 建立索引文件(用于加快大型归档中的查找)
m 将一个清单文件添加到JAR文件中。清单是对归档内容和来源的一个说明。每个归档有一个默认的清单文件。但是,想要验证归档文件的内容,可以提供自己的清单文件
M 不为条目创建清单文件
t 显示内容表
u 更新一个已有的jar文件
v 生成详细的输出结果
x 解压文件。如果提供一个或多个文件名,只解压这些文件;否则,解压所有文件
0 存储,但不进行zip解压

8.2 清单文件

除了类文件、图像和其他资源外,每个jar文件还包含一个清单文件(manifest),用来描述文档的特殊特性。

清单文件被命名为MANIFEST.MF,它位于JAR文件的一个特殊的META-INF子目录中。符合标准的最小清单文件极其简单:

Manifest-Version: 1.0

复杂的清单文件可能包含更多条目。这些条目被分为多个节。第一节被称为主节(main section)。它作用于整个jar文件。随后的条目用来指定命名实体的属性,如单个文件,包或者URL。

要想编辑清单文件,需要将希望添加到清单文件的行放到文本文件中,然后运行:

1
jar cfm jarFIleName manifestFileName...

要想更行一个已有的jar文件清单,则需要将添加的部分放置到一个文本文件中,然后执行如下命令

1
jar ufm jarFileName manifest-additions.mf

8.3 可执行jar文件

可以使用jar命令的e选择指定程序的入口点,即通常需要在调用Java程序启动器时指定的类。

1
java cvfe jarFileName classpath files to add

或者,可以在清单文件中指定程序的主类

1
Main-Class: classpath.mainClassName

在Mac OSX平台,jar文件可以直接双击打开

在Windows平台可以通过

1
java -jar jarFileName.jar

来运行jar包,也可以使用第三方工具如Launch4J和LzPack来将jar文件转换为.exe文件

8.4 多版本jar文件

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

1
java -d bin/8 --release 8 ...

8.5 命令行选项的说明

JDK命令行一直都以单个短横线加多字母选项名的形式,但是jar命令是个例外,同时从Java9开始,Java工具开始转向一种更为常用的选项格式,多字母选项名前加两个短横线,另外对于常见的选项可以使用单字母快捷方式。

九、文档注释

JDK包含有一个很有用的工具,叫做Javadoc,它可以由源文件生产一个HTML文档。

如果在源代码中添加以特殊定界符/**开始注释,可以快速生产一个看上去具有专业水平的文档。

9.1 注释的插入

javadoc实用工具从下面几项中抽取信息:

模块;

包;

公共类与接口;

公共的和受保护的字段;

公共的和受保护的构造器和方法;

每个/ * * ...*/文档注释包括标记以及之后紧跟着的自由格式文本(free-form text)。标记以@开始,如@since,@param

9.2 类注释

类注释必须放在import语句之后,类定义之前。

9.3 方法注释

方法注释必须放在方法之前。

  • @param variable description

这个标记将给当前方法的“parameters”(参数)部分添加一个条目。这个描述可以占据多行,并且可以使用HTML标记。一个方法的所有@param标记必须放在一起。

  • @return description

这个标记将给当前方法添加“return”(返回)部分。这个描述可以跨多行,并且可以使用HTML标记。

  • @throws class description

这个标记将添加一个注释,表示这个方法有可能抛出异常。

9.4 字段注释

只需要对公共字段(通常指静态常量)建立文档。

9.5 通用注释

@since text会建立一个“since”(始于)条目。text(文本)可以是引入这个特性的版本的任何描述。

@author name表示产生一个作者条目,可以使用多个,每个对应一个作者;

@version text 这个标记将会产生一个“version”(版本)条目。这里的文本可以是对当前版本的任何描述。

@see@link 可以使用超链接,链接到Javadoc文档的相关部分或外部文档。

@see @reference将在“see also"参见部分添加一个超链接。它可以用于类中,也可以用于方法中。

9.6 包注释

可以直接将类、方法和常量的注释放置在Java源文件中,要想产生包注释,需要在每一个包中添加一个单独的文件。

提供一个名为package-info.java的Java文件。这个文件必须包括一个初始的以/** */界面的javadoc注释,后面是一个package语句,不能包含的代码或注释

提供一个名为package.html的HTML文件。会抽取标记

之间的所有文本。

9.7 注释抽取

如果是一个包

1
javadoc -d docDiretory nameOfPackage

如果是多个包

1
javadoc -d docDiretory nameOfPackage1 nameOfPackage2

如果文件在无名的包中

1
javadoc -d docDiretory *.java

十、类设计技巧

  1. 一定要保证数据私有
  2. 一定要对数据进行初始化
  3. 不要在类中使用过多的基本数据类型
  4. 不是所有的字段都需要单独的字段访问器和字段更改器
  5. 分解有过多职责的类
  6. 类名和方法名要能够体现它们的职责
  7. 优先使用不可变的类