Java继承

时间:2020-01-09 10:35:45  来源:igfitidea点击:

Java继承是指Java中一个类从另一类继承的能力。在Java中,这也称为扩展类。一个类可以扩展另一个类,从而从该类继承。

当一个类从Java中的另一个类继承时,这两个类将担当某些角色。扩展的类(继承自另一个类)是子类,而扩展的类(继承自该类)是超类。换句话说,子类扩展了超类。或者,子类从超类继承。

继承的另一个常用术语是专门化和泛化。子类是超类的特化,而超类是一个或者多个子类的一般化。

继承是代码重用的一种方法

继承是在具有某些共同特征的类之间共享代码的有效方法,但允许类具有一些不同的部分。

这是说明名为"车辆"的类,该类具有两个名为"汽车"和"卡车"的子类。

"车辆"类是"汽车"和"卡车"的超类。 "汽车"和"卡车"是"车辆"的子类。 "车辆"类可以包含所有"车辆"所需的字段和方法(例如车牌,所有者等),而"汽车"和"卡车"可以包含特定于"汽车"的字段和方法。 s和Trucks。

注意:有人会说继承是一种根据类对类进行分类的方法。 "汽车"是"车辆"。 "卡车"是"车辆"。但是,实际上,这不是我们确定应用程序需要具有哪些超类和子类的方式。通常,这取决于我们需要在应用程序中使用它们的方式。

例如,我们是否需要将"汽车"和"卡车"对象称为"车辆"对象?我们需要统一处理"汽车"和"卡车"对象吗?然后,对于这两个类,有一个通用的" 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继承规则对于"抽象"类与非抽象类是相同的。