Java™ 教程(继承)

继承
在前面的课程中,你已经多次看到了继承,在Java语言中,类可以从其他类派生,从而从这些类继承字段和方法。
定义:从另一个类派生的类称为子类(也是派生类,扩展类或子类),派生子类的类称为超类(也是基类或父类)。
除了Object没有超类,每个类都有一个且只有一个直接超类(单继承),在没有任何其他显式超类的情况下,每个类都隐式地是Object的子类。
类可以从派生自类的类派生的类派生,依此类推,最终派生自最顶层的类,Object,这样的类被称为继承链中所有向后延伸到Object的类的子类。
继承的概念很简单但很强大:当你想要创建一个新类并且已经有一个包含你想要的一些代码的类时,你可以从现有类派生你的新类,在这样做时,你可以重用现有类的字段和方法,而无需自己编写(和调试)它们。
子类从其超类继承所有成员(字段、方法和嵌套类),构造函数不是成员,因此它们不是由子类继承的,但是可以从子类调用超类的构造函数。
Java平台类层次结构
在java.lang包中定义的Object类定义并实现所有类共有的行为 — 包括你写的那些,在Java平台中,许多类直接从Object派生,其他类派生自其中一些类,依此类推,形成类的层次结构。

在层次结构的顶部,Object是所有类中最通用的,层次结构底部附近的类提供更专业的行为。
继承的一个例子
下面是类和对象课程中提供的Bicycle类的可能实现的示例代码:
public class Bicycle {

// the Bicycle class has three fields
public int cadence;
public int gear;
public int speed;

// the Bicycle class has one constructor
public Bicycle(int startCadence, int startSpeed, int startGear) {
gear = startGear;
cadence = startCadence;
speed = startSpeed;
}

// the Bicycle class has four methods
public void setCadence(int newValue) {
cadence = newValue;
}

public void setGear(int newValue) {
gear = newValue;
}

public void applyBrake(int decrement) {
speed -= decrement;
}

public void speedUp(int increment) {
speed += increment;
}

}
作为Bicycle的子类的MountainBike类的类声明可能如下所示:
public class MountainBike extends Bicycle {

// the MountainBike subclass adds one field
public int seatHeight;

// the MountainBike subclass has one constructor
public MountainBike(int startHeight,
int startCadence,
int startSpeed,
int startGear) {
super(startCadence, startSpeed, startGear);
seatHeight = startHeight;
}

// the MountainBike subclass adds one method
public void setHeight(int newValue) {
seatHeight = newValue;
}
}
MountainBike继承了Bicycle的所有字段和方法,并添加了字段seatHeight和设置它的方法,除了构造函数之外,就好像你已经从头开始编写了一个新的MountainBike类,有四个字段和五个方法。但是,你不必完成所有工作,如果Bicycle类中的方法很复杂并且需要花费大量时间来调试,那么这将特别有价值。
你可以在子类中执行的操作
无论子类所在的包是什么,子类都会继承其父级的所有public成员和protected成员,如果子类与其父类在同一个包中,它还会继承父类的包私有成员,你可以按原样使用继承的成员,替换它们,隐藏它们或用新成员补充它们:

继承的字段可以直接使用,就像任何其他字段一样。
你可以在子类中声明一个与超类中的字段同名的字段,从而隐藏它(不推荐)。
你可以在子类中声明不在超类中的新字段。
继承的方法可以直接使用。
你可以在子类中编写一个新实例方法,该方法与超类中的签名具有相同的签名,从而覆盖它。
你可以在子类中编写一个新的静态方法,该方法与超类中的签名具有相同的签名,从而隐藏它。
你可以在子类中声明不在超类中的新方法。
你可以编写一个子类构造函数,它可以隐式地或使用关键字super来调用超类的构造函数。

本课程的以下部分将扩展这些主题。
超类中的私有成员
子类不继承其父类的private成员,但是,如果超类具有访问其私有字段的公共或受保护方法,则子类也可以使用这些方法。
嵌套类可以访问其封闭类的所有私有成员 — 包括字段和方法,因此,子类继承的public或protected嵌套类可以间接访问超类的所有私有成员。
转换对象
我们已经看到一个对象是实例化它的类的数据类型,例如,如果我们写
public MountainBike myBike = new MountainBike();
那么myBike是MountainBike类型。
MountainBike是Bicycle和Object的后代,因此,MountainBike是一个Bicycle并且也是一个Object,它可以在需要Bicycle或Object对象的任何地方使用。
反过来不一定是对的:Bicycle可能是MountainBike,但不一定。类似地,Object可以是Bicycle或山MountainBike,但不一定如此。
转换显示在继承和实现允许的对象中使用一种类型的对象代替另一种类型的对象,例如,如果我们写
Object obj = new MountainBike();
那么obj既是Object又是MountainBike(直到obj被赋予另一个不是MountainBike的对象的时候),这称为隐式转换。
另一方面,如果我们写
MountainBike myBike = obj;
我们会得到编译时错误,因为编译器不知道obj是MountainBike,但是,我们可以告诉编译器我们承诺通过显式转换将MountainBike分配给obj:
MountainBike myBike = (MountainBike)obj;
此强制转换插入运行时检查,为obj分配MountainBike,以便编译器可以安全地假设obj是MountainBike,如果obj在运行时不是MountainBike,则会抛出异常。
注意:你可以使用instanceof运算符对特定对象的类型进行逻辑测试,这可以避免由于转换不当而导致的运行时错误,例如:
if (obj instanceof MountainBike) {
MountainBike myBike = (MountainBike)obj;
}
这里,instanceof运算符验证obj是否引用了MountainBike,以便我们可以知道不会抛出运行时异常来进行转换。
状态、实现和类型的多重继承
类和接口之间的一个显着区别是类可以有字段而接口不能,此外,你可以实例化一个类来创建一个对象,这是你无法使用接口进行的,如什么是对象?部分所述,对象将其状态存储在字段中,这些字段在类中定义。Java编程语言不允许扩展多个类的一个原因是避免了多重继承状态的问题,即从多个类继承字段的能力。例如,假设你能够定义一个扩展多个类的新类,通过实例化该类来创建对象时,该对象将继承所有类的超类中的字段,如果来自不同超类的方法或构造函数实例化相同的字段会怎样?哪个方法或构造函数优先?由于接口不包含字段,因此你不必担心多重继承状态所导致的问题。
实现的多重继承是从多个类继承方法定义的能力,这种类型的多重继承会出现问题,例如名称冲突和歧义,当支持这种类型的多继承的编程语言的编译器遇到包含具有相同名称的方法的超类时,它们有时无法确定要访问或调用的成员或方法。此外,程序员可以通过向超类添加新方法而无意中引入名称冲突,默认方法引入了一种实现的多重继承形式,一个类可以实现多个接口,该接口可以包含具有相同名称的默认方法,Java编译器提供了一些规则来确定特定类使用哪种默认方法。
Java编程语言支持多种类型的继承,这是类实现多个接口的能力,对象可以有多种类型:它自己的类的类型以及该类实现的所有接口的类型。这意味着如果将变量声明为接口的类型,则其值可以引用从实现接口的任何类实例化的任何对象,这在将接口用作类型一节中讨论。
与多实现继承一样,一个类可以继承它扩展的接口中定义的方法的不同实现(作为默认或静态),在这种情况下,编译器或用户必须决定使用哪一个。
覆盖和隐藏方法
实例方法
子类中的实例方法的签名(名称,加上其参数的数量和类型)和返回类型与超类中的实例方法相同,将覆盖超类的方法。
子类覆盖方法的能力允许类从行为“足够接近”的超类继承,然后根据需要修改行为,重写方法与它重写的方法具有相同的名称、数量和参数类型,以及返回类型。重写方法还可以返回由被重写方法返回的类型的子类型,此子类型称为协变返回类型。
覆盖方法时,你可能希望使用@Override注解来指示编译器你要覆盖超类中的方法,如果由于某种原因,编译器检测到该方法在其中一个超类中不存在,那么它将生成错误,有关@Override的更多信息,请参阅注解。
静态方法
如果子类定义的静态方法与超类中的静态方法具有相同的签名,则子类中的方法会隐藏超类中的方法。
隐藏静态方法和覆盖实例方法之间的区别具有重要意义:

被调用的重写的实例方法的版本是子类中的版本。
被调用的隐藏静态方法的版本取决于它是从超类还是从子类调用的。

考虑一个包含两个类的示例,第一个是Animal,它包含一个实例方法和一个静态方法:
public class Animal {
public static void testClassMethod() {
System.out.println(“The static method in Animal”);
}
public void testInstanceMethod() {
System.out.println(“The instance method in Animal”);
}
}
第二个类是Animal的一个子类,叫做Cat:
public class Cat extends Animal {
public static void testClassMethod() {
System.out.println(“The static method in Cat”);
}
public void testInstanceMethod() {
System.out.println(“The instance method in Cat”);
}

public static void main(String[] args) {
Cat myCat = new Cat();
Animal myAnimal = myCat;
Animal.testClassMethod();
myAnimal.testInstanceMethod();
}
}
Cat类重写Animal中的实例方法,并隐藏Animal中的静态方法,此类中的main方法创建Cat的实例,并在类上调用testClassMethod()并在实例上调用testInstanceMethod()。
该程序的输出如下:
The static method in Animal
The instance method in Cat
正如所承诺的那样,被调用的隐藏静态方法的版本是超类中的版本,被调用的重写实例方法的版本是子类中的版本。
接口方法
接口中的默认方法和抽象方法与实例方法一样是继承的,但是,当类或接口的超类型提供具有相同签名的多个默认方法时,Java编译器遵循继承规则来解决名称冲突,这些规则由以下两个原则驱动:
实例方法优先于接口默认方法。
考虑以下类和接口:
public class Horse {
public String identifyMyself() {
return “I am a horse.”;
}
}
public interface Flyer {
default public String identifyMyself() {
return “I am able to fly.”;
}
}
public interface Mythical {
default public String identifyMyself() {
return “I am a mythical creature.”;
}
}
public class Pegasus extends Horse implements Flyer, Mythical {
public static void main(String… args) {
Pegasus myApp = new Pegasus();
System.out.println(myApp.identifyMyself());
}
}
方法Pegasus.identifyMyself返回字符串I am a horse。
已经被其他候选者覆盖的方法将被忽略,当超类型共享一个共同的祖先时,就会出现这种情况。
考虑以下接口和类:
public interface Animal {
default public String identifyMyself() {
return “I am an animal.”;
}
}
public interface EggLayer extends Animal {
default public String identifyMyself() {
return “I am able to lay eggs.”;
}
}
public interface FireBreather extends Animal { }
public class Dragon implements EggLayer, FireBreather {
public static void main (String… args) {
Dragon myApp = new Dragon();
System.out.println(myApp.identifyMyself());
}
}
方法Dragon.identifyMyself返回字符串I am able to lay eggs。
如果两个或多个独立定义的默认方法冲突,或者默认方法与抽象方法冲突,则Java编译器会产生编译器错误,你必须显式覆盖超类型方法。
考虑一下现在可以飞行的计算机控制汽车的例子,你有两个接口(OperateCar和FlyCar)为同一方法提供默认实现(startEngine):
public interface OperateCar {
// …
default public int startEngine(EncryptedKey key) {
// Implementation
}
}
public interface FlyCar {
// …
default public int startEngine(EncryptedKey key) {
// Implementation
}
}
实现OperateCar和FlyCar的类必须覆盖方法startEngine,你可以使用super关键字调用任何默认实现。
public class FlyingCar implements OperateCar, FlyCar {
// …
public int startEngine(EncryptedKey key) {
FlyCar.super.startEngine(key);
OperateCar.super.startEngine(key);
}
}
super之前的名称(在此示例中为FlyCar或OperateCar)必须引用定义或继承默认调用方法的直接超接口,这种形式的方法调用不限于区分包含具有相同签名的默认方法的多个已实现接口,你可以使用super关键字在类和接口中调用默认方法。
类中的继承实例方法可以覆盖抽象接口方法,考虑以下接口和类:
public interface Mammal {
String identifyMyself();
}
public class Horse {
public String identifyMyself() {
return “I am a horse.”;
}
}
public class Mustang extends Horse implements Mammal {
public static void main(String… args) {
Mustang myApp = new Mustang();
System.out.println(myApp.identifyMyself());
}
}
方法Mustang.identifyMyself返回字符串I am a horse,Mustang类继承了Horse类中的方法identifyMyself,它覆盖了Mammal接口中同名的抽象方法。
注意:接口中的静态方法永远不会被继承。
修饰符
重写方法的修饰符可以允许比被重写方法更多但不是更少的访问,例如,超类中的protected实例方法可以在子类中是public,但不能是private。
如果尝试将超类中的实例方法更改为子类中的静态方法,则会出现编译时错误,反之亦然。
总结
下表总结了在定义具有与超类中的方法相同的签名的方法时发生的情况。

超类实例方法
超类静态方法

子类实例方法
覆盖
生成编译时错误

子类静态方法
生成编译时错误
隐藏

注意:在子类中,你可以重载从超类继承的方法,这样的重载方法既不隐藏也不覆盖超类实例方法 — 它们是新方法,对于子类是唯一的。
多态性
多态性的字典定义是指生物学中的原理,其中生物体或物种可以具有许多不同的形式或阶段,这个原则也可以应用于面向对象的编程和像Java语言之类的语言,类的子类可以定义它们自己的唯一行为,但仍然共享父类的一些相同功能。
可以通过对Bicycle类的微小修改来演示多态性,例如,可以将printDescription方法添加到显示当前存储在实例中的所有数据的类中。
public void printDescription(){
System.out.println(“\nBike is ” + “in gear ” + this.gear
+ ” with a cadence of ” + this.cadence +
” and travelling at a speed of ” + this.speed + “. “);
}
要演示Java语言中的多态特性,请使用MountainBike和RoadBike类扩展Bicycle类,对于MountainBike,添加一个suspension字段,这是一个字符串值,表示自行车是否有前减震器,或者,自行车有一个前后减震器。
这是更新的类:
public class MountainBike extends Bicycle {
private String suspension;

public MountainBike(
int startCadence,
int startSpeed,
int startGear,
String suspensionType){
super(startCadence,
startSpeed,
startGear);
this.setSuspension(suspensionType);
}

public String getSuspension(){
return this.suspension;
}

public void setSuspension(String suspensionType) {
this.suspension = suspensionType;
}

public void printDescription() {
super.printDescription();
System.out.println(“The ” + “MountainBike has a” +
getSuspension() + ” suspension.”);
}
}
请注意重写的printDescription方法,除了之前提供的信息之外,还有关于suspension的其他数据包含在输出中。
接下来,创建RoadBike类,因为公路车或赛车的轮胎很薄,所以添加一个属性来跟踪轮胎的宽度,这是RoadBike类:
public class RoadBike extends Bicycle{
// In millimeters (mm)
private int tireWidth;

public RoadBike(int startCadence,
int startSpeed,
int startGear,
int newTireWidth){
super(startCadence,
startSpeed,
startGear);
this.setTireWidth(newTireWidth);
}

public int getTireWidth(){
return this.tireWidth;
}

public void setTireWidth(int newTireWidth){
this.tireWidth = newTireWidth;
}

public void printDescription(){
super.printDescription();
System.out.println(“The RoadBike” + ” has ” + getTireWidth() +
” MM tires.”);
}
}
请注意,再次重写了printDescription方法,这次,显示有关轮胎宽度的信息。
总而言之,有三个类:Bicycle、MountainBike和RoadBike,这两个子类重写printDescription方法并打印唯一信息。
这是一个创建三个Bicycle变量的测试程序,每个变量都分配给三个自行车类别中的一个,然后打印每个变量。
public class TestBikes {
public static void main(String[] args){
Bicycle bike01, bike02, bike03;

bike01 = new Bicycle(20, 10, 1);
bike02 = new MountainBike(20, 10, 5, “Dual”);
bike03 = new RoadBike(40, 20, 8, 23);

bike01.printDescription();
bike02.printDescription();
bike03.printDescription();
}
}
以下是测试程序的输出:
Bike is in gear 1 with a cadence of 20 and travelling at a speed of 10.

Bike is in gear 5 with a cadence of 20 and travelling at a speed of 10.
The MountainBike has a Dual suspension.

Bike is in gear 8 with a cadence of 40 and travelling at a speed of 20.
The RoadBike has 23 MM tires.
Java虚拟机(JVM)为每个变量中引用的对象调用适当的方法,它不会调用由变量类型定义的方法,此行为称为虚方法调用,并演示Java语言中重要多态特征的一个方面。
隐藏字段
在类中,与超类中的字段具有相同名称的字段会隐藏超类的字段,即使它们的类型不同,在子类中,超类中的字段不能通过其简单名称引用,相反,必须通过super访问该字段,一般来说,我们不建议隐藏字段,因为它使代码难以阅读。
使用super关键字
访问超类成员
如果你的方法覆盖了它的一个超类的方法,你可以通过使用关键字super来调用被重写的方法,你也可以使用super来引用隐藏字段(尽管不鼓励隐藏字段),虑这个类,Superclass:
public class Superclass {

public void printMethod() {
System.out.println(“Printed in Superclass.”);
}
}
这是一个名为Subclass的子类,它重写了printMethod():
public class Subclass extends Superclass {

// overrides printMethod in Superclass
public void printMethod() {
super.printMethod();
System.out.println(“Printed in Subclass”);
}
public static void main(String[] args) {
Subclass s = new Subclass();
s.printMethod();
}
}
在Subclass中,简单名称printMethod()引用在Subclass中声明的名称,它覆盖了Superclass中的名称,因此,要引用从Superclass继承的printMethod(),Subclass必须使用限定名,使用super,编译和执行Subclass将打印以下内容:
Printed in Superclass.
Printed in Subclass
子类构造函数
以下示例说明如何使用super关键字来调用超类的构造函数,回想一下Bicycle的例子,MountainBike是Bicycle的子类,这是MountainBike(子类)构造函数,它调用超类构造函数,然后添加自己的初始化代码:
public MountainBike(int startHeight,
int startCadence,
int startSpeed,
int startGear) {
super(startCadence, startSpeed, startGear);
seatHeight = startHeight;
}
调用超类构造函数必须是子类构造函数中的第一行。
调用超类构造函数的语法是:
super();

super(parameter list);
使用super(),可以调用超类无参数构造函数,使用super(parameter list),将调用具有匹配参数列表的超类构造函数。
注意:如果构造函数没有显式调用超类构造函数,Java编译器会自动插入对超类的无参数构造函数的调用,如果超类没有无参数构造函数,则会出现编译时错误,Object确实有这样的构造函数,因此如果Object是唯一的超类,则没有问题。
如果子类构造函数显式或隐式调用其超类的构造函数,你可能会认为将调用一整个构造函数链,一直返回Object的构造函数,事实上,情况就是这样,它被称为构造函数链,当需要很长的类层次时,你需要意识到这一点。
Object作为超类
java.lang包中的Object类位于类层次结构树的顶部,每个类都是Object类的直接或间接的后代,你使用或编写的每个类都继承Object的实例方法,你不需要使用任何这些方法,但是,如果你选择这样做,你可能需要使用特定于你的类的代码重写它们,本节中讨论的从Object继承的方法是:

protected Object clone() throws CloneNotSupportedException 创建并返回此对象的副本。

public boolean equals(Object obj) 指示某个其他对象是否“等于”此对象。

protected void finalize() throws Throwable 垃圾回收器在对象上执行垃圾回收时调用。 集合确定不再有对该对象的引用。

public final Class getClass() 返回对象的运行时类。

public int hashCode() 返回对象的哈希码值。

public String toString() 返回对象的字符串表示形式。

Object的notify、notifyAll和wait方法都在同步程序中独立运行的线程的活动中发挥作用,这将在后面的课程中讨论,这里不讨论,有五个方法:

public final void notify()
public final void notifyAll()
public final void wait()
public final void wait(long timeout)
public final void wait(long timeout, int nanos)

注意:许多方法都有一些微妙的方面,尤其是clone方法。
clone()方法
如果某个类或其中一个超类实现了Cloneable接口,则可以使用clone()方法从现有对象创建副本,要创建克隆,请编写:
aCloneableObject.clone();
Object的此方法实现检查调用clone()的对象是否实现Cloneable接口,如果对象没有,则该方法抛出CloneNotSupportedException异常,稍后的课程将介绍异常处理,目前,你需要知道clone()必须声明为:
protected Object clone() throws CloneNotSupportedException

public Object clone() throws CloneNotSupportedException
如果要编写clone()方法来覆盖Object中的方法。
如果调用clone()的对象确实实现了Cloneable接口,对象的clone()方法实现创建与原始对象相同的类的对象,并初始化新对象的成员变量,使其具有与原始对象的相应成员变量相同的值。
使类可克隆的最简单方法是将implements Cloneable添加到类的声明中,然后你的对象可以调用clone()方法。
对于某些类,Object的clone()方法的默认行为可以正常工作,但是,如果一个对象包含对外部对象的引用,比如说ObjExternal,则可能需要重写clone()以获得正确的行为,否则,一个对象对ObjExternal所做的更改也将在其克隆中可见,这意味着原始对象及其克隆不是独立的 — 要将它们分离,你必须重写clone()以便它克隆对象和ObjExternal,然后原始对象引用ObjExternal,并且克隆引用ObjExternal的克隆,以便对象及其克隆真正独立。
equals()方法
equals()方法将两个对象进行相等性比较,如果它们相等则返回true,Object类中提供的equals()方法使用标识运算符(==)来确定两个对象是否相等。对于原始数据类型,这会给出正确的结果,但是,对于对象,它没有,Object提供的equals()方法测试对象引用是否相等 — 即,如果比较的对象是完全相同的对象。
要测试两个对象在等价意义上是否相等(包含相同的信息),必须重写equals()方法,以下是重写equals()的Book类的示例:
public class Book {

public boolean equals(Object obj) {
if (obj instanceof Book)
return ISBN.equals((Book)obj.getISBN());
else
return false;
}
}
考虑这段代码来测试Book类的两个实例是否相等:
// Swing Tutorial, 2nd edition
Book firstBook = new Book(“0201914670”);
Book secondBook = new Book(“0201914670”);
if (firstBook.equals(secondBook)) {
System.out.println(“objects are equal”);
} else {
System.out.println(“objects are not equal”);
}
即使firstBook和secondBook引用了两个不同的对象,此程序也显示对objects are equal,它们被认为是相同的,因为比较的对象包含相同的ISBN号。
如果标识运算符不适合你的类,则应始终重写equals()方法。
注意:如果重写equals(),则还必须重写hashCode()。
finalize()方法
Object类提供了一个回调方法finalize(),当它变为垃圾时,可以在对象上调用它,Object的finalize()实现什么也没做 — 你可以重写finalize()来执行清理,例如释放资源。
finalize()方法可以由系统自动调用,但是什么时候调用,或者即使调用,也是不确定的,因此,你不应该依赖此方法为你进行清理。例如,如果在执行I/O后没有在代码中关闭文件描述符,并且你希望finalize()为你关闭它们,则可能会耗尽文件描述符。
getClass()方法
你不能重写getClass。
getClass()方法返回一个Class对象,该对象具有可用于获取有关类的信息的方法,例如其名称(getSimpleName()),其超类(getSuperclass())及其实现的接口(getInterfaces()),例如,以下方法获取并显示对象的类名:
void printClassName(Object obj) {
System.out.println(“The object’s” + ” class is ” +
obj.getClass().getSimpleName());
}
java.lang包中的Class类有很多方法(超过50个),例如,你可以测试以查看类是注解(isAnnotation()),接口(isInterface())还是枚举(isEnum()),你可以查看对象的字段是什么(getFields())或其方法是什么(getMethods()),等等。
hashCode()方法
hashCode()返回的值是对象的哈希码,它是十六进制的对象的内存地址。
根据定义,如果两个对象相等,则它们的哈希码也必须相等,如果重写equals()方法,则更改了两个对象的等效方式,并且Object的hashCode()实现不再有效,因此,如果重写equals()方法,则还必须重写hashCode()方法。
toString()方法
你应该始终考虑重写你的类中的toString()方法。
Object的toString()方法返回对象的String表示,这对调试非常有用,对象的String表示完全取决于对象,这就是你需要在类中重写toString()的原因。
你可以使用toString()和System.out.println()来显示对象的文本表示形式,例如Book的实例:
System.out.println(firstBook.toString());
对于正确重写的toString()方法,它会打印一些有用的东西,如下所示:
ISBN: 0201914670; The Swing Tutorial; A Guide to Constructing GUIs, 2nd Edition
编写Final类和方法
你可以声明一些或所有类的方法为final,你在方法声明中使用final关键字来指示子类不能重写该方法,Object类这样做 — 它的一些方法是final。
如果方法具有不应被更改的实现,并且对于对象的一致状态至关重要,则可能希望将方法设为final,例如,你可能希望在此ChessAlgorithm类中生成getFirstPlayer方法:
class ChessAlgorithm {
enum ChessPlayer { WHITE, BLACK }

final ChessPlayer getFirstPlayer() {
return ChessPlayer.WHITE;
}

}
从构造函数调用的方法通常应该声明为final,如果构造函数调用非final方法,子类可能会重新定义该方法,并产生意外或不希望看到的结果。
请注意,你还可以声明整个类final,声明为final的类不能被子类化,这特别有用,例如,在创建String类这样的不可变类时。
抽象方法和类
抽象类是一个声明为abstract的类 — 它可能包括也可能不包括抽象方法,抽象类无法实例化,但可以进行子类化。
抽象方法是在没有实现的情况下声明的方法(没有大括号,后跟分号),如下所示:
abstract void moveTo(double deltaX, double deltaY);
如果一个类包含抽象方法,那么该类本身必须被声明为abstract,如:
public abstract class GraphicObject {
// declare fields
// declare nonabstract methods
abstract void draw();
}
当抽象类被子类化时,子类通常为其父类中的所有抽象方法提供实现,但是,如果没有,那么子类也必须声明为abstract。
注意:未声明为default或static的接口(参见接口部分)中的方法是隐式抽象的,因此abstract修饰符不用于接口方法(可以使用,但不需要)。
抽象类与接口相比
抽象类与接口类似,你不能实例化它们,它们可能包含有或没有实现声明的方法的组合,但是,使用抽象类,你可以声明非静态和final的字段,并定义public、protected和private的具体方法。使用接口,所有字段都自动为public、static和final,并且你声明或定义的所有方法(作为默认方法)都是public,此外,你只能扩展一个类,无论它是否是抽象的,而你可以实现任意数量的接口。
你应该使用哪个,抽象类或接口?

如果任何这些语句适用于你的情况,请考虑使用抽象类:

你希望在几个密切相关的类之间共享代码。
你希望扩展抽象类的类具有许多公共方法或字段,或者需要非公共的访问修饰符(如protected和private)。
你想声明非静态或非最终字段,这使你能够定义能够访问和修改它们所属的对象的状态的方法。

如果任何这些语句适用于你的情况,请考虑使用接口:

你希望不相关的类将实现你的接口,例如,Comparable和Cloneable接口由许多不相关的类实现。
你希望指定特定数据类型的行为,但不关心谁实现其行为。
你希望利用类型的多重继承。

JDK中的抽象类的一个示例是AbstractMap,它是集合框架的一部分,它的子类(包括HashMap、TreeMap和ConcurrentHashMap)共享AbstractMap定义的许多方法(包括get、put、isEmpty、containsKey和containsValue)。
JDK中实现多个接口的类的一个示例是HashMap,它实现了Serializable、Cloneable和Map<K, V>接口。通过阅读这个接口列表,你可以推断出HashMap的实例(无论是实现该类的开发人员或公司)可以克隆,可序列化(这意味着它可以转换为字节流)。请参阅Serializable Objects部分,并具有map的功能,此外,Map<K, V>接口已经增强了许多默认方法,例如merge和forEach,实现此接口的旧类不必定义。
请注意,许多软件库都使用抽象类和接口,HashMap类实现了几个接口,并且还扩展了抽象类AbstractMap。
抽象类示例
在面向对象的绘图应用程序中,你可以绘制圆形、矩形、线条、贝塞尔曲线和许多其他图形对象,这些对象都具有某些状态(例如:位置、方向、线条颜色、填充颜色)和行为(例如:moveTo、rotate、resize、draw)。所有图形对象中的一些状态和行为都是相同的(例如:位置、填充颜色和moveTo),其他需要不同的实现(例如,resize或draw)。所有GraphicObjects必须能够自己绘制或调整大小,它们只是做的方式不同,这是抽象超类的完美情况。你可以利用相似性并声明所有图形对象从同一个抽象父对象(例如,GraphicObject)继承,如下图所示。

首先,声明一个抽象类GraphicObject,以提供所有子类完全共享的成员变量和方法,例如当前位置和moveTo方法,GraphicObject还声明了方法的抽象方法,例如draw或resize,它们需要由所有子类实现,但必须以不同的方式实现,GraphicObject类看起来像这样:
abstract class GraphicObject {
int x, y;

void moveTo(int newX, int newY) {

}
abstract void draw();
abstract void resize();
}
GraphicObject的每个非抽象子类(例如Circle和Rectangle)必须提供draw和resize方法的实现:
class Circle extends GraphicObject {
void draw() {

}
void resize() {

}
}
class Rectangle extends GraphicObject {
void draw() {

}
void resize() {

}
}
当抽象类实现接口时
在接口一节中,注意到实现接口的类必须实现所有接口的方法,但是,可以定义一个不实现所有接口方法的类,前提是该类被声明为abstract,例如:
abstract class X implements Y {
// implements all but one method of Y
}

class XX extends X {
// implements the remaining method in Y
}
在这种情况下,类X必须是abstract,因为它没有完全实现Y,但实际上,类XX实现了Y。
类成员
抽象类可以具有静态字段和静态方法,你可以像使用任何其他类一样使用带有类引用的静态成员(例如,AbstractClass.staticMethod())。
继承总结
除了Object类之外,一个类只有一个直接超类,类继承其所有超类中的字段和方法,无论是直接还是间接,子类可以重写它继承的方法,也可以隐藏它继承的字段或方法(请注意,隐藏字段通常是糟糕的编程习惯)。
“覆盖和隐藏方法”部分中的表显示了使用与超类中的方法相同的签名声明方法的效果。
Object类是类层次结构的顶部,所有类都是此类的后代,并从中继承方法,从Object继承的有用方法包括toString()、equals()、clone()和getClass()。
你可以通过在类的声明中使用final关键字来阻止类被子类化,同样,你可以通过将方法声明为final方法来防止子类重写该方法。
抽象类只能被子类化,它无法实例化,抽象类可以包含抽象方法 — 声明但未实现的方法,然后,子类提供抽象方法的实现。

上一篇:接口

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理