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

Java平台中的所有类都是对象的后代
在层次结构的顶部,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;
}
}
类声明的MountainBike类,它是一个子类Bicycle可能是这样的:
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类中的方法很复杂并且调试花费了大量时间,那么这将特别有价值。
您可以在子类中做什么
子类继承其父级的所有公共成员和受保护成员,无论该子类位于哪个包中。如果该子类与其父级位于同一包中,则它还将继承父级的包私有成员。您可以按原样使用继承的成员,替换它们,隐藏它们,或用新成员补充它们:
- 继承的字段可以像其他任何字段一样直接使用。
- 您可以在子类中声明一个与超类中的名称相同的字段,从而将其隐藏(不建议)。
- 您可以在子类中声明不在超类中的新字段。
- 继承的方法可以直接使用。
- 您可以在子类中编写一个新的实例方法,该方法具有与超类中的签名相同的签名,从而将其覆盖。
- 您可以在子类中编写一个新的静态方法,该方法具有与超类中的签名相同的签名,从而将其隐藏。
- 您可以在子类中声明不在超类中的新方法。
- 您可以编写一个隐式或使用关键字调用超类的构造函数的子类构造函数super。
本课的以下各节将对这些主题进行扩展。
超类中的私人成员
子类不继承private其父类的成员。但是,如果超类具有用于访问其私有字段的公共或受保护的方法,则子类也可以使用这些方法。
嵌套类可以访问其封闭类的所有私有成员,包括字段和方法。因此,子类继承的公共或受保护的嵌套类可以间接访问超类的所有私有成员。
投射对象
我们已经看到对象是实例化该类的数据类型的。例如,如果我们写
public MountainBike myBike = new MountainBike();
然后myBike是类型MountainBike。
MountainBike是Bicycle和的后代Object。因此,MountainBike是一个Bicycle,也是一个Object,并且它可用于任何Bicycle或Object对象被要求。
相反的情况不一定成立:aBicycle 可以是a MountainBike,但不一定。同样,anObject 可以是aBicycle或a 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引用了a的内容,MountainBike以便我们可以在不引发任何运行时异常的情况下进行强制转换。
状态,实现和类型的多重继承
类和接口之间的一个重要区别是,类可以具有字段,而接口则不能。另外,您可以实例化一个类来创建对象,而这是无法使用接口完成的。如“什么是对象? ”部分中所述 。,对象将其状态存储在类中定义的字段中。Java编程语言不允许您扩展多个类的原因之一是避免状态的多重继承问题,这是从多个类继承字段的能力。例如,假设您能够定义一个扩展多个类的新类。当通过实例化该类创建对象时,该对象将继承该类所有超类的字段。如果来自不同超类的方法或构造函数实例化同一字段怎么办?哪个方法或构造函数优先?因为接口不包含字段,所以您不必担心状态的多重继承引起的问题。
实现的多重继承是从多个类继承方法定义的能力。这种类型的多重继承会产生问题,例如名称冲突和歧义。当支持这种多重继承的编程语言的编译器遇到包含具有相同名称的方法的超类时,它们有时无法确定要访问或调用的成员或方法。另外,程序员可以通过向超类添加新方法来无意间引入名称冲突。 默认方法介绍实现的多重继承的一种形式。一个类可以实现多个接口,该接口可以包含具有相同名称的默认方法。Java编译器提供了一些规则来确定特定类使用哪种默认方法。
Java编程语言支持type的多重继承,这是类实现多个接口的能力。一个对象可以具有多种类型:其自己的类的类型以及该类实现的所有接口的类型。这意味着,如果将变量声明为接口的类型,则其值可以引用从实现该接口的任何类实例化的任何对象。在使用接口作为类型一节中对此进行了讨论 。
与实现的多重继承一样,类可以继承其扩展的接口中定义的方法的不同实现(默认或静态)。在这种情况下,编译器或用户必须决定使用哪个。
覆盖和隐藏方法
实例方法
子类中具有相同签名(名称,加上数字和参数的类型)且返回类型作为超类中的实例方法的实例方法将覆盖超类的方法。
子类重写方法的能力使类可以从行为“足够接近”的超类继承,然后根据需要修改行为。覆盖方法与其覆盖的方法具有相同的名称,数量和参数类型,并且返回类型相同。重写方法还可以返回重写方法返回的类型的子类型。此子类型称为协变返回类型。
覆盖方法时,可能需要使用@Override注释,该注释指示编译器打算覆盖超类中的方法。如果由于某种原因,编译器检测到该方法在超类之一中不存在,则它将生成错误。有关更多信息@Override,请参见 Annotations。
静态方法
如果子类定义的静态方法具有与超类中的静态方法相同的签名,则子类中的方法会将其隐藏在超类中。
隐藏静态方法和覆盖实例方法之间的区别具有重要意义:
- 被调用的重写实例方法的版本是子类中的版本。
- 调用的隐藏静态方法的版本取决于是从超类还是从子类调用。
考虑一个包含两个类的示例。第一个是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继承该方法identifyMyself,该类Horse将重写接口中同名的抽象方法Mammal。
注意:接口中的静态方法永远不会被继承。
修饰符
覆盖方法的访问说明符可以比覆盖方法允许更多但不能更少的访问。例如,超类中的受保护实例方法可以在子类中公开,但不能私有。
如果尝试将超类中的实例方法更改为子类中的静态方法,并且相反,则将遇到编译时错误。
概括
下表总结了在定义具有与超类中的方法相同的签名的方法时发生的情况。
Defining a Method with the Same Signature as a Superclass's Method |
||
Superclass Instance Method |
Superclass Static Method |
|
Subclass Instance Method |
Overrides |
Generates a compile-time error |
Subclass Static Method |
Generates a compile-time error |
Hides |
注意: 在子类中,您可以重载从超类继承的方法。这样的重载方法既不会隐藏也不会覆盖超类实例方法,它们是子类独有的新方法。
多态性
字典中的多态性定义是指生物学中的一种原理,其中生物或物种可以具有许多不同的形式或阶段。该原理也可以应用于面向对象的编程和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语言中的多态功能,请Bicycle使用MountainBike和扩展RoadBike该类。为MountainBike,添加的字段suspension,该字段的String值指示自行车是否具有前减震器Front。或者,自行车具有前后减震器Dual。
这是更新的类:
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方法。除了之前提供的信息外,有关悬架的其他数据也包含在输出中。
接下来,创建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();
}
}
以下是测试程序的输出:
自行车的档位为1,节奏为20,行驶速度为10。
自行车以20的节奏进入5档,并以10的速度行驶。
MountainBike具有双重悬挂。
自行车的档位为40,节奏为20,行驶速度为20。
RoadBike有23毫米的轮胎。
Java虚拟机(JVM)为每个变量中引用的对象调用适当的方法。它不会调用由变量类型定义的方法。此行为称为虚拟方法调用,它说明了Java语言中重要的多态性功能的一个方面。
隐藏字段
在类中,与超类中的字段具有相同名称的字段将隐藏超类的字段,即使它们的类型不同。在子类中,不能通过其简单名称引用超类中的字段。而是必须通过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。因此,要引用printMethod()继承自Superclass,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本节将讨论从中继承的方法:
- 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()
返回对象的字符串表示形式。
的notify,notifyAll和wait方法Object都在一个程序,它在后面的课程中讨论,并不会在这里介绍的同步独立运行的线程的活动中发挥作用。这些方法有五种:
- 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接口,则Object该clone()方法的实现将创建一个与原始对象相同类的对象,并将新对象的成员变量初始化为具有与原始对象的相应成员变量相同的值。
使您的类可克隆的最简单方法是添加implements Cloneable到类的声明中。然后您的对象可以调用该clone()方法。
对于一些类,默认行为Object的clone()方法工作得很好。但是,如果对象包含对外部对象的引用(例如)ObjExternal,则可能需要重写clone()以获取正确的行为。否则,ObjExternal一个对象所做的更改也将在其克隆中可见。这意味着原始对象及其克隆不是独立的,clone()要使它们分离,必须重写以克隆对象和 ObjExternal。然后,原始对象引用ObjExternal和克隆引用的克隆ObjExternal,从而使对象及其克隆真正独立。
equals()方法
该equals()方法比较两个对象是否相等,true如果相等则返回。该类中equals()提供的方法Object使用身份运算符(==)确定两个对象是否相等。对于原始数据类型,这将给出正确的结果。但是,对于对象,则不是。所equals()提供的方法Object测试对象引用是否相等,即所比较的对象是否是完全相同的对象。
要测试两个对象在等效性上是否相等(包含相同的信息),必须重写该equals()方法。这是Book重写的类的示例equals():
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');
}
该程序objects are equal即使显示firstBook并secondBook引用两个不同的对象。之所以认为它们相等,是因为所比较的对象包含相同的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());
}
程序包中的 Class类java.lang具有大量方法(超过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;Swing教程;GUI构造指南,第二版
编写Final类和方法
您可以将某些或所有类的方法声明为final。您可以final在方法声明中使用关键字来指示该方法不能被子类覆盖。该Object班做这个,它的一些方法final。
如果某个方法的实现不应该更改并且对于对象的一致状态至关重要,则可能希望将其定型。例如,您可能想使getFirstPlayer此类中的方法成为ChessAlgorithmfinal:
class ChessAlgorithm {
enum ChessPlayer { WHITE, BLACK }
...
final ChessPlayer getFirstPlayer() {
return ChessPlayer.WHITE;
}
...
}
从构造函数调用的方法通常应声明为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。
注: 方法在接口(见 接口部分)未声明为默认或静态的含蓄抽象的,所以abstract修改不与接口方法使用。(可以使用,但这不是必需的。)
抽象类与接口的比较
抽象类类似于接口。您无法实例化它们,它们可能包含使用或不使用实现声明的方法的混合。但是,使用抽象类,您可以声明非静态和最终字段,并定义公共,受保护的和私有的具体方法。使用接口时,所有字段都自动是公共的,静态的和最终的,并且您声明或定义的所有方法(作为默认方法)都是公共的。此外,无论是否抽象,您都只能扩展一个类,而您可以实现任意数量的接口。
您应该使用哪个抽象类或接口?
- 如果以下任何一种情况适用于您的情况,请考虑使用抽象类:您想在几个紧密相关的类之间共享代码。您期望扩展您的抽象类的类具有许多公共方法或字段,或者需要除public之外的访问修饰符(例如,protected和private)。您要声明非静态或非最终字段。这使您能够定义一些方法,这些方法可以访问和修改它们所属对象的状态。
- 如果以下任何一种情况适用于您的情况,请考虑使用接口:您期望不相关的类将实现您的接口。例如,接口 Comparable和 Cloneable由许多不相关的类实现。您想指定特定数据类型的行为,但不关心谁实现了它的行为。您想利用类型的多重继承。
JDK中的抽象类的一个示例是 AbstractMap,它是Collections Framework的一部分。它的子类(包括HashMap,TreeMap,和ConcurrentHashMap)共享许多方法(包括get,put,isEmpty,containsKey,和containsValue),其AbstractMap定义。
在JDK的类的例子,它实现几个接口是 HashMap,它实现了接口Serializable,Cloneable和Map<K, V>。通过阅读此接口列表,您可以推断出HashMap(可以实例化该类的开发人员或公司的)实例可以被克隆,并且可以序列化(这意味着可以将其转换为字节流;请参见“序列化对象”部分)。 ),并具有地图功能。此外,该Map<K, V>接口已通过许多默认方法进行了增强,例如,merge并且forEach不必定义实现此接口的较早的类。
注意,许多软件库同时使用抽象类和接口。在HashMap类实现多个接口,并且还扩展了抽象类AbstractMap。
一个抽象类的例子
在面向对象的绘图应用程序中,您可以绘制圆形,矩形,直线,贝塞尔曲线和许多其他图形对象。这些对象都有共同的某些状态(例如:位置,方向,线条颜色,填充颜色)和行为(例如:moveTo,旋转,调整大小,绘制)。所有图形对象的某些状态和行为都是相同的(例如:position,fill color和moveTo)。其他的则需要不同的实现(例如,调整大小或绘制)。所有GraphicObject的人都必须能够画画或调整自己的大小。他们只是在做事方式上有所不同。对于抽象超类来说,这是一个完美的情况。您可以利用相似之处,并声明所有图形对象都可以从同一个抽象父对象(例如GraphicObject)继承,如图所示。 下图。

从GraphicObject继承的Rectangle,Line,Bezier和Circle类
首先,您声明一个抽象类,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() {
...
}
}
当抽象类实现接口时
在上的小节中 Interfaces,指出实现接口的类必须实现接口的所有方法。但是,可以定义一个不实现接口所有方法的类,前提是该类声明为abstract。例如,
abstract class X implements Y {
// implements all but one method of Y
}
class XX extends X {
// implements the remaining method in Y
}
在这种情况下,classX必须是abstract因为它没有完全实现Y,但是classXX实际上却实现了Y。
类成员
抽象类可能具有static字段和static方法。您可以AbstractClass.staticMethod()像使用任何其他类一样将这些静态成员与类引用一起使用(例如)。
继承摘要
除了Object该类外,一类仅具有一个直接的超类。一个类从其所有超类(无论是直接的还是间接的)继承字段和方法。子类可以覆盖其继承的方法,也可以隐藏其继承的字段或方法。(请注意,隐藏字段通常是不好的编程习惯。)
“覆盖和隐藏方法”部分中的表 显示了声明与超类中的方法具有相同签名的方法的效果。
本Object类是类层次结构的顶部。所有类都是该类的后代,并从该类继承方法。从继承的有用的方法Object包括toString(),equals(),clone(),和getClass()。
您可以通过final在类的声明中使用关键字来防止类被子类化。同样,可以通过将方法声明为最终方法来防止方法被子类覆盖。
抽象类只能被子类化。它不能被实例化。抽象类可以包含抽象方法,即已声明但未实现的方法。然后,子类提供抽象方法的实现。
问题与练习:继承
问题
1.考虑以下两类:
public class ClassA {
public void methodOne(int i) {
}
public void methodTwo(int i) {
}
public static void methodThree(int i) {
}
public static void methodFour(int i) {
}
}
public class ClassB extends ClassA {
public static void methodOne(int i) {
}
public void methodTwo(int i) {
}
public void methodThree(int i) {
}
public static void methodFour(int i) {
}
}
一个。哪个方法会覆盖超类中的方法?
b。哪个方法在超类中隐藏了一个方法?
C。其他方法有什么作用?
2.考虑 Card, Deck以及 DisplayDeck类你写的 问题和练习班。Object这些类应重写哪些方法?