Java通过继承和组合支持类重用。在这个两部分的小部件中,我们将关注继承,面向对象编程的基本概念之一。首先,您将学习如何使用extends
关键字从父类派生子类,调用父类构造函数和方法以及覆盖方法。然后,在第2部分,我们将巡视java.lang.Object
和它的方法。Object
是Java的最终超类,每个其他类继承。
通过继承关系类
![Java 101:Java中的继承,图1](http://images.techhive.com/images/article/2015/10/j101-inheritance-fig1-100623245-small.idge.jpg)
图1.一对继承层次结构植根于通用车辆类别
继承是一种编程构造,软件开发人员用它来建立is-a类之间的关系。继承使我们能够从更一般的类别中导出更具体的类别。更具体的类别是一种更通用的类别。例如,支票帐户是一种可用于存款和取款的帐户。类似地,卡车是用于拖运大物品的一种车辆。
继承可以通过多个级别下降,导致更具体的类别。作为示例,图1示出了从车辆继承的汽车和卡车; 车从车继承; 和垃圾车从卡车继承。箭头从更具体的“子”类别(下部)指向较不具体的“父”类别(更高)。
![Java 101:Java中的继承](http://images.techhive.com/images/article/2015/09/j101-inheritance-fig2-100618021-small.idge.jpg)
图2.气垫船多次继承陆地车辆和水车类别
此示例说明了单个继承,其中子类别从一个直接父类别继承状态和行为。相比之下,多重继承使子类别能够从两个或多个直接父类别继承状态和行为。图2中的层次结构说明了多重继承。
菱形图案在多重继承中
当查看继承层次结构时,可以通过存在菱形图案轻松检测多重继承。图2示出了在车辆,陆地车辆,水车和气垫船的情况下的这种模式。
类别由类描述。Java通过类扩展支持单继承,其中一个类通过扩展该类直接从另一个类继承可访问的字段和方法。Java不支持通过类扩展的多继承。
类扩展
Java通过extends
关键字支持类扩展。当存在时,extends
指定两个类之间的父子关系。下面我用extends
建立类之间的关系Vehicle
和Car
之间,然后Account
和SavingsAccount
:
清单1. extends
关键字指定父子关系
该extends
关键字类名后先于另一个类名指定。extends
在extends
识别父类之前,类名称标识子类和类名称。以后不可能指定多个类名,extends
因为Java不支持基于类的多重继承。
这些例子编纂的是一种关系:Car
是一种专门化的Vehicle
,SavingsAccount
是一种专门化的Account
。Vehicle
并且Account
被称为基类,父类或超类。Car
和SavingsAccount
被称为子类,子类,或子类。
最后的类
你可以声明一个不应该扩展的类; 例如出于安全原因。在Java中,我们使用final
关键字来防止一些类被扩展。只需前缀一个类标题与final
,如final class Password
。给定此声明,如果有人尝试扩展,编译器将报告错误Password
。
子类继承其父类和其他祖先的可访问字段和方法。然而,他们从不继承构造函数。相反,子类声明自己的构造函数。此外,他们可以宣布自己的领域和方法,以区别于他们的父母。考虑清单2。
清单2. Account
父类
清单2描述了一个具有名称和初始金额的通用银行帐户类,它们都在构造函数中设置。此外,它允许用户存款。(您可以通过存入负数金额提款,但我们将忽略此可能性。)请注意,帐户名称必须在创建帐户时设置。
表示货币值
对于清单2,我选择将货币值表示为long
整数,其中值存储为一分钱。您可能更喜欢使用a double
或a float
来存储货币价值,但这样做可能会导致不准确。为了更好的解决方案,考虑BigDecimal
,这是Java的标准类库的一部分。
清单3展示了一个SavingsAccount
扩展Account
其父类的子类。
清单3. SavingsAccount
子类扩展它的Account
父类
的SavingsAccount
,因为它并不需要声明其他字段或方法的类实在是微不足道。但是,它会声明一个构造函数来初始化其Account
超类中的字段。初始化发生在Account
通过Java的super
关键字调用构造函数时,后面跟着一个带括号的参数列表。
何时何地调用super()
正如this()
必须是在同一个类中调用另一个构造函数的构造函数中super()
的第一个元素,必须是在其超类中调用构造函数的构造函数中的第一个元素。如果违反此规则,编译器将报告错误。如果编译器检测到super()
方法中的调用,它也将报告错误; 只有super()
在构造函数中调用。
清单4进一步扩展Account
了一个CheckingAccount
类。
清单4.一个CheckingAccount
子类扩展了它的Account
父类
CheckingAccount
是一个比一个更重要的SavingsAccount
因为它声明一个withdraw()
方法。注意这个方法对setAmount()
和的调用getAmount()
,它CheckingAccount
继承自Account
。您不能直接访问该amount
字段,Account
因为此字段已声明private
(参见清单2)。
super()和无参构造函数
如果super()
没有在子类构造函数中指定,并且超类没有声明一个无参数构造函数,那么编译器将报告一个错误。这是因为子类构造函数在super()
不存在时必须调用无参数超类构造函数。
演示帐户类层次结构
我创建了一个应用AccountDemo
程序类,让您尝试Account
类层次结构。首先看看AccountDemo
源代码。
清单5. AccountDemo
演示帐户类层次结构
该main()
清单5中方法首先表明SavingsAccount
,然后CheckingAccount
。假设Account.java
,SavingsAccount.java
,CheckingAccount.java
和AccountDemo.java
源文件在同一目录中,既可以执行以下命令来编译所有这些源文件:
执行以下命令运行应用程序:
您应该观察以下输出:
方法覆盖
子类可以覆盖(替换)继承的方法,以便调用子类的方法版本。覆盖方法必须指定与要覆盖的方法相同的名称,参数列表和返回类型。为了演示,我已经print()
在Vehicle
下面的类中声明了一个方法。
清单6.声明要覆盖的print()
方法
接下来,我print()
在Truck
类中重写。
清单7. print()
在Truck
子类中重写
Truck
的print()
方法具有相同的名称,返回类型和参数列表Vehicle
的print()
方法。也请注意,这Truck
的print()
方法首先调用Vehicle
的print()
通过前缀的方法super.
方法名。通常最好先执行超类逻辑,然后执行子类逻辑。
从子类方法调用超类方法
为了从覆盖子类方法调用超类方法,在方法名称前加上保留字super
和成员访问运算符。否则,你将最终递归调用子类的覆盖方法。在某些情况下,子类将private
通过声明相同命名的字段来掩盖非超类字段。您可以使用super
和成员访问运算符来访问非private
超类字段。
为了完成这个例子,我已经摘录了一个VehicleDemo
类的main()
方法:
最后一行,truck.print();
,调用truck
的print()
方法。此方法首先调用Vehicle
的print()
输出卡车的品牌,型号和年份; 然后输出卡车的吨位。这部分输出如下所示:
使用final来阻止方法覆盖
有时,您可能需要声明一个方法,不应为了安全或其他原因而重写。您可以final
为此使用关键字。为了防止覆盖,只需在方法标题前加上final
,如final String getMake()
。如果任何人试图在子类中覆盖此方法,编译器将报告错误。
方法重载而不是覆盖
假设您将print()
清单7中的方法替换为下面的方法:
修改的Truck
类现在有两个print()
方法:前面的显式声明的方法和继承的方法Vehicle
。该void print(String owner)
方法不覆盖Vehicle
的print()
方法。相反,它重载它。
您可以检测重载尝试,而不是在编译时覆盖方法,方法是在子类的方法头中添加@Override
注释:
指定@Override
告诉编译器给定的方法覆盖另一个方法。如果有人试图重载该方法,编译器将报告错误。没有这个注释,编译器不会报告错误,因为方法重载是合法的。
何时使用@覆盖
开发前缀覆盖方法的习惯@Override
。这个习惯将帮助你更快地检测超载错误。
方法覆盖和保护方法
Java提供了protected
在方法覆盖上下文中使用的关键字。您还可以protected
用于字段。此关键字通常用于标识设计为要覆盖的方法,因为并非所有可访问方法都应重写。
当您声明方法或字段protected
时,方法或字段可以访问在同一个包中声明的任何类中的所有代码。它也可以访问子类,而不管它们的包。(我将在以后的文章中讨论软件包。)