Java讲课笔记13:类的继承

文章目录

零、本讲学习目标

1、理解继承的概念

2、掌握方法的重写

3、掌握super关键字

4、熟悉Object类

  • 思考问题:我们为什么要引入类的继承(inheritance)?
  • 简而言之:利用继承这个机制,可以提高代码的可重用性(reusability)。

有了继承机制,就可区分父类和子类,被继承的那个类叫做父类(超类或基类),继承的那个类叫做子类(派生类)。子类可以继承父类所有的非私有属性与方法,还可以继续添加新的属性和方法,正如俗话所说的:青出于蓝而胜于蓝。

一、类的继承

1、继承示意图

在这里插入图片描述
如上图所示,狗、猫和鸟都属于动物,程序中便可以描述为狗、猫和鸟继承自动物;同理,哈士奇和金毛犬继承自狗,波斯猫和折耳猫继承自猫,而孔雀和乌鸦继承自鸟。

2、继承的定义

在Java中,类的继承是指在一个现有类的基础上去构建一个新的类,构建出来的新类被称作子类,现有类被称作父类或基类,子类会自动拥有父类所有可继承的属性和方法。

3、继承的语法格式

如果想声明一个类继承另一个类,需要使用extends关键字。

[修饰符] class 子类名 extends 父类名 {
    // 程序核心代码
}

4、继承案例演示

《Java讲课笔记10:类的封装》里,我们已经创建了Cat、Dog和Bird类,它们有共同点,都是动物,于是我们可以创建一个动物类Animal,然后让Cat、Dog和Bird都来继承Animal类。这样做有什么好处呢?比如,在Student类,有三个重载的feed方法,分别是feed(Cat cat)、feed(Dog dog)与feed(Bird bird),因为它们都是Animal类的子类,所以feed方法可以变得更抽象,feed(Animal animal),这样重载就没有了,但是会出现多态现象。什么叫多态?简而言之,同样的方法,同样的参数,但是作用在不同的对象上,结果有不同的呈现。

(1)创建动物类Animal

在这里插入图片描述

package net.hw.lesson13;

/**
 * 功能:动物类
 * 作者:华卫
 * 日期:2020年05月03日
 */
public class Animal {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void speak() {
        System.out.println(name + "今年" + age + "岁了。");
    }

    public void move() {
        System.out.println(name + "在动……");
    }

    public void eat() {
        System.out.println(name + "在吃……");
    }
}

(2)继承Animal类,创建Cat子类

在这里插入图片描述

package net.hw.lesson13;

/**
 * 功能:继承动物类,创建猫类
 * 作者:华卫
 * 日期:2020年05月03日
 */
public class Cat extends Animal {
    /**
     * 子类添加新的方法
     */
    public void play() {
        System.out.println(getName() + "在玩老鼠。");
    }
}

Cat子类继承了Animal类全部非私有成员:getName()、setName()、getAge()、setAge()、move()、eat()和speak(),然后我们还添加了一个新的方法play()。

(3)继承Animal类,创建Dog子类

在这里插入图片描述

package net.hw.lesson13;

/**
 * 功能:继承动物类,创建狗类
 * 作者:华卫
 * 日期:2020年05月03日
 */
public class Dog extends Animal {
    /**
     * 子类添加新的方法
     */
    public void play() {
        System.out.println(getName()+ "在玩飞盘。");
    }
}

Dog子类继承了Animal类全部非私有成员:getName()、setName()、getAge()、setAge()、move()、eat()和speak(),然后我们还添加了一个新的方法play()。

(4)继承Animal类,创建Bird子类

在这里插入图片描述

package net.hw.lesson13;

/**
 * 功能:继承动物类,创建猫类
 * 作者:华卫
 * 日期:2020年05月03日
 */
public class Cat extends Animal {
    /**
     * 子类添加新的方法
     */
    public void play() {
        System.out.println(getName()+ "在玩老鼠。");
    }
}

Bird子类继承了Animal类全部非私有成员:getName()、setName()、getAge()、setAge()、move()、eat()和speak(),然后我们还添加了一个新的方法play()。

(5)创建测试类TestDog

在这里插入图片描述

package net.hw.lesson13;

/**
 * 功能:测试狗类
 * 作者:华卫
 * 日期:2020年05月03日
 */
public class TestDog {
    public static void main(String[] args) {
        // 实例化狗类对象
        Dog dog = new Dog();
        // 设置对象属性
        dog.setName("瑞瑞");
        dog.setAge(1);
        // 调用对象方法(继承父类的方法)
        dog.speak();
        dog.move();
        dog.eat();
        // 调用对象方法(父类没有的方法)
        dog.play();
    }
}

在这里插入图片描述

(6)课堂练习:创建测试类TestCat与TestBird

在这里插入图片描述
在这里插入图片描述

5、Java类继承注意事项

(1)单根继承——不能一子多父

在Java中,类只支持单继承,不允许多重继承,也就是说一个类只能有一个直接父类。

class A {
    ……
} 
class B {
    ……
}
// C类不可以同时继承A类和B类
class C extends A, B {
    ……
}  

Java不允许一子多父,而C++是允许的,因此Java在这一点上比C++简单。

(2)多子继承——允许一父多子

在Java中,多个类可以继承同一个父类。

class A {
    ……
} 
class B extends A {
    ……
}
class C extends A {
    ……
}  

B类和C类都继承了A类,也就是说,A类现在有两个子类:B类与C类。

(3)多层继承——好比几代单传

在Java中,多层继承是可以的,即一个类的父类可以再去继承另外的父类。(有点像我们在实际生活里说的几代单传。)

class A {
    ……
} 
class B extends A {
    ……
}
class C extends B {
    ……
}  
  • B类继承A类,B类是A类的子类。
  • C类继承B类,C类是B类的子类,当然也是A类的子类。
  • 可以这样来理解:A——父亲、B——儿子、C——孙子

在这里插入图片描述

package net.hw.lesson13;

/**
 * 功能:演示多层继承
 * 作者:华卫
 * 日期:2020年05月03日
 */

class A {
}

class B extends A {
}

class C extends B {
}

public class Example1301 {
    public static void main(String[] args) {
        C c = new C();
        System.out.println("c instance of C: " + (c instanceof C));
        System.out.println("c instance of B: " + (c instanceof B));
        System.out.println("c instance of A: " + (c instanceof A));
    }
}

运行程序,查看结果:
在这里插入图片描述
说明:对象c是C类的实例,也是B类的实例,甚至还是A类的实例。

二、方法的重写——子类重写父类方法

1、方法重写的定义

在继承关系中,子类会自动继承父类中公共的方法,但有时在子类中需要对继承的方法进行一些修改,即对父类的方法进行重写。

2、方法重写的案例演示

(1)修改Dog类,重写move()与eat()方法

package net.hw.lesson13;

/**
 * 功能:继承动物类,创建狗类
 * 作者:华卫
 * 日期:2020年05月03日
 */
public class Dog extends Animal {
    /**
     * 子类改写父类的同名同参方法(多态)
     */
    @Override
    public void move() {
        System.out.println(getName() + "在走路。");
    }

    /**
     * 子类改写父类的同名同参方法(多态)
     */
    @Override
    public void eat() {
        System.out.println(getName() + "爱吃骨头。");
    }

    /**
     * 子类添加新的方法
     */
    public void play() {
        System.out.println(getName() + "在玩飞盘。");
    }
}

(2)查看并运行TestDog

  • 查看TestDog
package net.hw.lesson13;

/**
 * 功能:测试狗类
 * 作者:华卫
 * 日期:2020年05月03日
 */
public class TestDog {
    public static void main(String[] args) {
        // 实例化狗类对象
        Dog dog = new Dog();
        // 设置对象属性
        dog.setName("瑞瑞");
        dog.setAge(1);
        // 调用对象方法(继承父类的方法)
        dog.speak();
        dog.move();
        dog.eat();
        // 调用对象方法(父类没有的方法)
        dog.play();
    }
}
  • 运行程序,查看结果
    在这里插入图片描述
  • 对比子类没有重写父类方法之前的运行结果
    在这里插入图片描述
    从运行结果可以看出,在调用Dog类对象的move()与eat()方法时,只会调用子类重写的方法,而不会调用父类Animal类的move()与eat()方法。

3、方法重写的课堂练习

修改Cat类和Bird类,重写move()与eat()方法,然后运行TestCat与TestBird。
在这里插入图片描述
在这里插入图片描述

4、方法重写的注意事项

(1)方法签名要相同

子类中重写的方法需要和父类被重写的方法具有相同的方法名、参数列表以及返回值类型。

(2)访问权限不能缩小

子类重写父类方法时,不能使用比父类中被重写的方法更严格的访问权限。

  • 访问权限顺序:public > protected > default > private
    在这里插入图片描述
  • 子类重写方法的访问权限 \ge 父类被重写方法的访问权限

5、案例演示方法重写的访问权限

在这里插入图片描述

  • 如果Person类的move()方法访问权限是private,那么子类Man类的move()方法访问权限可以是private、default、protected和public。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 如果Person类的move()方法访问权限是default,那么子类Man类的move()方法访问权限可以是default、protected和public,但是不能为private。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 如果Person类的move()方法访问权限是protected,那么子类Man类的move()方法访问权限可以是protected和public,但是不能为private或default。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 如果Person类的move()方法访问权限是public,那么子类Man类的move()方法访问权限就只能是public,但是不能为private、default或protected。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

三、父类实例——super关键字

1、引入super关键字的场景

  • 问题: 在继承关系中,当子类重写父类的方法后,子类对象将无法直接访问父类被重写的方法。

  • 解决方法: 在Java中专门提供了一个super关键字来访问父类的成员,例如访问父类的成员变量、成员方法和构造方法。

2、super关键字的使用方法

(1)使用super关键字调用父类的成员变量和成员方法

super.成员变量
super.成员方法([参数1, 参数2...])

(2)使用super关键字调用父类的构造方法

super([参数1, 参数2 ...])

3、案例演示super关键字的使用

(1)使用super关键字调用父类成员变量和成员方法

  • 创建Example1303类
    在这里插入图片描述
package net.hw.lesson13;

/**
 * 功能:测试super用法
 *      调用成员变量与方法
 * 作者:华卫
 * 日期:2020年05月03日
 */

// 定义Flower类
class Flower {
    String name = "鲜花";
    public void bloom() {
        System.out.println("鲜花绽放。");
    }
}

// 定义Jasmine类继承Flower类
class Jasmine extends Flower {
    String name = "茉莉花";

    @Override
    public void bloom() {
        super.bloom();  // 调用父类成员方法
    }

    public void printSuperName() {
        System.out.println("父类name属性 = " + super.name); // 调用父类成员变量
    }

    public void printThisName() {
        System.out.println("子类name属性 = " + this.name); // this可以省略
    }
}

// 定义测试类
public class Example1303 {
    public static void main(String[] args) {
        Jasmine jasmine = new Jasmine();
        jasmine.bloom();
        jasmine.printSuperName();
        jasmine.printThisName();
    }
}

  • 运行程序,查看结果
    在这里插入图片描述
  • 简单说明:定义了一个Jasmine类继承Flower类,重写了Flower类的bloom()方法,并且重新定义了子类的name属性。在子类Jasmine的bloom()方法里使用“super.bloom()”调用了父类被重写的方法,在PrintSuperName()方法里访问了父类的成员变量name。从运行结果可以看出,子类通过super关键字成功地访问了父类成员变量和成员方法。

(2)使用super关键字访问父类的构造方法

  • 创建Example1304类
    在这里插入图片描述
package net.hw.lesson13;

/**
 * 功能:测试super关键字
 *      调用父类构造方法
 * 作者:华卫
 * 日期:2020年05月03日
 */

class Vehicle {
    // 定义有参构造方法
    public Vehicle(String name) {
        System.out.println("这是一辆" + name);
    }
}

class Car extends Vehicle {
    public Car(String name) {
        super(name); // 调用父类有参构造方法
        System.out.println("实例化了轿车对象~");
    }
}

public class Example1304 {
    public static void main(String[] args) {
        Car car = new Car("奔驰轿车");
    }
}
  • 运行程序,查看结果
    在这里插入图片描述
  • 简单说明:创建Car对象时,调用Car类的构造方法,执行内部的super(name)方法,从而调用了父类的构造方法。需要注意的是,通过super调用父类构造方法的代码必须位于子类构造方法的第一行,并且只能出现一次,否则程序在编译期间就会报错。
    在这里插入图片描述
    在这里插入图片描述
  • 给Car类添加一个无参构造方法,编译系统会报错
    在这里插入图片描述
  • 给父类Vehicle添加无参构造方法,在Example1304里调用无参构造方法实例化Car对象
package net.hw.lesson13;

/**
 * 功能:测试super关键字
 *      调用父类构造方法
 * 作者:华卫
 * 日期:2020年05月03日
 */

class Vehicle {
    // 定义无参构造方法
    public Vehicle() {
        System.out.println("这是一辆车");
    }
    // 定义有参构造方法
    public Vehicle(String name) {
        System.out.println("这是一辆" + name);
    }
}

class Car extends Vehicle {
    public Car() {
        super(); // 调用父类无参构造方法(可以省略)
        System.out.println("实例化了轿车对象~");
    }

    public Car(String name) {
        super(name); // 调用父类有参构造方法
        System.out.println("实例化了轿车对象~");
    }
}

public class Example1304 {
    public static void main(String[] args) {
        Car car = new Car();        
    }
}
  • 运行程序,查看结果
    在这里插入图片描述
  • 简单说明:利用子类无参构造方法实例化对象时,默认会调用父类无参构造方法。通过该案例,我们可以得出一个结论:在定义一个类时,如果没有特殊需求,当定义了有参构造方法后,尽量在类中显式地定义一个无参构造方法,这样可以避免该类被继承时出现错误。

四、Java类的老祖宗——Object类

1、Object类概述

  • 在Java中提供了一个Object类,它是所有类的父类,即每个类都直接或间接继承自该类,因此Object类通常被称为超类、基类或根类。
  • 当定义一个类时,如果没有使用extends关键字为这个类显式地指定父类,那么该类会默认继承Object类。

2、查看类层次结构

在这里插入图片描述
选中某个类,比如Animal类,按组合键<Ctrl>+<H>,就会显示该类的层次结构图:
在这里插入图片描述
对于Animal类,没有使用extends关键字为这个类显式地指定父类,那么该类会默认继承Object类。

3、Object类常用方法

方法声明 功能描述
boolean equals(Object obj) 判断某个对象与此对象是否相等
final Class<?> getClass() 返回此Object的运行时类
int hashCode() 返回该对象的哈希码值
String toString() 返回该对象的字符串表示
void finalize() 垃圾回收器调用此方法来清理没有被任何引用变量所引用对象的资源

4、案例演示toString()方法

(1)实例化Animal对象,调用toString()方法

在这里插入图片描述

package net.hw.lesson13;

/**
 * 功能:调用toString()方法
 * 作者:华卫
 * 日期:2020年05月04日
 */
public class Example1305 {
    public static void main(String[] args) {
        Animal animal = new Animal();
        System.out.println(animal.toString());
    }
}

运行程序,查看结果:
在这里插入图片描述
在Example1305中,第11行代码调用了Animal对象的toString()方法,虽然Animal类中并没有定义这个方法,但程序并没有报错,这是因为Animal类默认继承了Object类,在Object类中定义了toString()方法,在该方法中输出了对象的基本信息。

(2)查看Object类的toString()方法源码

在这里插入图片描述

public String toString() {
	return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
  • getClass().getName():代表返回对象所属类的类名,即包名+类名的全限定名称。
  • hashCode():代表返回该对象的哈希值。
  • Integer.toHexString(hashCode()):代表将对象的哈希值用16进制表示。其中,hashCode()是Object类中定义的一个方法,这个方法将对象的内存地址进行哈希运算,返回一个int类型的哈希值。

在实际开发中,通常希望toString()方法返回的不仅仅是对象的基本信息,而是一些特有的信息,为此我们可以通过重写Object的toString()方法来实现。

(3)重写Animal类的toString,输出对象的具体内容

在这里插入图片描述

@Override                            
public String toString() {           
    return "Animal{" +               
            "name='" + name + '\'' + 
            ", age=" + age +         
            '}';                     
}                                    

(4)修改Example1305,设置对象属性

package net.hw.lesson13;

/**
 * 功能:调用toString()方法
 * 作者:华卫
 * 日期:2020年05月04日
 */
public class Example1305 {
    public static void main(String[] args) {
        Animal animal = new Animal();
        animal.setName("金毛犬");
        animal.setAge(4);
        System.out.println(animal.toString());
    }
}

此时,再次运行程序,结果如下:
在这里插入图片描述

5、课堂练习:重写Animal的equals()方法

当两只动物对象的name与age属性值都相等时,两只动物对象相等。

五、课后作业

任务:针对不同几何图形计算周长与面积

1、创建Shape类,提供两个方法

  • double perimeter() —— 计算图形周长
  • double area() —— 计算图形面积

2、创建矩形类Rectangle

  • 继承Shape类
  • 包含双精度的长宽属性:a和b

3、创建圆形类Circle

  • 继承Shape类
  • 包含双精度的半径属性:r

4、创建ShapeCalculation类,提供两个方法

  • double getPerimter(Shape shape); ——计算图形周长
  • double getArea(Shape shape); ——计算图形面积

5、创建TestShapeCalculation类

  • 实例化ShapeCalculation对象
  • 输入矩形长与宽,计算其周长与面积
  • 输入圆半径,计算其周长和面积

猜你喜欢

转载自blog.csdn.net/howard2005/article/details/105901466
今日推荐