Java继承
Java继承是指Java中一个类从另一类继承的能力。在Java中,这也称为扩展类。一个类可以扩展另一个类,从而从该类继承。
当一个类从Java中的另一个类继承时,这两个类将担当某些角色。扩展的类(继承自另一个类)是子类,而扩展的类(继承自该类)是超类。换句话说,子类扩展了超类。或者,子类从超类继承。
继承的另一个常用术语是专门化和泛化。子类是超类的特化,而超类是一个或者多个子类的一般化。
继承是代码重用的一种方法
继承是在具有某些共同特征的类之间共享代码的有效方法,但允许类具有一些不同的部分。
这是说明名为"车辆"的类,该类具有两个名为"汽车"和"卡车"的子类。
"车辆"类是"汽车"和"卡车"的超类。 "汽车"和"卡车"是"车辆"的子类。 "车辆"类可以包含所有"车辆"所需的字段和方法(例如车牌,所有者等),而"汽车"和"卡车"可以包含特定于"汽车"的字段和方法。 s和Truck
s。
注意:有人会说继承是一种根据类对类进行分类的方法。 "汽车"是"车辆"。 "卡车"是"车辆"。但是,实际上,这不是我们确定应用程序需要具有哪些超类和子类的方式。通常,这取决于我们需要在应用程序中使用它们的方式。
例如,我们是否需要将"汽车"和"卡车"对象称为"车辆"对象?我们需要统一处理"汽车"和"卡车"对象吗?然后,对于这两个类,有一个通用的" Vehicle"超类是有意义的。如果我们从未以相同的方式处理"汽车"和"卡车"对象,那么为它们拥有一个共同的超类是没有意义的,除非它们之间可以共享代码(以避免编写重复的代码)。
类层次结构
超类和子类形成一个继承结构,也称为类层次结构。在类层次结构的顶部,我们具有超类。在类层次结构的底部,我们有子类。
一个类层次结构可以具有多个级别,这意味着多个级别的超类和子类。子类本身可以是其他子类的超类,等等。
Java继承基础
当类从超类继承时,它会继承部分超类的方法和字段。子类还可以重写(重新定义)继承的方法。字段不能被覆盖,但是可以在子类中被"阴影化"。这一切的工作方式将在本文后面介绍。
什么是继承的?
当子类在Java中扩展超类时,该超类的所有" protected"和" public"字段以及方法均由该子类继承。继承是指这些字段和方法是子类的一部分,就像子类已经声明了它们本身一样。可以像直接在子类中声明的方法一样,调用和引用" protected"和" public"字段。
仅当子类与超类位于同一包中时,子类才能访问带有默认(包)访问修饰符的字段和方法。子类永远不能直接引用超类的私有字段和方法。但是,可以通过子类可访问的方法(例如,默认(包)," protected"和" public"方法)间接引用它们。
构造函数不是子类继承的,但是子类构造函数必须在超类中调用构造函数。这将在后面的部分中详细说明。
Java仅支持单一继承
Java继承机制仅允许Java类从单个超类继承(单一继承)。在某些编程语言中,例如C ++,子类有可能从多个超类继承(多重继承)。由于多重继承会产生一些奇怪的问题,例如这些超类包含具有相同名称和参数的方法,Java中省略了多个继承。
在Java中声明继承
在Java中,继承是使用extends
关键字声明的。我们可以通过在类定义中使用extends
关键字来声明一个类扩展了另一类。这是使用extends
关键字的Java继承示例:
public class Vehicle { protected String licensePlate = null; public void setLicensePlate(String license) { this.licensePlate = license; } }
public class Car extends Vehicle { int numberOfSeats = 0; public String getNumberOfSeats() { return this.numberOfSeats; } }
本示例中的Car类扩展了Vehicle类,这意味着Car类继承自Veehicle类。
因为Car
类扩展了Vehicle
类,所以Car
类继承了Vehicle
类的protected
字段licensePlate
。当" licensePlate"字段被继承时,可以在" Car"实例内部访问该字段。
上面代码中的Car
类实际上并未引用licensePlate
字段,但是如果我们愿意的话,也可以引用。这是一个显示效果的示例:
public class Car extends Vehicle { int numberOfSeats = 0; public String getNumberOfSeats() { return this.numberOfSeats; } public String getLicensePlate() { return this.licensePlate; } }
引用发生在getLicensePlate()
方法内部。
在许多情况下,将getLicensePlate()方法放在" licensePlate"字段所在的" Vehicle"类中很有意义。我只是将getLicensePlate()方法放在Car类中,以表明这是可能的。
继承和类型转换
可以将子类引用为其父类之一的实例。例如,使用上一部分示例中的类定义,可以将Car类的实例引用为Vehicle类的实例。由于"汽车"类是"汽车"类的继承(继承自),因此也称为"汽车"类。这是一个Java代码示例:
Car car = new Car(); Vehicle vehicle = car;
首先创建一个Car实例。其次,将"汽车"实例分配给"汽车"类型的变量。现在," Vehicle"变量(参考)指向" Car"实例。这是可能的,因为Car
类继承自Vehicle
类。
如我们所见,可以使用某些子类的实例,就像它是其超类的实例一样。这样,我们无需确切知道对象是其实例的哪个子类。我们可以例如"卡车"和"汽车"实例都作为"车辆"实例。
将类的对象引用为与类本身不同的类型的过程称为类型转换。我们将对象从一种类型转换为另一种类型。
向上转换和向下转换
我们始终可以将子类的对象强制转换为其父类之一。这称为向上转换(从子类类型到超类类型)。
也可能将对象从超类类型转换为子类类型,但前提是该对象确实是该子类的实例(或者该子类的子类的实例)。这称为向下转换(从超类类型到子类类型)。因此,此向下转换的示例有效:
Car car = new Car(); // upcast to Vehicle Vehicle vehicle = car; // downcast to car again Car car2 = (Car) vehicle;
但是,下面的向下转换示例无效。 Java编译器将接受它,但是在运行该代码时在运行时,该代码将引发ClassCastException
。
Truck truck = new Truck(); // upcast to Vehicle Vehicle vehicle = truck; // downcast to car again Car car = (Car) vehicle;
"卡车"对象可以向上投射到"车辆"对象,但是以后不能向下投射到"车载"对象。这将导致ClassCastException
。
覆盖方法
在子类中,我们可以重写(重新定义)超类中定义的方法。这是一个Java方法覆盖示例:
public class Vehicle { String licensePlate = null; public void setLicensePlate(String licensePlate) { this.licensePlate = licensePlate; } }
public class Car extends Vehicle { public void setLicensePlate(String license) { this.licensePlate = license.toLowerCase(); } }
注意"车辆"类和"汽车"类如何定义了一种名为" setLicensePlate()"的方法。现在,只要在Car对象上调用setLicensePlate()方法,就会调用Car类中定义的方法。超类中的方法将被忽略。
要覆盖方法,子类中的方法签名必须与超类中的方法签名相同。这意味着子类中的方法定义必须具有完全相同的名称,相同数量的参数和类型,并且必须以与超类中完全相同的顺序列出参数。否则,子类中的方法将被视为单独的方法。
在Java中,我们不能覆盖超类的私有方法。如果超类从其他方法内部调用了一个私有方法,则即使我们在具有相同签名的子类中创建了一个私有方法,它也会继续从超类中调用该方法。
@override注释
如果我们覆盖子类中的方法,而该方法突然在超类中被删除或者重命名,或者其签名已更改,则子类中的方法将不再覆盖超类中的方法。但是你怎么知道?如果编译器可以告诉我们被覆盖的方法不再覆盖超类中的方法,那会很好,对吗?
这就是Java @override注释的用途。我们将Java@ override
注释放置在重写超类中的方法的方法上方。这是Java@ override
示例:
public class Car extends Vehicle { @Override public void setLicensePlate(String license) { this.licensePlate = license.toLowerCase(); } }
调用超类方法
如果我们覆盖子类中的方法,但仍需要调用超类中定义的方法,则可以使用" super"引用来做到这一点,如下所示:
public class Car extends Vehicle { public void setLicensePlate(String license) { super.setLicensePlate(license); } }
在上面的代码示例中,Car类中的setLicensePlate()方法调用了Vehicle类中的setLicensePlate()方法。
我们可以从子类中的任何方法调用超类实现,如上所述。它不必来自重写的方法本身。例如,我们还可以从Car类中名为updateLicensePlate()的方法中调用方法super.setLicensePlate(),该方法不会覆盖setLicensePlate()方法。
指令实例
Java包含名为" instanceof"的指令。 " instanceof"指令可以确定给定对象是否是某个类的实例。这是一个Javainstanceof
示例:
Car car = new Car(); boolean isCar = car instanceof Car;
执行此代码后," isCar"变量将包含值" true"。
还可以使用" instanceof"指令来确定对象是否是其类的超类的实例。这是一个" instanceof"示例,用于检查" Car"对象是否为" Vehicle"的实例:
Car car = new Car(); boolean isVehicle = car instanceof Vehicle;
假设Car类扩展自Vehicle类(继承),则在执行此代码后,isVehicle变量将包含值true。 "汽车"对象也是"汽车"对象,因为"汽车"是"汽车"的子类。
如我们所见,instanceof指令可用于探索继承层次结构。
与instanceof指令一起使用的变量类型不会影响其结果。看一下这个" instanceof"示例:
Car car = new Car(); Vehicle vehicle = car; boolean isCar = vehicle instanceof Car;
即使vehicle
变量是Vehicle
类型,在此示例中它最终指向的对象还是Car
对象。因此,"汽车实例汽车"指令将评估为"真"。
这是相同的" instanceof"示例,但是使用" Truck"对象而不是" Car"对象:
Truck truck = new Truck(); Vehicle vehicle = truck; boolean isCar = vehicle instanceof Car;
执行此代码后," isCar"将包含值" false"。 "卡车"对象不是"汽车"对象。
字段与继承
如前所述,在Java中,不能在子类中覆盖字段。如果我们在子类中定义一个与超类中的字段同名的字段,则子类中的该字段将隐藏(阴影)超类中的字段。如果子类尝试访问该字段,它将访问子类中的字段。
但是,如果子类调用了父类中的方法,并且该方法访问的名称与子类中的名称相同,则将访问父类中的字段。
以下是Java继承示例,该示例说明了子类中的字段如何遮蔽(隐藏)超类中的字段:
public class Vehicle { String licensePlate = null; public void setLicensePlate(String licensePlate) { this.licensePlate = licensePlate; } public String getLicensePlate() { return licensePlate; } }
public class Car extends Vehicle { protected String licensePlate = null; @Override public void setLicensePlate(String license) { super.setLicensePlate(license); } @Override public String getLicensePlate() { return super.getLicensePlate(); } public void updateLicensePlate(String license){ this.licensePlate = license; } }
注意这两个类如何定义一个" licensePlate"字段。
车载类和汽车类都具有setLicensePlate()和getLicensePlate()方法。 "汽车"类中的方法调用"汽车"类中的相应方法。结果是,最终这两种方法都访问了"车辆"类中的" licensePlate"字段。
Car类中的updateLicensePlate()方法直接访问licensePlate字段。因此,它访问"汽车"类的"许可证板"字段。因此,如果我们调用setLicensePlate()与调用updateLicense()方法时得到的结果不同。
查看以下几行Java代码:
Car car = new Car(); car.setLicensePlate("123"); car.updateLicensePlate("abc"); System.out.println("license plate: " + car.getLicensePlate());
该Java代码将打印出文本123.
updateLicensePlate()方法在Car类中的licensePlate字段上设置车牌值。但是,getLicensePlate()
方法将返回Vehicle
类中的licensePlate
字段的值。因此,通过setLicensePlate()方法设置为Vehicle类中的licensePlate字段的值的值123是打印出来的。
构造函数与继承
Java继承机制不包括构造函数。换句话说,超类的构造函数不会被子类继承。子类仍然可以使用" super()"构造在超类中调用构造函数。实际上,需要一个子类构造函数来调用超类中的一个构造函数,作为构造函数体内的第一个动作。看起来是这样的:
public class Vehicle { public Vehicle() { } }
public class Car extends Vehicle{ public Car() { super(); //perform other initialization here } }
注意在Car构造函数中对super()的调用。这个super()
调用执行Vehicle
类中的构造函数。
我们可能已经看到Java类,其中子类构造函数似乎并未调用超类中的构造函数。也许超类甚至没有构造函数。但是,在这种情况下,子类构造函数仍称为超类构造函数。我们只是看不到它。让我解释一下原因:
如果一个类没有定义任何显式的构造函数,则Java编译器将插入一个隐式的无参数构造函数。因此,类始终具有构造函数。因此,以下"车辆"版本与上面显示的版本等效:
public class Vehicle { }
其次,如果构造函数未在超类中显式调用构造函数,则Java编译器会在超类中向no-arg构造函数插入隐式调用。这意味着以下Car类的版本实际上与前面显示的版本等效:
public class Car extends Vehicle{ public Car() { } }
实际上,由于构造函数现在为空,我们可以将其保留,而Java编译器将其插入,并在超类中插入对no-arg构造函数的隐式调用。这是两个类的外观:
public class Vehicle { }
public class Car extends Vehicle{ }
即使在这两个类中没有声明任何构造函数,它们都获得了一个无参数的构造函数,而Car类中的无参数构造函数将调用Vehicle类中的无参数构造函数。
如果Vehicle
类没有no-arg构造函数,但是有另一个带有参数的构造函数,则Java编译器会抱怨。然后将需要Car类来声明一个构造函数,并在该构造函数内部调用Vehicle类中的构造函数。
嵌套类和继承
相同的Java继承规则适用于嵌套类。声明为"私有"的嵌套类不会被继承。如果子类与父类位于同一包中,则具有默认(包)访问修饰符的嵌套类只能由子类访问。带有" protected"或者" public"访问修饰符的嵌套类始终由子类继承。
这是一个嵌套的类继承示例:
class MyClass { class MyNestedClass { } }
public class MySubclass extends MyClass { public static void main(String[] args) { MySubclass subclass = new MySubclass(); MyNestedClass nested = subclass.new MyNestedClass(); } }
注意如何创建嵌套类MyNestedClass的实例,该实例是通过引用子类MySubclass来在超类MyClass中定义的。
final类和继承
一个类可以被声明为" final"。现在看起来是这样:
public final class MyClass { }
最终类不能扩展。换句话说,我们不能从Java中的final
类继承。
抽象类和继承
在Java中,可以将类声明为"抽象"。我已经在我的Java抽象类教程中更详细地介绍了"抽象"类。
"抽象"类是不包含"抽象"类应做的任何事情的完整实现的类。因此,它无法实例化。换句话说,我们不能创建"抽象"类的对象。
在Java中,"抽象"类旨在扩展以创建完整的实现。因此,完全有可能扩展"抽象"类。 Java继承规则对于"抽象"类与非抽象类是相同的。