《JAVA核心技术卷》第四章 对象与类
4.1 面向对象程序设计概述
4.1.1 类
类是构造对象的模板或蓝图。由类构造对象的过程称为创建类的实例。
封装:封装将数据和行为组合在一个包中,并对对象的使用者隐藏了数据的实现方式。实现封装的关键在于绝对不能让类中的方法直接地访问其他类地实例域。程序仅通过对象的方法与对象数据进行交互。
4.1.2 对象
要想使用OOP,一定要清楚对象的三个主要特性:
- 对象的行为
- 对象的状态
- 对象标识
4.1.3 识别类
识别类的简单规则是在分析问题的过程中寻找名词,而方法对应着动词。
4.1.4 类之间的关系
- 依赖
如果一个类的方法操纵另一个类的对象,我们就说一个类依赖于另一个类。应该尽可能让类之间的耦合度最小,即尽可能地将相互依赖的类减至最少。 - 聚合
聚合关系意味着类A的对象包含类B的对象。 - 继承
继承是一种用于表示特殊与一般关系的。一般而言,如果类A扩展类B,类A不但包含从类B继承的方法,还会拥有一些额外的功能。
4.2 使用预定义类
4.2.1 对象与对象变量
要想使用对象,就必须先构造对象,并指定其初始状态。然后对对象应用方法。
在JAVA中,用构造器构造新实例。构造器的名字应该与类名相同。
注意对象与对象变量的区别!!!!一个对象变量并没有实际包含一个对象,而仅仅引用一个对象。在Java中,任何对象变量的值都是对存储在另外一个地方的一个对象的引用
Date deadline; //deadline doesn't refer to any object
定义了一个对象变量deadline,它可以引用Date类型的对象。但是一定要认识到:变量deadline不是一个对象,实际上也没有引用对象。
4.2.3 更改器方法与访问器方法
更改器方法:对象的状态会因为方法的调用而发生改变。
访问器方法:只访问对象而不修改对象。
4.3 用户自定义类
4.3.1 Employee类
在Java中,最简单的类定义形式为:
class ClassName{
field1;
field2;
. . .
constructor1;
constructor2;
. . .
method1;
method2;
. . .
}
在一个源文件中,只能有一个公有类,但可以有任意数目的非公有类。
4.3.4 从构造器开始
- 构造器与类同名
- 每个类可以有一个以上的构造器
- 构造器可以有0个、1个或多个参数
- 构造器没有返回值
- 构造器总是伴随着new操作一起调用
- 不能对一个已经存在的对象调用构造器来达到重新设置实例域的目的
4.3.5 隐式参数与显式参数
隐式参数:出现在方法名前的类对象;
显式参数:方法名后面括号中的数值。
4.3.6 封装的优点
如果需要返回一个可变数据域的拷贝,应该使用clone。如:
class Employee{
. . .
public Date getHireDay(){
return (Date)hireDay.clone();
}
}
如果访问器方法返回的是引用可变对象,在类的外部调用访问器方法后,就可以对该引用可变对象进行修改,即破坏了封装性。
4.3.8 私有方法
在Java中,为了实现一个私有的方法,只需将关键字public改为private即可。
对于私有方法,如果改用其他方法实现相应的操作,则不必保留原有的方法,因为它不会被外部的其他类操作调用。如果方法是公有的,就不能将其删去,因为其他的代码很可能依赖它。
4.3.9 final实例域
final修饰符大都应用于基本类型域,或不可变类的域(如果类中的每个方法都不会改变其对象,这种类就是不可变的类)。
可变类用final修饰,只要不改变引用,改变值还是可以的,可变类传递的时候是引用传递;
不可变类用final修饰,值和引用都不能改变,不可变类传递的时候是值传递
例:
private final StringBuilder evaluations;
evaluations = new StringBuilder();
public void giveGoldStar(){
evaluations.append(LocalDate.now() + ":Gold star!\n");
}
4.4 静态域与静态方法
4.4.1 静态域
如果将域定义为static,每个类中就只有一个这样的域,即这个类的所有实例将共享这一个域。但是对于实例域,每个对象都对其有自己的一份拷贝。例如:
class Employee{
private static int nextId = 1;
private int id;
}
如果每个雇员对象都有一个自己的id域,但这个类的所有实例将共享一个nextId域。换句话说,如果有1000个Employee类的对象,则有1000个实例域id。但是,只有一个静态域nextId。即使没有一个雇员对象,静态域nextId也存在。它属于类,不属于任何独立的对象。
4.4.3 静态方法
静态方法是一种不能向对象实施操作的方法,在执行的时候不使用任何对象,换句话说,没有隐式参数。所以,静态方法不能访问实例域,但可以访问自身类中的静态域。
补充:
Java中的域:所谓的域,翻译成英文就是field, 也就是我们常说的字段,或者说是属性。
实例域:就是实例(“object” )的"field"。java中对象中的数据称为实例域。
静态域:类中定义为static的域。
在下面两种情况下使用静态方法:
- 一个方法不需要访问对象状态,其所需参数都是通过显式参数提供。(例如:Math.pow())
- 一个方法只需要访问类的静态域(例如:Employee.getNextId())
4.4.4 工厂方法
(挖坑待填)
4.4.5 main方法
静态的main方法将执行并创建程序所需要的对象。
4.5 方法参数
按引用调用:方法接收的是调用者提供的变量地址,方法可以修改其所对应的变量值。
按值调用:方法接收的是调用者提供的值,即对参数值的一个拷贝,所以不能改变所对应的变量值。
java程序程序设计语言总是采用按值调用!!!!
然而,Java的方法参数有两种类型:
- 基本数据类型(数字,布尔值)。
- 对象引用
传递的参数的类型不同,对参数做修改所得到的结果也不同。
假定一个方法试图将一个参数值增至三倍:
public static void tripleValue(double x){ //doesn't work
x = 3 * x;
}
double percent = 10;
tripleValue(percent);
调用这个方法之后,percent值还是10.具体执行过程如下:
- x被初始化为percent值的一个拷贝。
- x被乘3后得到30.但是percent仍然是10.
- 这个方法结束之后,参数变量x不再使用。
可以看到,一个方法不可能修改一个基本数据类型的参数。而对象引用作为参数就不同了。
public static void tripleSalary(Employee x){ //works
x.raiseSalary(200);
}
harry = new Employee(. . .);
tripleSalary(harry);
执行过程如下:
- x被初始化为harry值的拷贝,这里是一个对象的引用。
- raiseSalary方法应用于这个对象引用。x和harry同时引用的那个Employee对象的薪金提高了200%。
- 方法结束后,参数变量x不再使用。当然,对象变量harry继续引用那个薪金增至3倍的雇员对象。
总结
- 一个方法不能修改一个基本数据类型的参数。
- 一个方法可以改变一个对象参数的状态。
- 一个方法不能让对象参数引用一个新的对象。
4.6 对象构造
4.6.1 重载
定义:如果多个方法(比如,StringBuilder构造器方法)有相同的名字、不同的参数,便产生了重载。
由定义可知,要完整地描述一个方法,需要指出方法名以及参数类型,这叫做方法的签名。返回类型不是方法签名的一部分,即不能有两个名字相同、参数类型也相同却返回不同类型值的方法。
重载解析——程序在编译阶段就会决定具体执行哪个方法:编译器通过用各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应的方法。如果编译器找不到匹配的参数,就会产生编译时错误。
4.6.2 默认域初始化
域与局部变量的主要不同点:局部变量必须被明确地初始化,而域不用。若域没有被初始化,将会被自动初始化为默认值(0、false或null)。
4.6.3 无参数的构造器
- 如果在编写一个类时没有编写构造器,那么系统就会提供一个无参数构造器。该构造器将所有的实例域设置为默认值。
- 如果类中提供了至少一个构造器,但是没有提供无参数的构造器,则在构造对象时如果没有提供参数就会被视为不合法。
所以,要想让类的用户能够采用 new ClassName() 的方式构造实例,就必须提供一个默认的构造器(即不带任何参数)。
4.6.4 显式域初始化
有用的编程习惯:
当一个类的所有构造器都希望把相同的值赋予某个特定的实例域时,可以在执行构造器之前,先执行赋值操作。
例:
class Employee{
private static int nextId;
private int id = assignId();
. . .
private static int assignId(){
int r = nextId;
nextId++;
return r;
}
. . .
}
4.6.6 调用另一个构造器
如果构造器的第一个语句形如this(...)
,这个构造器将调用同一个类的另一个构造器。采用这种方式使用this关键字非常有用,这样对公共的构造器代码只编写一次即可。
public Employee(double s){
//calls Employee(String double)
this("Employee #" + nextId, s);
nextId;
}
4.6.8 对象析构与finalize方法
finalize()的使用见Java中finalize()方法的使用
(面试可能会问到垃圾回收,可能会问到final、finally、finalize,还是看一看吧)
4.7 包
使用包的主要原因是确保类名的唯一性。就算两个类类名相同,只要将这些类放置在不同的包中,就不会产生冲突。
4.7.1 类的导入
一个类可以使用所属包中的所有类,以及其他包中的公有类。在包中定位类是编译器的工作。
4.7.2 静态导入
import static java.lang.System.*
out.println("Goodbye,world!");
4.7.3 将类放入包中
4.7.4 包作用域
标记为public的部分可以被任意的类使用;
标记为private的部分只能被定义它们的类使用;
如果没有指定public或private,这个部分(类、方法、变量)可以被同一个包中的所有方法访问。
要想名垂青史害得靠写bug( ͡⚈ ͜ʖ ͡⚈)
4.8 类路径
4.9 文档注释
4.10 类设计技巧
- 一定要保证数据私有
- 一定要对数据初始化
- 不要在类中使用过多的基本类型,即用其他的类代替多个相关的基本类型的使用。
- 不是所有的域都需要独立的域访问器和域更改器
- 将职责过多的类进行分解
- 类名和方法名要能够体现它们的职责
- 优先使用不可变的类