我是靠谱客的博主 想人陪鼠标,最近开发中收集的这篇文章主要介绍Java基础(10)继承与多态,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

类的继承性是面向对象语言的基本特性,多态性的前提是继承性。Java支持继承性和多态性。

1.Java中的继承

为了了解继承性,先看这样一个场景:一位面向对象的程序员小赵,在编程过程中需要描述和处理个人信息,于是定义了类Person,如下所示:

//Person.java文件

package com.fumumu;

import java.util.Date;

public class Person {

    // 名字

    private String name;

    // 年龄

    private int age;

    // 出生日期

    private Date birthDate;

    public String getInfo() {

        return "Person [name=" + name

                + ", age=" + age

                + ", birthDate=" + birthDate + "]";

    }

}

一周以后,小赵又遇到了新的需求,需要描述和处理学生信息,于是他又定义了一个新的类Student,如下所示:

//Student.java文件

package com.fumumu;

import java.util.Date;

public class Student {

    // 所在学校

    public String school;

    // 名字

    private String name;

    // 年龄

    private int age;

    // 出生日期

    private Date birthDate;

    public String getInfo() {

        return "Person [name=" + name

                + ", age=" + age

                + ", birthDate=" + birthDate + "]";

    }

}

很多人会认为小赵的做法能够理解并相信这是可行的,但问题在于Student和Person两个类的结构太接近了,后者只比前者多了一个属性school,却要重复定义其他所有的内容,实在让人“不甘心”。Java提供了解决类似问题的机制,那就是类的继承,代码如下所示:

//Student.java文件

package com.fumumu;

import java.util.Date;

public class Student extends Person {

    // 所在学校

    private String school;

}

student类继承了Person类中的所有成员变量和方法,从上述代码可以见继承使用的关键字是extends,extends后面的Person是父类。

如果在类的声明中没有使用extends关键字指明其父类,则默认父类为Object类,java.lang.Object类是Java的根类,所有Java类包括数组都直接或间接继承了Object类,在Object类中定义了一些有关面向对象机制的基本方法,如equals()、toString()和finalize()等方法。

提示:一般情况下,一个子类只能继承一个父类,这称为“单继承”,但有的情况下一个子类可以有多个不同的父类,这称为“多重继承”。在Java中,类的继承只能是单继承,而多重继承可以通过实现多个接口实现。也就是说,在Java中,一个类只能继承一个父类,但是可以实现多个接口。

 

提示:面向对象分析与设计(OOAD)时,会用到UML图1,其中类图非常重要,用来描述系统静态结构。类图中的各个元素说明,类用矩形表示,一般分为上、中、下三个部分,上部分是类名,中部分是成员变量,下部分是成员方法。实线+空心箭头表示继承关系,箭头指向父类,箭头末端是子类。UML类图中还有很多关系,虚线+空心箭头表示实线关系,箭头指向接口,箭头末端是实现类。

Student继承Person的类图:

类图中元素:

元素之间关系:

2.调用父类构造方法

当子类实例化时,不仅需要初始化子类成员变量,也需要初始化父类成员变量,初始化父类成员变量需要调用父类构造方法,子类使用super关键字调用父类构造方法。

下面看一个示例,现有父类Person和子类Student,它们类图如图所示。

Person和Student类图:

父类Person代码如下:

//Person.java文件

package com.fumumu;

import java.util.Date;

public class Person {

    // 名字

    private String name;

    // 年龄

    private int age;

    // 出生日期

    private Date birthDate;

    // 三个参数构造方法

    public Person(String name, int age, Date d) {

        this.name = name;

        this.age = age;

        birthDate = d;

    }

    public Person(String name, int age) {

        // 调用三个参数构造方法

        this(name, age, new Date());

    }

    ...

}

子类Student代码如下:

//Student.java文件

package com.fumumu;

import java.util.Date;

public class Student extends Person {

    // 所在学校

    private String school;

    public Student(String name, int age, Date d, String school) {

        super(name, age, d);                                        ①

        this.school = school;

    }

    public Student(String name, int age, String school) {

        // this.school = school;//编译错误

        super(name, age);                                           ②

        this.school = school;

    }

    public Student(String name, String school) { // 编译错误        ③

        // super(name, 30);

        this.school = school;

    }

}

在Student子类代码第①行和第②行是调用父类构造方法,代码第①行super(name, age, d)语句是调用父类的Person(String name, int age, Date d)构造方法,代码第②行super(name, age)语句是调用父类的Person(String name, int age)构造方法。

提示:super语句必须位于子类构造方法的第一行。

代码第③行构造方法由于没有super语句,编译器会试图调用父类默认构造方法(无参数构造方法),但是父类Person并没有默认构造方法,因此会发生编译错误。解决这个编译错误有三种办法:

在父类Person中添加默认构造方法,子类Student会隐式调用父类的默认构造方法。

在子类Studen构造方法添加super语句,显式调用父类构造方法,super语句必须是第一条语句。

在子类Studen构造方法添加this语句,显式调用当前对象其他构造方法,this语句必须是第一条语句。

3.成员变量隐藏和方法覆盖

继承父类后,在子类中有可能声明了与父类一样的成员变量或方法,那么会出现什么情况呢?

3.1 成员变量隐藏

子类成员变量与父类一样,会屏蔽父类中的成员变量,称为“成员变量隐藏”。示例代码如下:

//ParentClass.java文件

package com.fumumu;

class ParentClass {

    // x成员变量

    int x = 10;                                        ①

}

class SubClass extends ParentClass {

    // 屏蔽父类x成员变量

    int x = 20;                                        ②

    public void print() {

        // 访问子类对象x成员变量

        System.out.println("x = " + x);                ③

        // 访问父类x成员变量

        System.out.println("super.x = " + super.x);    ④

    }

}

调用代码如下:

//HelloWorld.java文件

package com.fumumu;

public class HelloWorld {

    public static void main(String[] args) {

        //实例化子类SubClass

        SubClass pObj = new SubClass();

        //调用子类print方法

        pObj.print();

    }

}

运行结果如下:

x = 20

super.x = 10

上述代码第①行是在ParentClass类声明x成员变量,那么在它的子类SubClass代码第②行也声明了x成员变量,它会屏蔽父类中的x成员变量。那么代码第③行的x是子类中的x成员变量。如果要调用父类中的x成员变量,则需要super关键字,见代码第④行的super.x。

3.2  方法的覆盖(Override

如果子类方法完全与父类方法相同,即:相同的方法名、相同的参数列表和相同的返回值,只是方法体不同,这称为子类覆盖(Override)父类方法。

示例代码如下:

//ParentClass.java文件

package com.fumumu;

class ParentClass {

    // x成员变量

    int x;

    protected void setValue() {                  ①

        x = 10;

    }

}

class SubClass extends ParentClass {

    // 屏蔽父类x成员变量

    int x;

    @Override

    public void setValue() { // 覆盖父类方法      ②

        // 访问子类对象x成员变量

        x = 20;

        // 调用父类setValue()方法

        super.setValue();

    }

    public void print() {

        // 访问子类对象x成员变量

        System.out.println("x = " + x);

        // 访问父类x成员变量

        System.out.println("super.x = " + super.x);

    }

}

调用代码如下:

//HelloWorld.java文件

package com.fumumu;

public class HelloWorld {

    public static void main(String[] args) {

        //实例化子类SubClass

        SubClass pObj = new SubClass();

        //调用setValue方法

        pObj.setValue();

        //调用子类print方法

        pObj.print();

    }

}

运行结果如下:

x = 20

super.x = 10

上述代码第①行是在ParentClass类声明setValue方法,那么在它的子类SubClass代码第②行覆盖父类中的setValue方法,在声明方法时添加@Override注解,@Override注解不是方法覆盖必须的,它只是锦上添花,但添加@Override注解有两个好处:

提高程序的可读性。

编译器检查@Override注解的方法在父类中是否存在,如果不存在则报错。

注意:方法覆盖时应遵循的原则:

覆盖后的方法不能比原方法有更严格的访问控制(可以相同)。例如将代码第②行访问控制public修改private,那么会发生编译错误,因为父类原方法是protected。

覆盖后的方法不能比原方法产生更多的异常。

4.多态

在面向对象程序设计中多态是一个非常重要的特性,理解多态有利于进行面向对象的分析与设计。

4.1 多态概念

发生多态要有三个前提条件:

继承。多态发生一定要子类和父类之间。

覆盖。子类覆盖了父类的方法。

声明的变量类型是父类类型,但实例则指向子类实例。

下面通过一个示例理解什么多态。

几何图形类图:

具体代码如下:

//Figure.java文件

package com.fumumu;

public class Figure {

    //绘制几何图形方法

    public void onDraw() {

        System.out.println("绘制Figure...");

    }

}

//Ellipse.java文件

package com.fumumu;

//几何图形椭圆形

public class Ellipse extends Figure {

    //绘制几何图形方法

    @Override

    public void onDraw() {

        System.out.println("绘制椭圆形...");

    }

}

//Triangle.java文件

package com.fumumu;

//几何图形三角形

public class Triangle extends Figure {

    // 绘制几何图形方法

    @Override

    public void onDraw() {

        System.out.println("绘制三角形...");

    }

}

调用代码如下:

//HelloWorld.java文件

package com.fumumu;

public class HelloWorld {

    public static void main(String[] args) {

        // f1变量是父类类型,指向父类实例

        Figure f1 = new Figure();                        ①

        f1.onDraw();

        //f2变量是父类类型,指向子类实例,发生多态

        Figure f2 = new Triangle();                      ②

        f2.onDraw();

        //f3变量是父类类型,指向子类实例,发生多态

        Figure f3 = new Ellipse();                       ③

        f3.onDraw();

        //f4变量是子类类型,指向子类实例

        Triangle f4 = new Triangle();                    ④

        f4.onDraw();

    }

}

上述带代码第②行和第③行是符合多态的三个前提,因此会发生多态。而代码第①行和第④行都不符合,没有发生多态。

运行结果如下:

绘制Figure...

绘制三角形...

绘制椭圆形...

绘制三角形...

从运行结果可知,多态发生时,Java虚拟机运行时根据引用变量指向的实例调用它的方法,而不是根据引用变量的类型调用。

4.2 引用类型检查

有时候需要在运行时判断一个对象是否属于某个引用类型,这时可以使用instanceof运算符,instanceof运算符语法格式如下:

obj instanceof type

其中obj是一个对象,type是引用类型,如果obj对象是type引用类型实例则返回true,否则false。

为了介绍引用类型检查,先看一个示例,如图13-6所示的类图,展示了继承层次树,Person类是根类,Student是Person的直接子类,Worker是Person的直接子类。

继承关系类图:

实现代码如下:

//Person.java文件

package com.fumumu;

public class Person {

    String name;

    int age;

ring() {

        return "Worker [factory=" + factory

                + ", name=" + name

                + ", age=" + age + "]";

    }

}

//Student.java文件

package com.fumumu;

public class Student extends Person {

    String school;

    public Student(String name, int age, String school) {

        super(name, age);

        this.school = school;

    }

    @Override

    public String toString() {

        return "Student [school=" + school

                + ", name=" + name

                + ", age=" + age + "]";

    }

}

调用代码如下:

//HelloWorld.java文件

package com.fumumu;

public class HelloWorld {

    public static void main(String[] args) {

        Student student1 = new Student("Tom", 18, "清华大学");         ①

        Student student2 = new Student("Ben", 28, "北京大学");

        Student student3 = new Student("Tony", 38, "香港大学");        ②

        Worker worker1 = new Worker("Tom", 18, "钢厂");                ③

        Worker worker2 = new Worker("Ben", 20, "电厂");                ④

        Person[] people = { student1, student2, student3, worker1, worker2 };    ⑤

        int studentCount = 0;

        int workerCount = 0;

        for (Person item : people) {                 ⑥

            if (item instanceof Worker) {            ⑦

                workerCount++;

            } else if (item instanceof Student) {    ⑧

                studentCount++;

            }

        }

        System.out.printf("工人人数:%d,学生人数:%d", workerCount, studentCount);

    }

}

上述代码第①行和第②行创建了3个Student实例,代码第③行和第④行创建了两个Worker实例,然后程序把这5个实例放入people数组中。

代码第⑥行使用for-each遍历people数组集合,当从people数组中取出元素时,元素类型是People类型,但是实例不知道是哪个子类(Student和Worker)实例。代码第⑦行item instanceof Worker表达式是判断数组中的元素是否是Worker实例;类似地,第⑧行item instanceof Student表达式是判断数组中的元素是否是Student实例。

输出结果如下:

工人人数:2,学生人数:3

4.3 引用类型转换

引用类型可以进行转换,但并不是所有的引用类型都能互相转换,只有属于同一棵继承层次树中的引用类型才可以转换。

代码示例如下:

//HelloWorld.java文件

package com.fumumu;

public class HelloWorld {

    public static void main(String[] args) {

        Person p1 = new Student("Tom", 18, "清华大学");

        Person p2 = new Worker("Tom", 18, "钢厂");

        Person p3 = new Person("Tom", 28);

        Student p4 = new Student("Ben", 40, "清华大学");

        Worker p5 = new Worker("Tony", 28, "钢厂");

        …

    }

}

上述代码创建了5个实例p1、p2、p3、p4和p5,它们的类型都是Person继承层次树中的引用类型,p1和p4是Student实例,p2和p5是Worker实例,p3是Person实例。首先,对象类型转换一定发生在继承的前提下,p1和p2都声明为Person类型,而实例是由Person子类型实例化的。

类型转换:

代码示例如下:

// 向上转型

Person p = (Person) p4;            ①

// 向下转型

Student p11 = (Student) p1;        ②

Worker p12 = (Worker) p2;          ③

// Student p111 = (Student) p2;    //运行时异常    ④

if (p2 instanceof Student) {

    Student p111 = (Student) p2;

}

// Worker p121 = (Worker) p1;    //运行时异常      ⑤

if (p1 instanceof Worker) {

    Worker p121 = (Worker) p1;

}

// Student p131 = (Student) p3;    //运行时异常    ⑥

if (p3 instanceof Student) {

    Student p131 = (Student) p3;

}

上述代码第①行将p4对象转换为Person类型,p4本质上是Student实例,这是向上转型,这种转换是自动的,其实不需要小括号(Person)进行强制类型转换。

代码第②行和第③行是向下类型转换,它们的转型都能成功。而代码第④、⑤、⑥行都会发生运行时异常ClassCastException,如果不能确定实例是哪一种类型,可以在转型之前使用instanceof运算符判断一下。

5.final关键字

为了声明常量常常使用final关键字,在Java中final关键字的作用还有很多,final关键字能修饰变量、方法和类。

5.1 final修饰变量

final修饰的变量即成为常量,只能赋值一次,但是final所修饰局部变量和成员变量有所不同。

final修饰的局部变量必须使用之前被赋值一次才能使用。

final修饰的成员变量在声明时没有赋值的叫“空白final变量”。空白final变量必须在构造方法或静态代码块中初始化。

final修饰变量示例代码如下:

//FinalDemo.java文件

package com.fumumu;

class FinalDemo {

    void doSomething() {

        // 没有在声明的同时赋值

        final int e;                        ①

        // 只能赋值一次

        e = 100;                            ②

        System.out.print(e);

        // 声明的同时赋值

        final int f = 200;                  ③

    }

    //实例常量

    final int a = 5; // 直接赋值            ④

    final int b; // 空白final变量           ⑤

    //静态常量

    final static int c = 12;// 直接赋值     ⑥

    final static int d; // 空白final变量    ⑦

    // 静态代码块

    static {

        // 初始化静态变量

        d = 32;                             ⑧

    }

    // 构造方法

    FinalDemo() {

        // 初始化实例变量

        b = 3;                              ⑨

        // 第二次赋值,会发生编译错误

        // b = 4;                           ⑩

    }

}

上述代码第①行和第③行是声明局部常量,其中第①行只是声明没有赋值,但必须在使用之前赋值(见代码第②行),其实局部常量最好在声明的同时初始化。

代码第④、⑤、⑥和⑦行都声明成员常量。代码第④和⑤行是实例常量,如果是空白final变量(见代码第⑤行),则需要在构造方法中初始化(见代码第⑨行)。代码第⑥和⑦行是静态常量,如果是空白final变量(见代码第⑦行),则需要在静态代码块中初始化(见代码第⑧行)。

另外,无论是那种常量只能赋值一次,见代码第⑩行为b常量赋值,因为之前b已经赋值过一次,因此这里会发生编译错误。

5.2 final修饰类

final修饰的类不能被继承。有时出于设计安全的目的,不想让自己编写的类被别人继承,这时可以使用final关键字修饰父类。

示例代码如下:

//SuperClass.java文件

package com.fumumu;

final class SuperClass {

}

class SubClass extends SuperClass { //编译错误

}

在声明SubClass类时会发生编译错误。

5.3 final修饰方法

final修饰的方法不能被子类覆盖。有时也是出于设计安全的目的,父类中的方法不想被别人覆盖,这是可以使用final关键字修饰父类中方法。

示例代码如下:

//SuperClass.java文件

package com.fumumu;

class SuperClass {

    final void doSomething() {

        System.out.println("in SuperClass.doSomething()");

    }

}

class SubClass extends SuperClass {

    @Override

    void doSomething() { //编译错误

        System.out.println("in SubClass.doSomething()");

    }

}

子类中的void doSomething()方法试图覆盖父类中void doSomething()方法,父类中的void doSomething()方法是final的,因此会发生编译错误。

最后

以上就是想人陪鼠标为你收集整理的Java基础(10)继承与多态的全部内容,希望文章能够帮你解决Java基础(10)继承与多态所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(34)

评论列表共有 0 条评论

立即
投稿
返回
顶部