Java继承机制详解(图文并茂,附带实例)

Java继承机制详解(图文并茂,附带实例)

在 Java 的面向对象程序设计中,继承是不可或缺的一部分。通过继承可以实现代码的复用,提高程序的可维护性。

在现实世界中的对象存在很多如下图的关系:

图 1 不同车之间的关系

巴士、卡车和出租车都是汽车的一种,分别拥有相似的特性,如对所有的交通工具而言都具备引擎数量、外观的颜色,相似的行为如刹车和加速的功能。但每种不同的交通工具又有自己的特性,例如:

巴士拥有和其他交通工具不同的特性和行为,即最大载客数量和到指定站点要报站的特点;

卡车的主要功能是运送货物,也就是载货和卸货,因此拥有最大载重的特性。

面向对象的程序设计中该怎样描述现实世界中的这种情况呢?这就要用到继承的概念。继承就是从已有的类派生出新的类。新的类能吸收已有类的数据属性和行为,并能扩展新的能力。

已有的类一般称为父类(基类或超类)。由基类产生的新类称为派生类或子类。派生类同样也可以作为基类再派生新的子类,这样就形成了类间的层次结构。修改后的交通工具间的继承关系如下图所示。

图 2 继承关系

汽车被抽象为父类(基类或超类),代表一般化属性。而巴士、卡车和出租车转化为子类,继承父类的一般特性包括父类的数据成员和行为,如外观颜色和刹车等特性,又产生自己独特的属性和行为,如巴士的最大载客数和报站。

继承的方式包括单一继承和多重继承:

单一继承是最简单的方式,一个派生类只从一个基类派生;

多重继承是一个派生类有两个或多个基类。

两种继承方式如下图所示:

图 3 继承的方式

图中箭头的方向表示继承的方向,由子类指向父类。

通过上面介绍可以看出基类与派生类的关系:

基类是派生类的抽象(基类抽象了派生类的公共特性);

派生类是对基类的扩展;

派生类和基类的关系相当于“是一个(is a)”的关系,即派生类是基类的一个对象,而不是“有(has)”的组合关系,即类的对象包含一个或多个其他类的对象作为该类的属性,如汽车类拥有发动机、轮胎,这种关系称为类的组合。

注意,Java 语言只支持单一继承,不支持多重继承。

例如设计并实现教师类,其中教师分为 Java 教师以及 .NET 教师,各自的要求如下:

Java教师:

属性:姓名、所属部门

方法:授课(打开 Eclipse、实施理论课授课)、自我介绍

.NET教师:

属性:姓名、所属部门

方法:授课(打开 Visual studio 2010、实施理论课授课)、自我介绍

根据要求我们分别定义 Java 教师类和 .NET 教师类,代码如下:

public class JavaTeacher {

private String name; // 教师姓名

private String school; // 所在学校

public JavaTeacher(String myName, String mySchool) {

name = myName;

school = mySchool;

}

public void giveLesson() { // 授课方法的具体实现

System.out.println("启动 MyEclipse");

System.out.println("知识点讲解 ");

System.out.println("总结提问 ");

}

public void introduction() { // 自我介绍方法的具体实现

System.out.println("大家好!我是" + school + "的" + name + ".");

}

}

public class DotNetTeacher {

private String name; // 教师姓名

private String school; // 所在学校

public DotNetTeacher(String myName, String mySchool) {

name = myName;

school = mySchool;

}

public void giveLesson() {

System.out.println("启动 VS2010");

System.out.println("知识点讲解 ");

System.out.println("总结提问 ");

}

public void introduction() {

System.out.println("大家好!我是" + school + "的" + name + ".");

}

}

通过以上代码可以看到,JavaTeacher 类和 DotNetTeacher 类有很多相同的属性和方法,例如都有姓名、所在学校属性,都具有授课、上课功能。在实际开发中,一个系统中往往有很多类并且它们之间有很多相似之处,如果每个类都将这些相同的变量和方法定义一遍,不仅代码乱,工作量也很大。

在这个例子中,可以将 JavaTeacher 类和 DotNetTeacher 类的共同点抽取出来,形成一个 Teacher 类,代码如下:

public class Teacher {

private String name; // 教师姓名

private String school; // 所在学校

public Teacher(String myName, String mySchool) {

name = myName;

school = mySchool;

}

public void giveLesson() { // 授课方法的具体实现

System.out.println("知识点讲解");

System.out.println("总结提问");

}

public void introduction() { // 自我介绍方法的具体实现

System.out.println("大家好!我是" + school + "的" + name + "。");

}

}

然后让 JavaTeacher 类和 DotNetTeacher 类继承 Teacher 类,在 JavaTeacher 类和 DotNetTeacher 类中可以直接使用 Teacher 类中的属性和方法。

Java 中,子类继承父类的语法格式如下:

【修饰符】class 子类名 extends 父类名 {

// 子类的属性和方法的定义

};

修饰符:可选,用于指定类的访问权限,可选值 public、abstract 和 final。

class 子类名:必选,用于指定子类的名称。

extends 父类名:必选,用于指定要定义的子类继承于哪个父类。

使用继承实现以上代码【实例 1】:

class Teacher {

String name; // 教师姓名

String school; // 所在学校

public Teacher(String myName, String mySchool) {

name = myName;

school = mySchool;

}

public void giveLesson() { // 授课方法的具体实现

System.out.println(" 知识点讲解 ");

System.out.println(" 总结提问 ");

}

public void introduction() { // 自我介绍方法的具体实现

System.out.println("大家好!我是 " + school + " 的 " + name + "。");

}

}

class JavaTeacher extends Teacher {

public JavaTeacher(String myName, String mySchool) {

super(myName, mySchool);

}

public void giveLesson() {

System.out.println("启动 Eclipse");

super.giveLesson();

}

}

class DotNetTeacher extends Teacher {

public DotNetTeacher(String myName, String mySchool) {

super(myName, mySchool);

}

public void giveLesson() {

System.out.println("启动 VS2010");

super.giveLesson();

}

}

public class TestTeacher {

public static void main(String args[]) {

// 创建 javaTeacher 对象

JavaTeacher javaTeacher = new JavaTeacher("张三", "xxx大学");

javaTeacher.introduction();

javaTeacher.giveLesson();

System.out.println("\n");

// 创建 dotNetTeacher 对象

DotNetTeacher dotNetTeacher = new DotNetTeacher("李四", "xxx学院");

dotNetTeacher.introduction();

dotNetTeacher.giveLesson();

}

}

程序运行结果为:

大家好!我是 xxx大学 的 张三。

启动 Eclipse

知识点讲解

总结提问

大家好!我是 xxx学院 的 李四。

启动 VS2010

知识点讲解

总结提问

通过关键字 extends 分别创建父类 Teacher 的子类 JavaTeacher 和 DotNetTeacher。子类继承父类所有的成员变量和成员方法,但不能继承父类的构造方法。在子类的构造方法中可使用语句 super(参数列表) 调用父类的构造方法。

TestTeacher 的 main() 方法中声明两个子类对象。子类对象分别调用各自的方法进行授课和自我介绍。如语句 javaTeacher.giveLesson(),就调用 JavaTeacher 子类的方法实现授课的处理,该子类的方法来自对父类 Teacher 方法 giveLesson() 的继承,语句 super.giveLesson() 代表对父类同名方法的调用。

Java继承的使用原则

1) 方法覆盖

在继承关系中,子类从父类中继承可访问的方法。但有时从父类继承的方法不能完全满足子类需要,这时就需要在子类的方法里修改父类的方法,即子类重新定义从父类继承的成员方法,这个过程称为方法覆盖或重写。

在实例 1 中,父类 Teacher 中定义了 giveLesson() 方法,但是两个子类也各自定义了自己的 giveLesson() 方法。

在进行方法覆盖时,特别需要注意,子类在覆盖父类方法时应注意以下几点:

子类的方法不能缩小父类方法的访问权限;

父类的静态方法不能被子类覆盖为非静态方法;

父类的私有方法不能被子类覆盖;

父类的 final 不能被覆盖。

另外,需要注意方法重载与方法覆盖的区别:

第一,方法重载是在同一个类中,方法重写是在子类与父类中。

第二,方法重载要求方法名相同,参数列表不同;方法覆盖要求子类与父类的方法名、返回值和参数列表相同。

第三,方法重载解决了同一个类中,相同功能的方法名称不同的问题;方法覆盖解决子类继承父类之后,父类的某一个方法不满足子类的具体要求,此时需要重新在子类中定义该方法。

2) 成员变量覆盖

子类也可以覆盖继承的成员变量,只要子类中定义的成员变量和父类中的成员变量同名,子类就覆盖继承的成员变量。

总之,子类可以继承父类中所有可被子类访问的成员变量和成员方法,但必须遵循以下原则:

父类中声明为 public 和 protected 的成员变量和方法可以被子类继承,但声明为 private 的成员变量和方法不能被子类继承;

如果子类和父类位于同一个包中,则父类中由默认修饰符修饰的成员变量和方法可被子类继承;

子类不能继承父类中被覆盖的成员变量;

子类不能继承父类中被覆盖的成员方法。

【实例 2】定义一个动物类 Animal,包含两个成员变量 live 和 skin 以及两个成员方法 eat() 和 move();再定义 Animal 的子类 Bird,在该类中隐藏父类的成员变量 skin,覆盖父类的 move() 方法,并定义测试类进行测试。

class Animal {

public boolean live = true;

public String skin = "";

public void eat() {

System.out.println("动物需要吃食物");

}

public void move() {

System.out.println("动物会运动");

}

}

class Bird extends Animal {

public String skin = "羽毛";

public void move() {

System.out.println("鸟会飞翔");

}

}

public class Main {

public static void main(String[] args) {

Bird bird = new Bird();

bird.eat();

bird.move();

System.out.println("鸟有: " + bird.skin);

}

}

eat() 方法是从父类 Animal 继承下来的方法,move() 方法是 Bird 子类覆盖父类的成员方法,skin 变量为子类自己定义的成员变量。

程序运行结果为:

动物需要吃食物

鸟会飞翔

鸟有: 羽毛

Java继承的传递性

Java 语言虽然不支持多重继承,但支持多层继承,即一个类的父类可以继承另外的类,这称为类继承的传递性。

类的传递性对 Java 语言有重要的意义。如下代码演示了继承的传递性,定义了三个类 Vehicle、Trunk、SmallTruck,其中类 Trunk 继承 Vehicle,类 SmallTruck 继承 Trunk,并测试 SmallTruck 可以继承 Vehicle 的成员。

public class Vehicle {

void vehicleRun() {

System.out.println("汽车在行驶!");

}

}

public class Truck extends Vehicle { // 直接父类为 Vehicle

void truckRun() {

System.out.println("卡车在行驶!");

}

}

public class SmallTruck extends Truck { // 直接父类为 Truck

protected void smallTruckRun() {

System.out.println("微型卡车在行驶!");

}

public static void main(String[] args) {

SmallTruck smalltruck = new SmallTruck();

smalltruck.vehicleRun(); // 祖父类的方法调用

smalltruck.truckRun(); // 直接父类的方法调用

smalltruck.smallTruckRun(); // 子类自身的方法调用

}

}

程序中,SmallTruck 继承了 Truck,Truck 继承了 Vehicle,所以 SmallTruck 同时拥有 Truck 和 Vehicle 的所有可以被继承的成员。

从本例可以看出,Java 语言的继承关系既解决了代码复用的问题,又表示了一个体系,这是面向对象中继承真正的作用。运行该程序,执行结果为:

汽车在行驶!

卡车在行驶!

微型卡车在行驶!

Java super关键字

super 关键字主要用于在继承关系中实现子类对父类方法的调用,包括对父类构造方法和一般方法的调用。

1) 调用父类的构造方法

子类可以调用父类的构造方法,但是必须在子类的构造方法中使用super关键字调用,并且必须把super放在构造方法的第一个可执行语句。具体语法格式如下。

super([参数列表]);

如果父类的构造方法中包括参数,则参数列表为必选项,用于指定父类构造方法的入口参数。

例如,在实例 2 中的 Animal 类中添加一个默认的构造方法和一个带参数构造方法。

public Animal() {

}

public Animal(String skin) {

this.skin = skin;

}

这时,如果想在子类 Bird 中使用父类带参数的构造方法,则需要在 Bird 中的构造方法中通过以下代码实现:

public Bird() {

super("羽毛");

}

2) 访问被隐藏的成员变量和成员方法

如果想在子类中操作父类中被隐藏的成员变量和成员方法,也可以使用 super 关键字。

语法格式如下:

super.成员变量

super.成员方法([参数列表])

在实例 2 中,如果想在子类 Bird 中改变父类 Animal 的成员变量 skin 的值,可以使用如下代码:

super.skin = “羽毛”;

如果想在子类 Bird 中调用父类 Animal 中的 move() 方法,可以使用如下代码:

super.move();

Java在子类中调用父类构造方法

子类不能继承父类的构造方法。子类在创建新对象时,依次向上寻找其基类,直到找到最初的基类,然后开始执行最初基类的构造方法,再依次向下执行派生类的构造方法,直至执行完最终的扩充类的构造方法为止。

如果子类中没有显式地调用父类的构造方法,那么将自动调用父类中不带参数的构造方法,编译器不再自动生成默认构造方法。如果不在子类构造方法中调用父类带参构造方法,则编译器会因为找不到无参构造方法而报错。

为了解决以上错误,可以在子类显示地调用父类中定义的构造方法,也可以在父类中显示定义无参构造方法。

下面通过一个实例分析怎样在子类中调用父类构造方法。在程序中声明父类 Employee 和子类 CommonEmployee。子类继承父类的非私有属性和方法,但父子类计算各自的工资的方法不同,如父类对象直接获取工资,而子类在底薪的基础上增加奖金数为工资总额。通过子类构造方法中 super 调用类初始化父类的对象,并调用继承父类的方法 toString() 输出员工的基本信息。

class Employee { // 定义父类:雇员类

private String employeeName; // 姓名

private double employeeSalary; // 工资总额

static double mini_salary = 600; // 员工的最低工资

public Employee(String name) { // 有参构造方法

employeeName = name;

System.out.println("父类构造方法的调用。");

}

public double getEmployeeSalary() { // 获取雇员工资

return employeeSalary;

}

public void setEmployeeSalary(double salary) { // 计算员工的薪水

employeeSalary = salary + mini_salary;

}

public String toString() { // 输出员工的基本信息

return ("姓名:" + employeeName + ":工资:" + employeeSalary);

}

}

class CommonEmployee extends Employee { // 定义子类:一般员工类

private double bonus; // 奖金,新的数据成员

public CommonEmployee(String name, double bonus) {

super(name); // 通过 super() 的调用,给父类的数据成员赋初值

this.bonus = bonus; // this 指当前对象

System.out.println("子类构造方法的调用。");

}

public void setBonus(double newBonus) { // 新增的方法,设置一般员工的薪水

bonus = newBonus;

}

// 来自父类的继承,但在子类中重新覆盖父类方法,用于修改一般员工的薪水

public double getEmployeeSalary() {

return bonus + mini_salary;

}

public String toString() {

String s;

s = super.toString(); // 调用父类的同名方法 toString()

// 调用自身对象的方法 getEmployeeSalary(),覆盖父类同名的该方法

return (s + getEmployeeSalary() + "");

}

}

public class TestConstructor { // 主控程序

public static void main(String args[]) {

Employee employee = new Employee("李平"); // 创建员工的一个对象

employee.setEmployeeSalary(1200);

// 输出员工的基本信息

System.out.println("员工的基本信息为:" + employee.toString() + employee.getEmployeeSalary());

// 创建子类一般员工的一个对象

CommonEmployee commonEmployee = new CommonEmployee("李晓云", 500);

// 输出子类一般员工的基本信息

System.out.println("员工的基本信息为:" + commonEmployee.toString());

}

}

程序的运行结果为:

父类构造方法的调用。

员工的基本信息为:姓名:李平:工资:1800.01800.0

父类构造方法的调用。

子类构造方法的调用。

员工的基本信息为:姓名:李晓云:工资:0.01100.0

程序中,在创建子类 CommonEmployee 对象时,父类的构造方法首先被调用,接下来才是子类构造方法的调用;子类对象创建时,为构建父类对象,就必须使用 super() 将子类的实参传递给父类的构造方法,为父类对象赋初值。

关于子类构造方法的使用总结如下:

构造方法不能继承,它们只属于定义它们的类;

创建一个子类对象时,先顺着继承的层次关系向上回溯到最顶层的类,然后向下依次调用每个类的构造方法,最后才执行子类构造方法。

相关阅读

孙俪新剧热播!江门又双叒出镜了
bt365手机投注

孙俪新剧热播!江门又双叒出镜了

🕒 08-08 👁️‍🗨️ 7022
尼日利亞國家足球隊
beat365手机安卓版

尼日利亞國家足球隊

🕒 08-02 👁️‍🗨️ 1110
任心是什么字(任心读什么字)
beat365手机安卓版

任心是什么字(任心读什么字)

🕒 09-07 👁️‍🗨️ 5521
为什么说美不美看大腿,美腿的标准是什么,怎样的腿才算完美?
beat365手机安卓版

为什么说美不美看大腿,美腿的标准是什么,怎样的腿才算完美?

🕒 06-29 👁️‍🗨️ 338
Intel Core i5-3470vsIntel Core i5-4460
bt365手机投注

Intel Core i5-3470vsIntel Core i5-4460

🕒 08-21 👁️‍🗨️ 4724
新3ds肿么完全关机
365bet在线娱乐

新3ds肿么完全关机

🕒 09-06 👁️‍🗨️ 2729
世界杯揭秘:尼日利亚为什么能够战胜冰岛
beat365手机安卓版

世界杯揭秘:尼日利亚为什么能够战胜冰岛

🕒 06-27 👁️‍🗨️ 8425
蕲艾的解释和发音 「欧路词典」英汉-汉英词典 为您提供权威的英语单词解释
51信用卡贷款怎么样,靠谱吗?
365bet在线娱乐

51信用卡贷款怎么样,靠谱吗?

🕒 08-17 👁️‍🗨️ 7519