一、本章要点
- 使用关键字super调用父类的构造方法和方法
- 重写和重载
- instanceof
- 熟悉Object类中几个有用的方法(equals(object)、hashCode()、toString()、finalize()、clone()和getClass())
- 理解隐藏数据域和初始化块
- 类之间关系(关联、聚集、依赖、继承)
1.使用关键字super
super指向使用它的类的父类,有以下两种用途:
- 调用父类的构造函数
super() / super(参数);注意必须写在子类构造函数的第一行,而且这是调用父类构造函数的唯一方式。1
2
3
4
5
6public A(){
}
//等价于
public A(){
super();
}
如果一个类要扩展,最好提供一个无参的构造函数以免意外的错误。1
2
3
4
5
6
7
8
9
10public class Apple extends Fruit {
//程序编译错误
}
class Fruit{
public Fruit(String name) {
}
}
在Apple中没有定义构造函数,因此就隐式地声明了默认无参构造器。Apple是Fruit的子类,所以它会首先调用super();但是Fruit中没有无参构造器,所以程序会编译错误!
- 调用父类的方法
super.方法名(参数);
2.重写和重载
- 重写
子类从父类中继承方法时,有时需要修改父类方法的实现,这叫做重写(Override)。实例方法仅当可以被外部访问时才可以被重写,如果在子类中定义的方法在父类中是私有的,那么这两个方法就完全没有关系。
静态方法也可以被继承,但是不能覆盖,如果父类的静态方法被重新定义,父类的方法会被隐藏。 - 重载
被重载(Overload)的方法具有相同的方法名,和不同的参数列表。不能基于修饰符和返回值类型重载方法。
注:有时一个方法调用会有多个匹配,编译器无法确认哪个更为合适。这时歧义调用会引起编译错误。1
2
3max(int a,double b);
max(double a,int b);
max(1,2); //引起歧义调用,编译错误
3.instanceof
将一个子类的实例转换为一个父类的变量总是可行的,称为向上转换(upcasting),因为子类的实例永远是父类的实例。把父类实例转换为它的子类变量,称为向下转换(downcasting),必须使用转换记号(子类名)进行显式转换,向编译器指明你的目的。为使转换成功,必须确保转换的对象是子类的一个实例。如果父类对象不是子类的一个实例,会发生实时异常ClassCastException。
因此,应该养成好习惯,在进行转换前确保该对象是另一个对象的实例,使用instanceof关键字判断。
4.Object类重要方法
- toString()
调用对象的toString()方法返回一个代表该对象的字符串。默认情况下返回该对象所属的类名+@+十六进制散列码构成的字符串。我们可以重写toString()方法使返回值更加具有意义。
System.out.println(object)等价于System.out.println(object.toString()) - equals()
检验两个对象是否相等,Object中的实现如下:1
2
3public boolean equals(Object obj){
return this == obj;
}
==
运算符用来判断两个基本数据类型的值是否相等,或判断两个引用类型是否具有相同的引用值。如果有特殊需求可以进行重写,比如String类的equals()方法就是依次比较字符串中的每个字符是否相同。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) { //比较对象是否是String
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i]) //依次比较每个字符是否相同
return false;
i++;
}
return true;
}
}
return false;
}
hashCode()
返回对象的散列码,用于在散列集合中存储并快速查找对象。根据约定->两个对象相等那么两个对象的散列码相同,所以覆盖equals方法的同时应该覆盖hashCode方法finalize()
当对象变成垃圾时,finalize方法会被垃圾回收程序调用。默认情况下该方法不会做任何事,如果必要可以重写finalize方法,释放系统资源或进行其他清洁工作。finalize方法由Java虚拟机调用,在程序中不要出现调用该方法的代码。clone()
有时需要复制一个对象,我们便简单错误滴写下了下面的语句1
newObject = originObject;
这条语句并不能创建对象的复制品,只是将originObject的地址复制给newObject(堆中没有新增复制)。要创建一个有单独内存空间的新对象应该使用clone()方法。1
2
3
4
5
6
7
8public static void main(String[] args) {
java.util.Date date = new java.util.Date();
java.util.Date date1 = (Date) date.clone();
System.out.println("date == date1:" + (date == date1)); //false
System.out.println("date.equals(date1):" + (date.equals(date1))); //true
}
equals返回true,因为Date类对equals进行了重写,比较getTime()的值。==
返回false,因为这是两个对象,他们的引用不同。1
2
3public boolean equals(Object obj) {
return obj instanceof Date && getTime() == ((Date) obj).getTime();
}
- getClass()
返回类被装入Java虚拟机时的类信息,见反射相关
5.隐藏域和初始化块
如果在子类中声明的数据域或静态方法与父类中的名字相同,父类中的将被隐藏,但是它依旧存在。这两个数据域或静态方法是独立的。在子类中可以使用super关键字调用父类隐藏的数据域或静态方法,隐藏的域或方法也能通过父类类型的引用变量来访问。
初始化块可以与构造函数一起用于对象的初始化。它是使用{}括住的代码块,位于类的声明中。它的作用相当于把它放在子类中每个构造函数最开始的位置。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class Apple {
private int num = 100;
private String color;
public Apple(){
System.out.println(num);
}
public Apple(String color){
this.color = color;
System.out.println("有参:"+num);
}
{
num++;
}
public static void main(String[] args){
Apple apple = new Apple();
Apple apple1 = new Apple("red");
}
}
以上代码相当于1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class Apple {
private int num = 100;
private String color;
public Apple(){
num++;
System.out.println(num);
}
public Apple(String color){
num++;
this.color = color;
System.out.println("有参:"+num);
}
public static void main(String[] args){
Apple apple = new Apple();
Apple apple1 = new Apple("red");
}
}
类初始化的过程如下:
- 第一次使用类时,需要装入类,装入包括两个阶段
a. 装入父类,在装入任何类之前,如果父类没有装入,必须装入父类。这是一个递归的过程,直到继承链上的父类已经装入为止。
b. 类装入内存之后,它的静态数据域和静态初始化模块按它们在类中出现的顺序执行(先执行Object(最上层类)的初始化块,如果有的话)。
2.调用类的构造方法分三个阶段
a. 调用父类的构造方法,这是一个递归过程直到父类为java.lang.Object为止。
b. 初始化实例数据域并按它们在类中出现的顺序执行初始化模块。
c. 执行构造方法的方法体。
6.类之间关系
类的关系可以分成:
- 关联(association)
- 聚集(aggregation)
- 组合(composition)
- 依赖(dependency)
- 继承(inheritance)
关联
描述两个类之间行为的一般二元关系。例如,一个学生选学一门课程是学生类Student和课程类Course之间的一个关联,而一位教师教一门课是师资类Faculty和课程类Course之间的一个关联。这些关联可以用UML图形符号表示:聚集与组合
聚集是一种特殊的关联形式,表示两个对象之间的所属关系。聚集具有(has-a)关系。所有者对象称为聚集对象,从属对象称为被聚集对象。如果一个对象被一个聚集对象所专有,它与聚集对象之间的关系就称为”组合”。
一个人只有一个名字,所以Person与Name之间是组合关系;但是Address可以被多个Person共享,所以Person与Address是聚集关系;依赖
两个类之间一个使用另一个的这种关系称为依赖。例如:1
2
3
4public class ArrayList(){
public void add(Object o)
...
}
ArrayList类中使用Object类,其UML关系图为:

注:关联和依赖都描述了一个类依赖于另一个类,关联比依赖表达的关系更强一些。关联关系用数据域(会注入)和方法实现,两个类之间有很强的联系;而依赖关系用方法实现,属于松耦合。
- 继承
继承模拟的是一种(is a)的关系,例如:一个教师是一个人的这种关系,使用代码模拟1
2
3public class Faculty extends Person{
}

注:并非所有的关系都可以使用继承模拟,比如,正方形是一个矩形,但是不应该通过扩展矩形的功能来满足正方形的特性,因为从矩形到正方形没有什么可以扩展!如果用类A丶扩展类A,那么A丶应该包含比A更多的详细信息。
本文链接: http://www.xiaopeng.pro/articles/c59ea41a.html
版权声明: 本原创文章采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!