概述
第4章 对象与类
4.1 面向对象程序设计概述
介绍
- 面向对象的程序是由对象组成的, 每个对象包含对用户公开的特定功能部分和隐藏的实现部分。
次序
- 而 OOP 却调换了这个次序, 将数据放在第一位,然后再考虑操作数据的算法。
4.1.1 类
实例
- 由类构造(construct) 对象的过程称为创建类的实例 (instance ).
4.1.2 对 象
三个主要特性
-
对象的行为(behavior)
可以对对象施加哪些操作,或可以对对象施加哪些方法?
-
对象的状态(state )
—当施加那些方法时,对象如何响应?
-
对象标识(identity )
对象标识(identity )如何辨别具有相同行为与状态的不同对象?
行为
- 对象的行为是用可调用的方法定义的。
状态
- 对象的状态是每个对象都保存着描述当前特征的信息。
标识
- 对象的状态并不能完全描述一个对象。每个对象都有一个唯一的身份( identity)。
4.1.3 识别类
简单规则
- 识别类的简单规则是在分析问题的过程中寻找名词,而方法对应着动词。
4.1.4 类之间的关系
常见关系
- 依赖(“ uses-a”)
- 聚合(“ has-a”)
- 继承(“ is-a”)
依赖
- 依赖( dependence ), 即“ uses-a” 关系, 是一种最明显的、 最常见的关系。
聚合
- 聚合(aggregation ), 即“ has-a ” 关系, 是一种具体且易于理解的关系。
继承
- 继承( inheritance ), 即“ is-a” 关系, 是一种用于表示特殊与一般关系的。
表达式关系的UML符号
UML
- 很多程序员采用 UML ( Unified Modeling Language , 统一建模语言)绘制类图,用来描述类之间的关系。
4.2 使用预定义类
4.2.1 对象与对象变量
构造器
- Java 程序设计语言中, 使用构造器(constructor ) 构造新实例。
- 构造器是一种特殊的方法, 用来构造并初始化对象。
变量值
- 一定要认识到: 一个对象变量并没有实际包含一个对象,而仅仅引用一个对象。
- 在 Java 中,任何对象变量的值都是对存储在另外一个地方的一个对象的引用。new 操作符的返回值也是一个引用。
与C++区别
- 很多人错误地认为 Java 对象变量与 C++ 的引用类似。
- 在 C++ 中没有空引用, 并且引用不能被赋值。可以将 Java 的对象变量看作 C++ 的对象指针。
4.2.2 Java 类库中的 LocalDate 类
UTC时间
- UTC 时间 1970 年 1 月 1 日 00:00:00。UTC 是 Coordinated Universal Time 的缩写。
LocalDate的使用
- 不要使用构造器来构造 LocalDate 类的对象。实际上,应当使用静态工厂方法 (factory method) 代表你调用构造器。
Local Date.now();//静态方法,返回一个实例
LocalDate.of(1999, 12, 31);
LocalDate newYearsEve = Local Date.of(1999, 12, 31);
int year = newYearsEve.getYearO; // 1999
int month = newYearsEve.getMonthValueO; // 12
int day = newYearsEve.getDayOfMonth(); // 31
LocalDate aThousandDaysLater = newYearsEve.piusDays(1000);//未修改
year = aThousandDaysLater.getYearO;// 2002
month = aThousandDaysLater.getMonthValueO; // 09
day = aThousandDaysLater.getDayOfMonth(); // 26
4.2.3 更改器方法与访问器方法
更改器方法
- 调用更改器方法 ( mutatormethod ),对象的状态会改变。
访问器方法
- 只访问对象而不修改对象的方法有时称为访问器方法。
4.3 用户自定义类
自定义类
- 这些类没有 main 方法, 却有自己的实例域和实例方法。
- 要想创建一个完整的程序, 应该将若干类组合在一起, 其中只有一个类有 main 方法。
4.3.1 Employee 类
类定义形式
class ClassName {
field1
field2
...
constructor1
constructor2
...
method1
method2
...
}
4.3.2 多个源文件的使用
两种编译源程序的方法
-
一种是使用通配符调用 Java编译器。于是,所有与通配符匹配的源文件都将被编译成类文件。
javac Employee*.java
-
第二种方式不会显式编译,当Java编译器发现使用了XXX类后会查找名为XXX.class的文件。如果没找到该文件,就会自动搜索XXX.java并对其编译。
javac EmployeeTest.java
Java编译器自动重编
- 如果XXX.java版本较已有的class文件新,Java编译器就会自动重新编译这个文件。
4.3.3 剖析 Employee 类
- 类通常包括类型属于某个类类型的实例域。
4.3.4 从构造器开始
构造器
- 构造器与类同名。在构造 Employee 类的对象时,构造器会运行,以便将实例域初始化为所希望的状态。
- 构造器总是伴随着 new 操作符的执行被调用,而不能对一个已经存在的对象调用构造器来达到重新设置实例域的目的。
构造器内容
- 构造器与类同名
- 每个类可以有一个以上的构造器
- 构造器可以有 0 个、1 个或多个参数
- 构造器没有返回值
- 构造器总是伴随着 new 操作一起调用
构造器局部变量
- 不要在构造器中定义与实例域重名的局部变量。
- 这些变量屏蔽了同名的实例域。
4.3.5 隐式参数与显式参数
隐式参数
number007. raiseSalary(5);
- 第一个参数称为隐式 ( implicit ) 参数, 是出现在方法名前的Employee 类对象。
- 第二个参数位于方法名后面括号中的数值,这是一个显式(explicit)参数 。
- 有些人把隐式参数称为方法调用的目标或接收者。
- 在每一个方法中, 关键字 this 表示隐式参数。
4.3.6 封装的优点
域访问器
- 只返回实例域值,因此又称为域访问器。
好处
- 可以改变内部实现,除了该类的方法之外,不会影响其他代码。
- 更改器方法可以执行错误检查,然而直接对域进行赋值将不会进行这些处理。
注意返回引用引用可变
- 注意不要编写返回引用可变对象的访问器方法。
- 如果需要返回一个可变数据域的拷贝,就应该使用 clone。
4.3.7 基于类的访问权限
注意对整个类的访问权限
- 一个方法可以访问所属类的所有对象的私有数据,
class Employee {
public boolean equals(Employee other) {
return name.equals(other.name);
}
}
...
if (harry,equals(boss))//调用别的实例的私有域
- C++ 也有同样的原则。方法可以访问所属类的私有特性(feature ), 而不仅限于访问隐式参数的私有特性。
4.3.8 私有方法
私有情况
- 有时,可能希望将一个计算代码划分成若干个独立的辅助方法。
4.3.9 final 实例域
final实例域
- 可以将实例域定义为 final。构建对象时必须初始化这样的域。
- 必须确保在每一个构造器执行之后,这个域的值被设置, 并且在后面的操作中,不能够再对它进行修改。
- final 关键字只是表示存储在xxx 变量中的对象引用不会再指示其他对象,不过这个对象可以更改.
4.4 静态域与静态方法
4.4.1 静态域
静态域
- 如果将域定义为 static, 每个类中只有一个这样的域。
- 它属于类,而不属于任何独立的对象。
- 而每一个对象对于所有的实例域却都有自己的一份拷贝。
4.4.2 静态常量
- 静态变量使用得比较少,但静态常量却使用得比较多。
本地方法修改final
- 本地方法不是用 Java 语言实现的。
- 本地方法可以绕过 Java 语言的存取控制机制。
4.4.3 静态方法
- 静态方法没有隐式的参数。
- 静态方法不能访问例域,因为它不能操作对象。
- 但是静态方法可以访问自身类中的静态域。
静态方法使用
- 可以使用对象调用静态方法, 但建议使用类名,而不是对象来调用
静态方法。
推荐使用静态方法
- 一个方法不需要访问对象状态,其所需参数都是通过显式参数提供(例如:Math.pow) 。
- 一个方法只需要访问类的静态域(例如:Employee.getNextldh)。
4.4.4 工厂方法
不用构造器原因
- 无法命名构造器。构造器的名字必须与类名相同。但是, 这里希望将得到的货币实例和百分比实例采用不用的名字。
- 当使用构造器时,无法改变所构造的对象类型。而 Factory 方法将返回一个 DecimalFormat类对象,这是 NumberFormat 的子类。
4.4.5 main 方法
特定
- main 方法也是一个静态方法。
- main 方法不对任何对象进行操作。
- 事实上,在启动程序时还没有任何一个对象。静态的main 方法将执行并创建程序所需要的对象。
使用main方法单元测试
- 每一个类可以有一个 main 方法。这是一个常用于对类进行单元测试的技巧。
4.5 方法参数
两种调用方式
- 按值调用(call by value) 表示方法接收的是调用者提供的值。
- 按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。
- 一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。
改变参数状态 - 实现一个改变对象参数状态的方法并不是一件难事。
- 理由很简单,方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。
Java对象引用实质
public static void swap(Employee x , Employee y) { // doesn't work
Employee temp = x;
x = y;
y = temp;
}
//如果 Java 对对象采用的是按引用调用,那么这个方法就应该能够实现交换数据的效果:
Employee a = new Employee("Alice", . . .);
Employee b = new Employee("Bob", . . .);
swap(a, b);
// does a now refer to Bob, b to Alice?
//但是,方法并没有改变存储在变量 a 和 b 中的对象引用。swap 方法的参数 x 和 y 被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝。
// x refers to Alice, y to Bob
Employee temp = x;
x = y;
y = temp;
// now x refers to Bob, y to Alice
//最终,白费力气。在方法结束时参数变量 X 和 y 被丢弃了。原来的变量 a 和 b 仍然引用这个方法调用之前所引用的对象.
- Java 程序设计语言对对象采用的不是引用调用,实际上,对象引用是按值传递的。
参数使用情况
- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。
- 一个方法可以改变一个对象参数的状态。
- 一个方法不能让对象参数引用一个新的对象。
4.6 对象构造
4.6.1 重载
重载定义
- 如果多个方法有相同的名字、不同的参数,便产生了重载。
- 编译器通过用各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应的方法。
- Java 允许重载任何方法。
方法的签名
- 要完整地描述一个方法,需要指出方法名以及参数类型。这叫做方法的签名(signature)。
- 返回类型不是方法签名的一部分。
4.6.2 默认域的初始化
域的默认值
- 如果在构造器中没有显式地给域赋予初值,那么就会被自动地赋为默认值: 数值为 0、布尔值为 false、 对象引用为 null。
局部变量默认值
- 必须明确地初始化方法中的局部变量。 但是,如果没有初始化类中的域, 将会被自动初始化为默认值(0、 false 或 null )。
4.6.3 无参数的构造器
无参数构造器
- 对象由无参数构造函数创建时, 其状态会设置为适当的默认值。
- 如果在编写一个类时没有编写构造器, 那么系统就会提供一个无参数构造器。这个构造器将所有的实例域设置为默认值。
- 如果类中提供了至少一个构造器, 但是没有提供无参数的构造器, 则在构造对象时如果没有提供参数就会被视为不合法。
注意
- 仅当类没有提供任何构造器的时候, 系统才会提供一个默认的构造器。
4.6.4 显式域初始化
直接给域赋值
- 可以在类定义中, 直接将一个值赋给任何域。
- 在执行构造器之前,先执行赋值操作。
C++区别
- 在 C++ 中, 不能直接初始化类的实例域。 所有的域必须在构造器中设置。
4.6.5 参数名
参数名技巧
- 在每个参数前面加上一个前缀“ a”。
- 参数变量用同样的名字将实例域屏蔽起来,采用 this.xxx的形式访问实例域。
4.6.6 调用另一个构造器
关键字 this构造
- 如果构造器的第一个语句形如 this(…), 这个构造器将调用同一个类的另一个构造器。
C++区别
- 在 C++ 中, 一个构造器不能调用另一个构造器 ,在 C++ 中,必须将抽取出的公共初始化代码编写成一个独立的方法。
4.6.7 初始化块
初始化数据域的方法
- 在构造器中设置值
- 在声明中赋值
- 初始化块
初始化块
- 在一个类的声明中,可以包含多个代码块。只要构造类的对象,这些块就会被执行。
- 首先运行初始化块,然后才运行构造器的主体部分。
- 建议将初始化块放在域定义之后。
调用构造器步骤
- 所有数据域被初始化为默认值(0、false 或 null)。
- 按照在类声明中出现的次序, 依次执行所有域初始化语句和初始化块。
- 如果构造器第一行调用了第二个构造器, 则执行第二个构造器主体。
- 执行这个构造器的主体.。
静态的初始化块
- 将代码放在一个块中,并标记关键字 static。
// static initialization block
static {
Random generator = new Random0;
nextld = generator.nextlnt(lOOOO) ;
}
静态域初始化
- 类第一次加载的时候, 将会进行静态域的初始化。
- 与实例域一样,除非将它们显式地设置成其他值, 否则默认的初始值是 0、 false 或 null。
没有main方法的程序
public class Hello {
static {
System.out.println("Hel1o, World");
}
}
- 在 JDK 6 之前,都可以用 Java 编写一个没有 main 方法的“ Hello, World” 程序 。
- 从 Java SE 7 以后,java 程序首先会检查是否有一个 main 方法。
4.6.8 对象析构与 finalize 方法
析构器
- 由于 Java 有自动的垃圾回收器,不需要人工回收内存, 所以 Java 不支持析构器。
finalize方法
- finalize 方法将在垃圾回收器清除对象之前调用。
- 在实际应用中,不要依赖于使用 finalize 方法回收任何短缺的资源,这是因为很难知道这个方法什么时候才能够调用。
处理资源,人工关闭*
- 如果某个资源需要在使用完毕后立刻被关闭, 那么就需要由人工来管理。
- 对象用完时,可以应用一个 close 方法来完成相应的清理操作。
4.7 包
包的功能
- Java 允许使用包( package > 将类组织起来。
包的唯一性
- 为了保证包名的绝对唯一性, Sun 公司建议将公司的因特网域名(这显然是独一无二的)以逆序的形式作为包名,并且对于不同的项目使用不同的子包。
4.7.1 类的导入
类的访问
- 一个类可以使用所属包中的所有类, 以及其他包中的公有类(public class)。
访问类的方式
- 在每个类名之前添加完整的包名。
- 更简单且更常用的方式是使用 import 语句。
import语句
- import 语句是一种引用包含在包中的类的简明描述。
- 一旦使用了 import 语句,在使用类时,就不必写出包的全名了。
注意"*"通配符
- 需要注意的是, 只能使用星号(*) 导入一个包, 而不能使用 import java.* 或import java.*.* 导入以 java 为前缀的所有包。
类文件使用完整包名
- 在包中定位类是编译器( compiler) 的工作。类文件中的字节码肯定使用完整的包名来引用其他类。
和C++的区别
- 在 C++ 中, 必须使用 include 将外部特性的声明加栽进来, 这是因为 C++ 编译器无法查看任何文件的内部。
- 在 C++ 中, 与 包 机 制 类 似 的 是 命 名 空 间(namespace)。
4.7.2 静态导入
导入静态方法和域
- import 语句不仅可以导人类,还增加了导人静态方法和静态域的功能。
4.7.3 将类放入包中
类的默认位置
- 如果没有在源文件中放置 package 语句, 这个源文件中的类就被放置在一个默认包(defaulf package)中。
- 将包中的文件放到与完整的包名匹配的子目录中,编译器将类文件也放在相同的目录结构中。
注意编译器和解释器目录操作
javac com/myconipany/Payrol1App.java
java com.mycompany.PayrollApp
- 编译器对文件 (带有文件分隔符和扩展名 .java 的文件)进行操作。
- 而 Java 解释器加载类(带有 . 分隔符 )。
注意编译器不检查目录
- 编译器在编译源文件的时候不检查目录结构。
- 如果包与目录不匹配, 虚拟机就找不到类 ,除非先将所有类文件移到正确的位置上,否则程序将无法运行。
4.7.4节在applet窗口中修改警告字符串故事:
在 Java 程序设计语言的早期版本中, 只需要将下列这条语句放在类文件的开头, 就可以很容易地将其他类混人 java.awt包中:
package java.awt;
然后, 把结果类文件放置在类路径某处的 java/awt子目录下, 就可以访问 jaVa.awt 包的内部了。
使用这一手段, 可以对警告框进行设置。
4.7.4 包作用域
- 标记为 public 的部分可以被任意的类使用;
- 标记为 private 的部分只能被定义它们的类使用。
- 如果没有指定 public 或 private, 这 个 部分(类、方法或变量)可以被同一个包中的所有方法访问。
4.8 类路径
归档文件JAR
- 类文件也可以存储在 JAR(Java归档 )文件中。
类的共享
- 把类放到一个目录中( 这个目录是包树状结构的基目录)。
- 将 JAR 文件放在一个目录中。
- 设置类路径(classpath)。
类路径
- 类路径是所有包含类文件的路径的集合。
几点注意
-
句点(.)表示当前目录。
-
从 Java SE 6 开始, 可以在 JAR 文件目录中指定通配符。
/home/user/dassdir:.:/home/aser/archives/’*’
c:classdir;.;c:archives* -
UNIX 中,禁止使用 * 以防止 shell 命令进一步扩展。
虚拟机查找类文件
- 首先要查看存储在jre/lib 和jre/lib/ext 目录下的归档文件中所存放的系统类文件。
- 然后再查看类路径。( Java 虚拟机仅在类路径中有“.”目录的时候才查看当前目录)
编译器定位文件
- 首先查找包含这个类的包,并询查所有的 import 指令,确定其中是否包含了被引用的类。
- 注意编译器会试图查找 java.lang包,因为默认该包导入。
- 注意 如果找到了一个以上的类,就会产生编译错误。
- 类必须是唯一的,而 import 语句的次序却无关紧要。
编译器其他工作
- 查看源文件(Source files) 是否比类文件新。如果是这样的话,那么源文件就会被自动地重新编译。
- 如果从当前包中导人一个类, 编译器就要搜索当前包中的所有源文件, 以便确定哪个源文件定义了这个类。
注意
- 仅可以导人其他包中的公有类,和当前包中的非公有类。
- 一个源文件只能包含一个公有类,并且文件名必须与公有类匹配。
4.8.1 设置类路径
-
采用 -classpath (或 -cp) 选项指定类路径:
java -classpath /home/user/dassdir:.:/home/user/archives/archive.jar HyProg
java -classpath c:classdir;.;c:archivesarchive.jar MyProg -
也可以通过设置 CLASSPATH 环境变量完成这个操作。
-
bash中:
export CLASSPATH=/home/user/classdir:.???? home/user/archives/archive.jar
-
Windows shell中:
set CLASSPATH=c:classdir;.;c:archivesarchive.jar
4.9 文档注释
实用工具javadoc
- JDK 包含一个很有用的工具,叫做javadoc, 它可以由源文件生成一个 HTML 文档。
4.9.1 注释的插入
几个特性中抽取信息
- 包
- 公有类与接口
- 公有的和受保护的构造器及方法
- 公有的和受保护的域
自由格式文本
- 每个 /** . . . */ 文档注释在标记之后紧跟着自由格式文本( free-form text )。标记由@开始, 如@author 或@param。
- 自由格式文本的第一句应该是一个概要性的句子。j
- 在自由格式文本中,可以使用 HTML 修饰符.
4.9.2 类注释
位置
- 类注释必须放在 import 语句之后,类定义之前。
注意星号*
- 没有必要在每一行的开始用星号 *
4.9.3 方法注释
- @param 变量描述
- 这个标记将对当前方法的“ param” (参数)部分添加一个条目。
- 这个描述可以占据多行, 并可以使用 HTML 标记。
- 一个方法的所有@param 标记必须放在一起。
- @return 描述
- 这个标记将对当前方法添加“ return” (返回)部分。
- 这个描述可以跨越多行, 并可以使用 HTML 标记。
- @throws 类描述
- 这个标记将添加一个注释, 用于表示这个方法有可能抛出异常。
4.9.4 域注释
- 只需要对公有域(通常指的是静态常量)建立文档。
4.9.5 通用注释
用于类文档的注释中
- @author 姓名
- 这个标记将产生一个 ** author" (作者)条目。
- 可以使用多个@aUthor 标记,每个@author 标记对应一个作者.
- @version 文本
- 这个标记将产生一个“ version”(版本)条目。
- 这里的文本可以是对当前版本的任何描述。
用于所有的文档注释中
- @sinee 文本
- 这个标记将产生一个“ since” (始于)条目。
- 这里的 text 可以是对引人特性的版本描述。
- 例如,©since version 1.7.10
- @deprecated文本
- 这个标记将对类、 方法或变量添加一个不再使用的注释。
- @see 引用
- 这个标记将在“ see also” 部分增加一个超级链接。
- 它可以用于类中,也可以用于方法中。
@see 引用情形
package.class#feature label
<a href="…">label</a>
“text”
几点注意
- 一定要使用井号(#),而不要使用句号(.)分隔类名与方法名,或类名与变量名。
- 如果@see 标记后面有一个 < 字符,就需要指定一个超链接。
- 可以为一个特性添加多个@see 标记,但必须将它们放在一起。
- 还可以在注释中的任何位置放置指向其他类或方法的超级链接, 以及插人一个专用的标记,特性描述规则与@see 标记规则一样。
{@link package,classifeature label ]
4.9.6 包与概述注释
包注释
- 要想产生包注释,就需要在每一个包目录中添加一个单独的文件。
- 提供一个以 package.html 命名的 HTML 文件。在标记<body>…
</body> 之间的所有文本都会被抽取出来。 - 提供一个以 package-info.java 命名的 Java 文件。这个文件必须包含一个初始的以 /**和 */ 界定的 Javadoc 注释,跟随在一个包语句之后。它不应该包含更多的代码或注释。
源文件注释
- 还可以为所有的源文件提供一个概述性的注释。
- 这个注释将被放置在一个名为 overview.html 的文件中,这个文件位于包含所有源文件的父目录中。
4.9.7 注释的抽取
执行以下步骤:
-
切换到包含想要生成文档的源文件目录。如果有嵌套的包要生成文档,就必须切换到包含子目录的目录
-
如果是一个包,应该运行命令:
javadoc -d docDirectory nameOfPackage
-
或对于多个包生成文档, 运行:
javadoc -d docDirectory nameOfPackage nameOfPackage . . .
-
如果文件在默认包中, 就应该运行:
javadoc -d docDirectory *. java
几点注意
-
可以使用 -author 和-version 选项在文档中包含@author 和@version 标记(默认情况下,这些标记会被省略)。
-
选项-link,可以用来为标准类添加超链接。
javadoc -link http://docs.oracle.eom/:javase/8/docs/api *.java
那么,所有的标准类库类都会自动地链接到 Oracle 网站的文档。 -
如果使用-linksource 选项,则每个源文件被转换为 HTML.并且每个类和方法名将转变为指向源代码的超链接。
4.10 类设计技巧
1. 一定要保证数据私有
- 数据的表示形式很可能会改变, 但它们的使用方式却不会经常发生变化。
2. 一定要对数据初始化
- Java 不对局部变量进行初始化, 但是会对对象的实例域进行初始化。
3.不要在类中使用过多的基本类型
- 用其他的类代替多个相关的基本类型的使用,会使类更加易于理解且易于修改。
4.不是所有的域都需要独立的域访问器和域更改器
5.将职责过多的类进行分解
6.类名和方法名要能够体现它们的职责
- 命名类名的良好习惯是采用一个名词(Order )、 前面有形容词修饰的名词( RushOrder)或动名词(有“ -ing” 后缀)修饰名词(例如, BillingAddress )。
- 对于方法来说,习惯是访问器方法用小写 get 开头 ( getSalary ), 更改器方法用小写的 set 开头(setSalary )。
7.优先使用不可变的类
- 更改对象的问题在于, 如果多个线程试图同时更新一个对象, 就会发生并发更改,结果是不可预料。
- 如果类是不可变的,就可以安全地在多个线程间共享其对象。
最后
以上就是坚定故事为你收集整理的第4章 对象与类第4章 对象与类的全部内容,希望文章能够帮你解决第4章 对象与类第4章 对象与类所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复