4.1 面向对象程序设计概述
4.1.3 识别类
设计面向对象的系统时首先从设计类开始,然后每个类中添加方法。
识别类的简单规则是在分析问题的过程中寻找名词,而方法对应着动词。——“名词与动词”原则只是一种粗略的方法,还依赖于个人的开发经验。
4.1.4 类之间的关系
类之间常见的关系有:
- 依赖(dependency):”uses-a”关系。
- 聚合(aggregation):”has-a”关系。
- 继承(inheritance):”is-a”关系
4.2 使用现有类
4.2.1 对象与对象变量
一个对象变量并没有实际包含一个对象,而仅仅引用一个对象。
在Java中,任何对象变量的值都是对存储在另外一个地方的一个对象的引用。new操作符的返回值也是一个引用。
Java中可以将对象变量设置为null,而C++中没有空引用,而且引用不能被赋值。可以将Java的对象看做C++的对象指针。例如:
Date birthday;//Java
实际上等同于:
Date* birthday;//C++
Java中必须使用clone()方法获得对象的完整拷贝。
4.2.2 Java类库中的GregorianCalendar类
Date类的时间是用距离一个固定时间点的毫秒数(可正可负)表示的,这个点即所谓的“纪元”(epoch),它是UTC时间1970年1月1日00:00:00(为何选择这个时间作为“原点”可以参考这里UNIX_Time)。
类库设计者觉得将保存时间与给时间点命名分开,所以标准Java类库分别包含了2个类:一个用来表示时间点的Date类,另一个用来表示大家熟悉的日历表示法的GregorianCalendar类。
4.2.3 更改器方法与访问器方法
对实例域做出修改的方法称为更改器方法(mutator method),仅访问实例域而不进行修改的方法称为访问器方法(accessor method)。
PS: C++ 中带有const后缀的方法是访问器方法,默认为更改器方法。但Java中访问器方法与更改器方法在语法上没有明显的区别。
有关日历需要注意每个星期的第一天存在着不同的约定,美国第一天是周日,欧洲则是周一。
如果想看到不同地区程序的输出,应该在main方法的第一行添加如下代码:
Locale.setDefault(Locale.ITALY);
4.3 用户自定义类
4.3.2 多个源文件的使用
有多个源文件时,有2种编译源程序的方式:
- 使用通配符调用Java编译器:比如有2个类,分别是Employee.java, EmployeeTest.java,则javac Employee*.java
- 利用javac自动搜索源文件:例子同上,直接javac EmployeeTest.java,当编译器发现该类调用了Employee.java,会自动搜索Employee.java并编译,而且已有Employee.class但Employee.java版本更新,Java编译器会自动重新编译Employee.java(相当于内置了“make”的功能)。
4.3.6 封装的优点
注意不要编写返回引用可变对象的访问器方法。详细内容参见书P108。
4.3.7 基于类的访问权限
一个方法可以访问所属类的所有对象的私有数据。例如:
class Employee
{
...
boolean equals(Employee other)
{
return name.equals(<strong>other.name</strong>);
}
}
4.3.9 final实例域
构建对象时必须初始化final域。即必须确保每个构造器执行后,这个域的值被设置,并且在之后的操作中,不能再对它进行修改。
final大都应用于基本数据类型域或是不可变类(类中的每个方法都不会改变其对象)的域。对于可变的类,final就可能会带来混淆,比如
private final Date hireDate;
,仅仅意味着hireDate变量的对象引用在对象构造后不能被改变,并不意味着hireDate对象是一个常量。
4.4 静态域与静态方法
4.2 静态常量
常见的常量:Math.PI, System.out
4.3 静态方法
可以认为静态方法是没有this参数的方法。由于静态方法不能操作对象,所以不能再静态方法中访问实例域。但静态方法可以访问自身类中的静态域。
使用静态方法的情况:
- 一个方法不需要访问对象状态,其所需参数都是通过显式参数提供(如Math.pow)
- 一个方法只需要访问类的静态域
静态方法还有一种常见的用途:factory方法。
4.5 方法参数
Java总是采用值调用,方法得到的是所有参数值的一个拷贝,方法不能修改传递给它的任何参数变量的内容。Java中方法参数的使用情况:
- 一个方法不能修改一个基本数据类型的参数
- 一个方法可以改变一个对象参数的状态
- 一个方法不能实现让对象参数引用一个新的对象
PS:C++有值调用和引用调用,引用参数标有&符号。
4.6 对象构造
4.6.1 重载
Java允许重载任何方法,但方法的签名(方法名以及参数类型,不包括返回类型)必须不同。即,不能有两个名字相同、参数类型相同、返回类型不同的方法。
4.6.2 默认域初始化
如果在构造器中没有显式地给域赋予初值,那么就会被自动地赋予默认值:数值为0、布尔值为false、对象引用为null。但一般都会明确地对域进行初始化,否则影响程序的可读性。
PS:这是域与局部变量的主要不同点。必须明确地初始化方法中的局部变量。但如果没有初始化类中的域,将会被初始化为默认值。
4.6.3 默认构造器
默认构造器指没有参数的构造器。
如果编写一个类时,没有编写构造器,则系统会提供一个默认构造器。这个默认构造器将所有的实例域设置为默认值。如果类中提供了至少一个构造器,但没有提供默认的构造器,则在构造对象时如果没有提供构造参数就会被视为不合法。即:仅当类没有提供任何构造器时,系统才会提供一个默认构造器,如果编写类时给出了一个构造器,系统就不会提供默认构造器,必须手写。如果想要给域赋默认值,则可以
public ClassName(){
}
4.6.6 调用另一个构造器
如果构造器的第一条语句是this(…),这个构造器将调用同一个类的另一个构造器。
4.6.7 初始化块
初始化数据域的方法:
- 构造器中设置值
- 声明中赋值
- 初始化块
一个类的声明中可以包含多个代码块,只要构造类的对象,这些块就会被执行。建议将初始化块放在域定义之后。
可以用静态的初始化块来对静态域进行初始化。
4.6.8 对象析构与finalize方法
一般不推荐使用finalize方法,但如果某些对象使用了内存之外的其他资源,例如,文件或使用了系统资源的另一个对象的句柄。在这种情况下,当资源不再需要时,将其回收和再利用就显得十分重要。
可以为任何一个类添加finalize方法,该方法将在垃圾回收器清楚对象之前调用。实际应用中不要依赖于使用finalize方法回收任何短缺的资源,因为很难知道这个方法什么时候用到。
4.7 包
为了保证包名的唯一,建议将公司的域名逆序作为包名,并且对于不同的项目使用不同的子包。
4.7.2 静态导入
Java SE 5开始,import语句不仅可以导入类,还增加导入静态方法和静态域的功能。如:
import static java.lang.System.*;
就可以使用System类中的静态方法和静态域,而不用加类名前缀:
out.println("helllo");
exit(0);
静态导入有两个实际应用:算术函数(静态导入Math的域和方法),笨重的常量
4.7.3 将类放入包中
注意如果类是某个包中,就必须将包的名字放在源文件开头,javac编译时也需要将注意加上包名(也要注意当前目录是什么)。
4.7.4 包作用域
如果没有指定public或是private,这个部分(类、方法或变量)可以被同一个包中的所有方法访问。