java继承extends
Java通过继承和组合支持类重用。 本教程分为两部分,教您如何在Java程序中使用继承。 在第1部分中,您将学习如何使用extends
关键字从父类派生子类,调用父类的构造函数和方法以及重写方法。 在第2部分中,您将游览java.lang.Object
,这是Java的超类,其他所有类都从该类继承。
要完成对继承的学习,请务必查看我的Java技巧,其中解释了何时使用composition vs继承 。 您将了解为什么组合是继承的重要补充,以及如何使用组合来防止Java程序中的封装问题。
Java继承:两个例子
继承是软件开发人员用来在类别之间建立is-a关系的一种编程结构。 继承使我们能够从更通用的类别中派生出更具体的类别。 较具体的类别是较一般的类别的一种。 例如,支票帐户是一种您可以在其中进行存款和取款的帐户。 同样,卡车是一种用于运输大型物品的车辆。
继承可以分为多个级别,从而导致类别更加具体。 例如,图1显示了从车辆继承的汽车和卡车。 从汽车继承的旅行车; 和从卡车继承的垃圾车。 箭头从较具体的“子”类别(较低)指向较不具体的“父”类别(较高)。
此示例说明了单个继承 ,其中子类别从一个直接父类别继承状态和行为。 相反, 多重继承使子类别可以继承两个或多个直接父类别的状态和行为。 图2中的层次结构说明了多重继承。
类别按类别进行描述。 Java通过类扩展支持单一继承,其中一个类通过扩展该类直接从另一类继承可访问的字段和方法。 但是,Java不支持通过类扩展进行多重继承。
查看继承层次结构时,您可以通过菱形图案的存在轻松地检测到多个继承。 图2在车辆,陆地车辆,水上车辆和气垫船的背景下显示了这种模式。
extend关键字
Java通过extends
关键字支持类扩展。 如果存在,则extends
指定两个类之间的父子关系。 下面我使用extends
来建立类Vehicle
和Car
之间的关系,然后建立Account
和SavingsAccount
之间的关系:
清单1. extends
关键字指定父子关系
class Vehicle
{
// member declarations
}
class Car extends Vehicle
{
// inherit accessible members from Vehicle
// provide own member declarations
}
class Account
{
// member declarations
}
class SavingsAccount extends Account
{
// inherit accessible members from Account
// provide own member declarations
}
extends
关键字在类名之后和另一个类名之前指定。 extends
前的类名标识子级, extends
后的类名标识父级。 extends
后无法指定多个类名,因为Java不支持基于类的多重继承。
这些示例将is-a关系SavingsAccount
: Car
是专门的Vehicle
而SavingsAccount
是专门的Account
。 Vehicle
和Account
被称为基类 , 父类或超类 。 Car
和SavingsAccount
被称为派生类 , 子类或子类 。
期末课程
您可以声明一个不应扩展的类。 例如出于安全原因。 在Java中,我们使用final
关键字来防止某些类被扩展。 只需在类标头前面加上final
,如final class Password
。 给定此声明,如果有人尝试扩展Password
,则编译器将报告错误。
子类从其父类和其他祖先继承可访问的字段和方法。 但是,它们从不继承构造函数。 而是,子类声明其自己的构造函数。 此外,他们可以声明自己的领域和方法,以区别于父母。 考虑清单2。
清单2. Account
父类
class Account
{
private String name;
private long amount;
Account(String name, long amount)
{
this.name = name;
setAmount(amount);
}
void deposit(long amount)
{
this.amount += amount;
}
String getName()
{
return name;
}
long getAmount()
{
return amount;
}
void setAmount(long amount)
{
this.amount = amount;
}
}
清单2描述了具有名称和初始金额的通用银行帐户类,它们都在构造函数中设置。 此外,它还允许用户进行存款。 (您可以通过存入负数进行提款,但是我们会忽略这种可能性。)请注意,在创建帐户时必须设置帐户名称。
代表货币价值
几分钱。 您可能更喜欢使用double
或float
来存储货币值,但是这样做可能会导致不准确。 为了获得更好的解决方案,请考虑BigDecimal
,它是Java标准类库的一部分。
清单3给出了一个SavingsAccount
子类,该子类扩展了其Account
父类。
清单3. SavingsAccount
子类扩展了其Account
父类
class SavingsAccount extends Account
{
SavingsAccount(long amount)
{
super("savings", amount);
}
}
SavingsAccount
类很简单,因为它不需要声明其他字段或方法。 但是,它确实声明了一个构造函数,用于初始化其Account
超类中的字段。 初始化发生在通过Java的super
关键字调用Account
的构造函数,然后是带有括号的参数列表。
何时何地调用super()
就像this()
必须是构造函数中调用同一类中另一个构造函数的第一个元素一样, super()
必须是构造函数中调用其父类中的构造函数的第一个元素。 如果违反此规则,编译器将报告错误。 如果编译器在方法中检测到super()
调用,则还将报告错误。 仅在构造函数中调用super()
。
清单4用CheckingAccount
类进一步扩展了Account
。
清单4. CheckingAccount
子类扩展了其Account
父类
class CheckingAccount extends Account
{
CheckingAccount(long amount)
{
super("checking", amount);
}
void withdraw(long amount)
{
setAmount(getAmount() - amount);
}
}
CheckingAccount
比SavingsAccount
实质性多一点,因为它声明了withdraw()
方法。 注意此方法对setAmount()
和getAmount()
的调用, CheckingAccount
继承自Account
。 您不能直接访问Account
的amount
字段,因为该字段被声明为private
(请参见清单2)。
super()和无参数构造函数
如果未在子类构造函数中指定super()
,并且如果超类未声明no-argument
构造函数,则编译器将报告错误。 这是因为当不存在super()
时,子类构造函数必须调用no-argument
超类构造函数。
类层次结构示例
我创建了一个AccountDemo
应用程序类,可让您试用Account
类的层次结构。 首先看一下AccountDemo
的源代码。
清单5. AccountDemo
演示了帐户类的层次结构
class AccountDemo
{
public static void main(String[] args)
{
SavingsAccount sa = new SavingsAccount(10000);
System.out.println("account name: " + sa.getName());
System.out.println("initial amount: " + sa.getAmount());
sa.deposit(5000);
System.out.println("new amount after deposit: " + sa.getAmount());
CheckingAccount ca = new CheckingAccount(20000);
System.out.println("account name: " + ca.getName());
System.out.println("initial amount: " + ca.getAmount());
ca.deposit(6000);
System.out.println("new amount after deposit: " + ca.getAmount());
ca.withdraw(3000);
System.out.println("new amount after withdrawal: " + ca.getAmount());
}
}
清单5中的main()
方法首先演示了SavingsAccount
,然后是CheckingAccount
。 假设Account.java
, SavingsAccount.java
, CheckingAccount.java
和AccountDemo.java
源文件位于同一目录中,请执行以下任一命令来编译所有这些源文件:
javac AccountDemo.java
javac *.java
执行以下命令以运行该应用程序:
java AccountDemo
您应该观察以下输出:
account name: savings
initial amount: 10000
new amount after deposit: 15000
account name: checking
initial amount: 20000
new amount after deposit: 26000
new amount after withdrawal: 23000
方法覆盖(和方法重载)
子类可以重写 (替换)继承的方法,以便改为调用子类的方法版本。 覆盖方法必须指定与被覆盖方法相同的名称,参数列表和返回类型。 为了演示,我在下面的Vehicle
类中声明了print()
方法。
清单6.声明要重写的print()
方法
class Vehicle
{
private String make;
private String model;
private int year;
Vehicle(String make, String model, int year)
{
this.make = make;
this.model = model;
this.year = year;
}
String getMake()
{
return make;
}
String getModel()
{
return model;
}
int getYear()
{
return year;
}
void print()
{
System.out.println("Make: " + make + ", Model: " + model + ", Year: " +
year);
}
}
接下来,我在Truck
类中重写print()
。
清单7.在Truck
子类中重写print()
class Truck extends Vehicle
{
private double tonnage;
Truck(String make, String model, int year, double tonnage)
{
super(make, model, year);
this.tonnage = tonnage;
}
double getTonnage()
{
return tonnage;
}
void print()
{
super.print();
System.out.println("Tonnage: " + tonnage);
}
}
Truck
的print()
方法具有相同的名称,返回类型和参数列表Vehicle
的print()
方法。 还要注意, Truck
的print()
方法首先通过为super.
加上前缀来调用Vehicle
的print()
方法super.
方法名称。 首先执行超类逻辑然后执行子类逻辑通常是一个好主意。
从子类方法调用超类方法
为了从覆盖的子类方法中调用超类方法,请在方法名称前加上保留字super
和成员访问运算符。 否则,您将最终以递归方式调用子类的重写方法。 在某些情况下,子类将通过声明同名字段来掩盖非private
超类字段。 您可以使用super
和成员访问运算符来访问非private
超类字段。
为了完成此示例,我摘录了VehicleDemo
类的main()
方法:
Truck truck = new Truck("Ford", "F150", 2008, 0.5);
System.out.println("Make = " + truck.getMake());
System.out.println("Model = " + truck.getModel());
System.out.println("Year = " + truck.getYear());
System.out.println("Tonnage = " + truck.getTonnage());
truck.print();
最后一行, truck.print();
,调用truck
的print()
方法。 此方法首先调用Vehicle
的print()
来输出卡车的品牌,型号和年份; 然后输出卡车的吨位。 输出的这一部分如下所示:
Make: Ford, Model: F150, Year: 2008
Tonnage: 0.5
使用final阻止方法覆盖
出于安全性或其他原因,有时您可能需要声明一个不应重写的方法。 您可以为此使用final
关键字。 为避免覆盖,只需在方法标头之前加上final
,就像final String getMake()
。 如果有人试图在子类中重写此方法,则编译器将报告错误。
方法重载与覆盖
假设您用以下代码替换了清单7中的print()
方法:
void print(String owner)
{
System.out.print("Owner: " + owner);
super.print();
}
修改后的Truck
类现在具有两个print()
方法:前面的显式声明的方法和从Vehicle
继承的方法。 void print(String owner)
方法不会覆盖Vehicle
的print()
方法。 而是,它使它过载 。
您可以通过在子类的方法标头前面添加@Override
批注来检测是否尝试重载而不是在编译时覆盖方法:
@Override
void print(String owner)
{
System.out.print("Owner: " + owner);
super.print();
}
指定@Override
告诉编译器给定的方法将覆盖另一个方法。 如果有人尝试重载该方法,则编译器将报告错误。 没有此注释,编译器将不会报告错误,因为方法重载是合法的。
何时使用@Override
养成使用@Override
为覆盖方法加前缀的习惯。 这种习惯将帮助您更快地发现超载错误。
翻译自: https://www.infoworld.com/article/2987426/java-101-inheritance-in-java-part-1.html
java继承extends