首页 > 编程笔记 > Java笔记 阅读:63

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

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

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


图 1 不同车之间的关系

巴士、卡车和出租车都是汽车的一种,分别拥有相似的特性,如对所有的交通工具而言都具备引擎数量、外观的颜色,相似的行为如刹车和加速的功能。但每种不同的交通工具又有自己的特性,例如:
面向对象的程序设计中该怎样描述现实世界中的这种情况呢?这就要用到继承的概念。继承就是从已有的类派生出新的类。新的类能吸收已有类的数据属性和行为,并能扩展新的能力。

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


图 2 继承关系

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

继承的方式包括单一继承和多重继承:
两种继承方式如下图所示:


图 3 继承的方式

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

通过上面介绍可以看出基类与派生类的关系:
注意,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 父类名 {
    // 子类的属性和方法的定义
};

使用继承实现以上代码【实例 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() 方法。

在进行方法覆盖时,特别需要注意,子类在覆盖父类方法时应注意以下几点:
另外,需要注意方法重载与方法覆盖的区别:

2) 成员变量覆盖

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

总之,子类可以继承父类中所有可被子类访问的成员变量和成员方法,但必须遵循以下原则:
【实例 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() 将子类的实参传递给父类的构造方法,为父类对象赋初值。

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

相关文章