第4章 对象与类
4.1 面向对象程序设计概述
- 实现封装的关键在于,绝对不能让类中的方法直接访问其他类的实例字段。程序只能通过对象的方法与对象数据进行交互。
- 对象的三个主要特性:
- 对象的行为
- 对象的状态
- 对象的标识
- 识别类的一个简单经验是在分析问题的过程中寻找名词,而方法对应着动词。
- 类之间的关系:
- 依赖:一个类的方法使用或操纵另一个类的对象
- 聚合:包含关系意味着类A的对象包含类B的对象
- 继承:表示一个更特殊的类与一个更一般的类之间的关系
4.2 使用预定义类
-
Java中使用构造器来构造新实例。构造器的名字应该与类名相同。Date deadline = new Date();变量
deadline不是一个对象,在它引用任何对象前,不能使用Date方法,否则将产生编译错误。注意:对象变量并没有实际包含一个对象,它只是引用一个对象。
-
所有的
Java对象都存储在堆中。 -
不要使用构造器来构造
LocalDate类的对象。实际上,应当使用静态工厂方法,它会代表你调用构造器。不通过
new,而是用一个静态方法来对外提供自身实例的方法,即为我们所说的静态工厂方法(Static factory method) -
访问器方法:只访问对象而不修改对象的方法
更改器方法:访问对象并且修改对象的方法
4.3 用户自定义类
-
文件名必须与
public类的名字相匹配。在一个文件中,只能有一个公共类,但是可以有任意数目的非公共类。 -
构造器没有返回值。构造器总是伴随着
new操作符一起调用。 -
不要在构造器中定义与实例字段同名的局部变量。
-
Java 10中,如果可以从变量的初始值推导出它们的类型,那么可以用var关键字声明局部变量,而无须指定类型。注意var关键字只能用于方法中的局部变量。Employee harry = new Employee("Harry Hacker", 50000, 1989, 10, 1); var harry = new Employee("Harry Hacker", 50000, 1989, 10, 1); -
对象变量可以包含一个特殊值
null,如果对null值应用一个方法,就会产生一个NullPointerException异常。定义一个类时,避免将某些字段定义为
null的两种解决方法:-
“宽容型”方法是把
null参数转换为一个适当的非null值if (n == null) name = "unknow" else name = n; // Objects类提供了一个便利方法 public Employee(String n, double s, int year, int month, int day) { name = Objects.requireNonNullElse(n, "unknow"); } -
“严格型”方式则是拒绝
null参数public Employee(String n, double s, int year, int month, int day) { name = Objects.requireNonNull(n, "The name cannot be null"); }
-
-
方法的隐式参数和显式参数:
- 隐式参数:调用方法的对象
- 显式参数:传入方法的实参
在每一个方法中,关键字
this指示隐式参数。 -
Java的所有方法都必须在类的内部定义,但并不一定是内联函数。是否为内联函数将由Java虚拟机决定。 -
对实例字段的封装:
- 一个私有数据字段
- 一个公共的字段访问器方法
- 一个公共的字段更改器方法
-
注意不要编写返回可变对象引用的访问器方法,对于这种情况,应该对可变对象进行克隆
(clone)。否则会破坏封装。 -
一个方法可以访问所属类的所有对象的私有数据。
-
实例字段如果定义为
final,则该字段必须在构造对象时初始化,并且以后不能再修改,类似于C/C++中的const。final修饰符对于类型为基本类型或者不可变类(如String类)的字段非常有用。final修复可变的类时,存储在变量中的对象引用不能再指向另一个不同的对象,但是指向的对象是可以改变的。// C/C++ char hello[] = {"Hello"}; char* const ptr = hello; // final修复可变的类时与ptr变量类似。ptr为常量指针,初始化后,ptr指向的地址就不能再改变,不过指向的地址中的数据是可以改变的。
4.4 静态字段和静态方法
-
如果将类中的一个实例字段定义为
static,这个字段就为静态字段。该字段属于类,不属于任何单个的对象。但是对每个对象来说,该字段是共享的。 -
静态常量:
public class Math{ public static final double PI = 3.14159265358979323846; ... }对于上述代码。可以使用
Math.PI来访问这个常量。如果去掉static,只能由某个对象来访问PI。 -
静态方法:不在对象上执行,没有隐式参数,只能提供显式参数。不能访问实例字段,但是可以访问静态字段。可以用类名调用静态方法。
-
使用静态工厂方法来构造对象。
- 静态工厂方法可以有不同的名字,不会局限在类名;
- 可以返回构造对象类型的子类;
-
每一个类都可以有一个
main方法,可以用于单元测试。
4.5 方法参数
-
参数传递可以分为:
- 按值调用
(call by value) - 按引用调用
(call by reference)
Java程序中总是按值调用。 - 按值调用
-
有两种类型的方法参数:
- 基本数据类型(数字、布尔值)
- 对象引用
在
Java中:- 方法不能修改基本数据类型的参数
- 方法可以改变对象参数的状态
- 方法不能让对象参数引用一个新的对象
4.6 对象构造
-
Java中允许重载任何方法,包括构造器方法。方法名以及参数类型构成了方法的签名。 -
如果构造器中没有显式的为字段设置初值,那么字段会被自动赋为默认值:数值为0,布尔值为false,对象引用为null。注意方法中的局部变量必须要明确的初始化。
-
仅当类没有任何其他构造器时,才会得到一个默认的无参构造器。如果类中提供了一个构造器,但是没有提供无参构造器,那么构造对象时不提供参数就是不合法的。
public ClassName() { } -
显式字段初始化:
public Employee() { private String name = ""; ... }可以利用方法调用初始化一个字段。
-
参数名
// 方法1 public Employee(String aName, double aSalary){ name = aName; salary = aSalary; } // 方法2 public Employee(String name, double salary){ this.name = name; this.salary = salary; } -
调用另一个构造器
public Employee(double s) { // calls Employee(String, double) this("Employee #" + nextId, s); nextId++; }this(...)语句必须是构造器的第一行语句。 -
初始化数据字段的方法:
- 在构造器中设置值
- 在声明中显式的赋值
- 初始化块
-
调用构造器的具体处理步骤:
1)如果构造器第一行调用了另一个构造器,则基于所提供的参数执行第二个构造器。
2)否则,
a)所有数据字段初始化为其默认值(0、false或null)。
b)按照在类声明中出现的顺序,执行所有字段初始化方法和初始化块。
3)执行构造器主体代码。
-
静态字段的初始化:
- 在声明中赋值
- 静态初始化块,只在类第一次加载时执行
-
Java不支持析构器。
4.7 包
-
为了保证包名的唯一性,要用一个因特网域名(这显然是唯一的)以逆序的形式作为包名,然后对于不同的工程使用不同的子包。
-
一个类可以访问所属包中的所有类,以及其他包中的公共类。
-
两种方法访问另一个包中的公共类:
- 使用完全限定名
- 使用
import语句
-
import语句应该位于源文件的顶部,但位于package语句的后面 -
静态导入:允许导入静态方法和字段,还可以导入特定的方法和字段。
import static java.lang.System.*; out.println("Hello, world!"); // 静态导入后,可以不加类名前缀 -
要想将类放入包中,就必须将包的名字放在源文件的开头。没有在源文件中放置
package语句,这个源文件中的类就属于无名包。 -
应该将源文件放到与完整包名匹配的子目录中。如
com.horstmann.corejava包中所有源文件应该放置在子目录com/horstmann/corejava中。 -
如果没有指定
public或private,这个部分(类、方法或变量)可以被同一个包中的所有方法访问。变量必须显式的声明为private,否则的话将默认为包可访问,这会破坏封装性。 -
为了使类能被多个程序共享,需要做到下面几点:
-
把类文件放到一个目录中。例如
/home/user/classdir,注意这个目录是包树状结构的基目录。 -
将
JAR文件放在一个目录中。如/home/user/archives。 -
设置类路径
(class path)。类路径是所有包含类文件的路径的集合。
-
-
类路径包括:
- 基目录,
/home/user/classdir - 当前目录
(.) JAR文件/home/user/archives/archives.jar
Unix中:/home/user/classdir:.:/home/user/archives/'*'。注意*必须转义以防止shell扩展Windows中:c:\classdir;.;c:\archives\*。 - 基目录,
-
只可以从其他包中导入公共类。还可以从当前包中导入非公共类。
-
设置类路径:
-
Unix:java -classpath /home/user/classdir:.:/home/user/archives/archives.jar MyProg还可以使用
-cp,或者Java 9中的--class-path -
Windows:java -classpath c:\classdir;.;c:\archives\archives.jar MyProg
或者通过
CLASSPATH环境变量设置:bash shell:export CLASSPATH=/home/user/classdir:.:/home/user/archives/archives.jarWindows shell:set CLASSPATH=c:\classdir;.;c:\archives\archives.jar
直到
shell退出,类路径设置均有效。 -
4.8 JAR文件
-
一个
JAR文件即可以包含类文件,也可以包含诸如图像和声音等其他类型的文件。JAR文件是ZIP压缩格式的。 -
使用
jdk/bin文件下的jar工具制作JAR文件的常用语法:jar cvf jarFileName file1 file2 ... -
清单文件,用于描述归档文件的特殊特性,被命名为
MANIFEST.MF,位于JAR文件的一个特殊的META-INF子目录中。
4.9 文档注释
javadoc工具从下面几项中抽取信息:- 模块
- 包
- 公共类与接口
- 公共的和受保护的字段
- 公共的和受保护的构造器及方法
- 每个
/**...*/文档注释包含标记以及之后紧跟着的自由格式文本。标记以@开始,如@since或@param。 - 在自由格式文本中可以使用
HTML修饰符。 - 要想产生包注释,需要在每一个包目录中添加一个单独的文件。
- 注释抽取使用
javadoc工具。
4.10 类设计技巧
- 一定要保证数据私有
- 一定要对数据进行初始化
- 不要在类中使用过多的基本类型
- 不是所有的字段都需要单独的字段访问器和字段更改器
- 分解有过多职责的类
- 类名和方法名要能够体现它们的职责
- 优先使用不可变的类

浙公网安备 33010602011771号