在实现了一个类之后,我们可以使用继承特性来扩展这个类的功能,并且复用类中的成员变量和成员方法。
例如,当我们已经定义了Student类之后,我们可以再定义一个Freshman类,用于表示大一新生。Freshman类继承自Student类。在声明类Freshman时,我们使用了关键字extends表示Freshman继承自类Student。因此,在此继承关系中,类Student被称为基类(Base Class)或者父类(Parent Class),而类Freshman被称为派生类(Derived Class)或者子类(SubClass)。
Java语言规定,一个子类只能继承自一个父类。我们将在下一章讨论Java语言为什么不支持多继承。
public class Student {
private int id;
public Student (int id) {
this.id = id;
}
public int getId() {
return this.id;
}
public void setId(int id) {
this.id = id;
}
}
public class Freshman extends Student {
private int age;
...
}
我们称子类"继承自"父类的原因是子类继承了父类中的成员变量和成员方法。换句话说,一个Freshman对象包含了两个成员变量id和age,同时也包含了Student类和Freshman类中定义的方法。
Student类中使用的访问控制关键字(public、protected、private)明确指出了哪些成员变量和成员方法可以在子类Freshman中访问。根据Java语言的访问控制规则,子类能够访问父类中声明为public或者protected的成员变量和成员方法。虽然子类不能直接访问父类声明的私有成员(private),但是这并不表示这些成员不存在于子类的对象中。这些私有成员可以通过调用父类提供的public或者protected方法访问。
在子类对象的构造过程中,Java语言会从父类的构造函数开始逐级地向下调用各个层级的构造函数。在子类的构造函数中,可以通过使用super()显式地指明调用哪个父类的构造函数来初始化父类中的成员变量。super()语句必须出现于子类构造函数中的第一行。如果子类没有显式调用父类构造函数的话,Java程序会自动调用父类的无参数构造函数。
public class Freshman extends Student {
private int age;
public Freshman(int id, int age) {
super(id);
this.age = age;
}
}
我们还可以继续"延展"继承等级。Freshman不仅可以继承Student,它还可以被其他类继承。例如,我们还可以创建一个新类OutstandingFreshman;它继承自Freshman,表示优秀的大一新生。所以,OutstandingFreshman不仅继承了Freshman和Student类声明的成员变量和方法,它还声明了新的成员变量awards,用于表示优秀大一新生所获得的奖项。
public class OutstandingFreshman extends Freshman {
private Award[] awards = null;
...
OutstandingFreshman(int id, int age) {
super(id, age);
}
}
从设计上看,如果我们不想OutstandingFreshman再被其他类继承的话,我们可以声明OutstandingFreshman是final的。因此,其他类就不能继承OutstandingFreshman了。
final public class OutstandingFreshman extends Freshman {
private Award[] awards = null;
...
}
关键字final还可以用于成员方法,表示该成员方法不能被子类的成员方法覆盖(Override)。
public class Student {
private int id;
public Student (int id) {
this.id = id;
}
final public int getId() {
return this.id;
}
final public void setId(int id) {
this.id = id;
}
}
在开发过程中,我们常常需要使用类型转换。将子类类型转换为基类类型或者接口类型是非常自然的,不需要开发人员额外的进行任何强制类型转换。例如,在下面的代码示例中,我们可以将Freshman对象赋值给Student引用类型的变量,因为Freshman对象也是一个Student类型的对象。
Student s = new Freshman(1010, 22);
但是,如果将一个基类类型的变量转换成子类类型的变量,开发人员需要使用强制类型转换。在转换过程中,Java虚拟机会检查类型转换是否能成功执行。当类型转换失败时,程序会收到ClassCastException异常。
类型转换有如下两种写法。第一种是直接将类型转换的目标类型放在一对括号中,Java编译器会将其翻译成相应的类型转换逻辑。第二种方法是调用目标类的cast()成员方法,完成类型转换。第一种方法也能用于基本数据类型的强制类型转换。
Student s = new OutstandingFreshman(1010, 22);
Freshman f = (Freshman)s;
OutstandingFreshman o = OutstandingFreshman.class.cast(f);
为了避免类型转换失败,收到ClassCastException异常,在运行类型转换之前,我们可以先判断一下对象的类型。这个判断的过程可由instanceof操作符完成,或者使用目标类的isinstance()成员方法完成。这两种方法的实现逻辑十分相似。唯一的区别是instanceof是操作符,由编译器处理。而isinstance()方法则是程序运行时调用的方法。
Student s = new OutstandingFreshman(1010, 22);
if (s instanceof Freshman) {
Freshman f = (Freshman)s;
...
}
if (OutstandingFreshman.class.isinstance(f)) {
OutstandingFreshman o = OutstandingFreshman.class.cast(f);
...
}
Java语言为所有的对象提供了一个"超级"父类java.lang.Object类。Object类是Java语言提供的一个预定义类(Predefined Class)。当开发人员未明确指出继承的父类时,这个类会默认的继承自Object类。所以,在我们的例子中,Student类继承自Object类,而且我们在声明Student类时,不需要明确地使用extends Object来声明这个继承关系。Java编译器会帮我们完成。
Student类是Object类的子类;Freshman类是Object类的"孙子"类,以此类推。因此,在Java程序中,所有的类都直接或者间接继承自Object类。Object类是所有类的"超级"父类。
public class Student {
private int id;
public Student (int id) {
this.id = id;
}
final public int getId() {
return this.id;
}
final public void setId(int id) {
this.id = id;
}
}
Object类定义了一些重要的成员方法。它们很重要是因为所有的类都会继承这些成员方法。其中,equals()方法用于比较两个对象是否相等。clone()方法用于生成一个对象的拷贝。当对象被Java虚拟机的垃圾回收器回收时,会调用finalize(),以进行额外的一些垃圾回收处理。getClass()方法用于在运行时获得对象的信息。wait()和notify()方法可用于多线程的Java程序设计之中。我们会在后续章节中逐一介绍它们。
hashCode()方法用于根据对象的内容生成一个哈希值(Hash Code Value)。这个方法被用于HashMap容器,快速判断对象是否相同。hashCode()方法的特点是给定两个对象,如果它们是同一个对象的话,它们的哈希值一定相同。如果它们是两个不同的对象的话,它们的哈希值可能相同,也可能不同。但是,它们有着不同哈希值的可能性非常大。
另一个重要的方法是toString()。该方法用于根据对象的内容生成一个字符串对象。这个方法常用于日志功能,打印对象的内容。在子类中,我们常常建议覆盖基类的toString()方法,以便于在生成的字符串中包含子类的内容。
继承特性帮助开发人员更好的复用代码功能。在继承了一个父类之后,子类也继承了父类中声明的变量和方法。在Java语言中,所有的类都直接或者间接继承自Object类,以便于实现对象的比较、查找、保护和打印等功能。
注册用户登陆后可留言