Java讲课笔记15:抽象类、接口和多态

文章目录

零、本讲学习目标

1、掌握抽象类及接口的定义和使用

2、熟悉多态的概念

3、掌握对象的类型转换

一、抽象类

1、引入抽象类

(1)问题

在《Java讲课笔记13:类的继承》里,我们定义了Animal类,其中定义了eat()方法用于表示动物吃东西,但是不同的动物,吃的东西也是不同的,因此在eat()方法中无法准确描述动物吃什么东西。

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

如何能使Animal类中既包含eat()方法,又无需提供其方法的实现呢?

(2)解决方案

Java提供了抽象方法来满足这种需求。抽象方法必须使用abstract关键字来修饰,并且在定义方法时不需要实现方法体。当一个类中包含了抽象方法,那么该类也必须使用abstract关键字来修饰,这种使用abstract关键字修饰的类就是抽象类。

2、抽象类及抽象方法定义的语法格式

// 定义抽象类
[修饰符] abstract class 类名 {
    // 定义抽象方法
    [修饰符] abstract 方法返回值类型 方法名([参数列表]);
    // 其它方法或属性
 }

注意: 如果一个类包含了没有实现的方法(抽象方法),那么这个类就必定是抽象类,不能实例化,只能通过继承,在子类中实现其抽象方法,然后就可以实例化子类。我们知道,包含了抽象方法的类一定是抽象类,然而抽象类中可以不包含任何抽象方法。

3、案例演示抽象类与抽象方法

  • 创建Example1501
    在这里插入图片描述
package net.hw.lesson15;

/**
 * 功能:演示抽象类与抽象方法
 * 作者:华卫
 * 日期:2020年05月08日
 */

// 定义抽象类
abstract class Flower {
    // 定义抽象方法
    public abstract void bloom();
}

// 继承Flower类创建Jasmine类
class Jasmine extends Flower {
    // 实现抽象方法bloom(),编写方法体
    @Override
    public void bloom() {
        System.out.println("茉莉花正在绽放,散发着淡淡幽香。");
    }
}

// 测试类
public class Example1501 {
    public static void main(String[] args) {
       Jasmine jasmine = new Jasmine();
       jasmine.bloom();
    }
}
  • 运行程序,查看结果
    在这里插入图片描述
    子类Jasmine实现了父类Flower的抽象方法bloom()后,就可以正常进行实例化操作,并可以通过实例化对象设置属性与调用子类实现的方法。

4、课堂练习:继承抽象类Flower创建牡丹类Peony并测试

在这里插入图片描述

二、接口

1、老版本接口定义

接口是一种特殊的抽象类,它不能包含普通方法,其内部的所有方法都是抽象方法,它将抽象进行得更为彻底。接口其实是定义了一种标准或规范,既然如此,接口里不能包含一般的属性,只能包含常量。

2、JDK8接口定义

接口中除了抽象方法外,还可以有默认方法和静态方法(也叫类方法),默认方法使用default修饰,静态方法使用static修饰,并且这两种方法都允许有方法体。

(1)接口定义语法格式

 [修饰符] interface 接口名 [extends 父接口1, 父接口2, ...] {
      [public] [static] [final] 常量类型 常量名 = 常量值;
      [public] [abstract] 方法返回值类型 方法名([参数列表]);
      [public] default 方法返回值类型 方法名([参数列表]){
	      // 默认方法的方法体
      }
      [public] static 方法返回值类型 方法名([参数列表]){
         // 类方法的方法体
     }
}

(2)接口语法定义说明

  • 修饰符可以使用public或直接省略(省略时默认采用包权限访问控制符)。
  • 在接口内部可以定义多个常量和抽象方法,定义常量时必须进行初始化赋值,定义默认方法和静态方法时,可以有方法体。
  • 在接口中定义常量时,可以省略“public static final”修饰符,接口会默认为常量添加“public static final”修饰符。与此类似,在接口中定义抽象方法时,也可以省略“public abstract”修饰符,定义default默认方法和static静态方法时,可以省略“public”修饰符,这些修饰符系统都会默认进行添加。
  • 接口中可以包含三类方法,抽象方法、默认方法、静态方法。
  • 静态方法可以通过“接口名.方法名”的形式来调用。
  • 抽象方法和默认方法只能通过接口实现类的实例对象来调用。
  • 接口的实现类,必须实现接口中的所有抽象方法。

3、案例演示接口的使用

(1)创建Shape接口

在这里插入图片描述

package net.hw.lesson15;

/**
 * 功能:Shape接口
 * 作者:华卫
 * 日期:2020年05月08日
 */
public interface Shape {
    // 定义全局常量
    double PI = 3.1415926;
    // 定义抽象方法
    double getCircumference();
    double getArea();
    // 定义默认方法
    default void getType(String type) {
        System.out.println("该图形属于" + type);
    }
    // 定义静态方法
    static double getPI() {
        return Shape.PI;
    }
}

(2)创建Circle类,实现Shape接口

在这里插入图片描述

扫描二维码关注公众号,回复: 11266737 查看本文章
package net.hw.lesson15;

/**
 * 功能:实现Shape接口创建Circle类
 * 作者:华卫
 * 日期:2020年05月08日
 */
public class Circle implements Shape {
    private double r;

    public double getR() {
        return r;
    }

    public void setR(double r) {
        this.r = r;
    }

    @Override
    public double getCircumference() {
        return 2 * PI * r;
    }

    @Override
    public double getArea() {
        return PI * r * r;
    }
}
  • 查看Circle的类图
    在这里插入图片描述
    在这里插入图片描述
  • 查看Circle类结构(属性与方法)
    在这里插入图片描述

(3)创建测试类TestCircle

在这里插入图片描述

package net.hw.lesson15;

/**
 * 功能:测试Circle类
 * 作者:华卫
 * 日期:2020年05月08日
 */
public class TestCircle {
    public static void main(String[] args) {
        // 通过接口名访问接口全局常量
        System.out.println("Shape.PI = " + Shape.PI);
        // 通过接口名调用类方法(接口静态方法)
        System.out.println("Shape.getPI() = " + Shape.getPI());
        // 实例化Circle对象
        Circle circle = new Circle();
        // 通过对象名访问接口全局常量
        System.out.println("circle.PI = " + circle.PI);
        // 通过对象名调用接口默认方法
        circle.getType("圆");
        // 设置对象属性
        circle.setR(3);
        // 调用对象方法
        System.out.println("circumference = " + circle.getCircumference());
        System.out.println("area = " + circle.getArea());
    }
}
  • 运行程序,查看结果
    在这里插入图片描述
  • 对象可以调用接口默认方法,但是不能调用接口静态方法
    在这里插入图片描述
    提示错误“static method may be invoked on containing interface class only.”
    这是因为在Java 8中,在接口中添加静态方法带来了一个限制 :这些方法不能由实现它的类继承。这样做是有道理的,因为一个类可以实现多个接口。如果2个接口具有相同的静态方法,它们都将被继承,编译器就不知道要调用哪个接口的静态方法了。

4、课堂练习:实现Shape接口,创建Retangle类并测试

在这里插入图片描述

5、案例演示接口的继承

注意:类只能继承一个父类,但是接口可以继承多个父接口。

(1)创建Person接口

在这里插入图片描述

package net.hw.lesson15;

/**
 * 功能:Person接口
 * 作者:华卫
 * 日期:2020年05月09日
 */
public interface Person {
    void eat();
}
  • 创建Writable接口
    在这里插入图片描述
package net.hw.lesson15;

/**
 * 功能:Writable接口
 * 作者:华卫
 * 日期:2020年05月09日
 */
public interface Writable {
    void write();
}
  • 继承Person与Writable接口,创建Man接口
    在这里插入图片描述
package net.hw.lesson15;

/**
 * 功能:继承Person与Writable接口,创建Man接口
 * 作者:华卫
 * 日期:2020年05月09日
 */
public interface Man extends Person, Writable {
    void fight();
}

Man接口不仅继承了Person与Writable接口,还添加了一个抽象方法fight()。

(2)实现Man接口,创建Police类

在这里插入图片描述

package net.hw.lesson15;

/**
 * 功能:实现Man接口,创建Police类
 * 作者:华卫
 * 日期:2020年05月09日
 */
public class Police implements Man {
    private String name;

    public String getName() {
        return name;
    }

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

    // 实现Man接口的抽象方法
    @Override
    public void fight() {
        System.out.println("警察[" + getName() + "]在破案。");
    }

    // 实现Person接口的抽象方法
    @Override
    public void eat() {
        System.out.println("警察[" + getName() + "]在吃饭。");
    }

    // 实现Writable接口的抽象方法
    @Override
    public void write() {
        System.out.println("警察[" + getName() + "]在写作。");
    }
}

我们来看一看Police类的类图:
在这里插入图片描述

(3)创建测试类TestPolice

在这里插入图片描述

package net.hw.lesson15;

/**
 * 功能:测试Police类
 * 作者:华卫
 * 日期:2020年05月09日
 */
public class TestPolice {
    public static void main(String[] args) {
        Police police = new Police();
        police.setName("李文强");
        police.eat(); // 调用Person接口的方法
        police.write(); // 调用Writable接口的方法
        police.fight(); // 调用Man接口的方法        
    }
}
  • 运行程序,查看效果
    在这里插入图片描述

6、案例演示类继承一个类与实现多个接口

Java简化了C++的多重继承,是单根继承,只能继承一个类,但是作为补偿,Java允许实现多个接口,让一个类可以实现更多的特性。

(1)创建NetCard接口

在这里插入图片描述

package net.hw.lesson15;

/**
 * 功能:网卡接口
 * 作者:华卫
 * 日期:2020年05月09日
 */
public interface NetCard {
    void connectNetwork();
}

(2)创建USB接口

在这里插入图片描述

package net.hw.lesson15;

/**
 * 功能:USB接口
 * 作者:华卫
 * 日期:2020年05月09日
 */
public interface USB {
    void connectUSB();
}

(3)创建抽象类Computer

在这里插入图片描述

package net.hw.lesson15;

/**
 * 功能:抽象类Computer
 * 作者:华卫
 * 日期:2020年05月09日
 */
public abstract class Computer {
    public abstract void compute();
}

(4)创建Laptop类,继承Computer类,实现NetCard与USB接口

在这里插入图片描述

package net.hw.lesson15;

/**
 * 功能:笔记本电脑类
 * 作者:华卫
 * 日期:2020年05月09日
 */
public class Laptop extends Computer implements NetCard, USB {
    private String name;

    public String getName() {
        return name;
    }

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

    // 重写Computer抽象类的抽象方法
    public void compute() {
        System.out.println("笔记本电脑[" + getName() + "]正在计算。");
    }

    // 实现NetCard接口的抽象方法
    public void connectNetwork() {
        System.out.println("笔记本电脑[" + getName() + "]连接网络。");
    }

    // 实现USB接口的抽象方法
    public void connectUSB() {
        System.out.println("笔记本电脑[" + getName() + "]连接USB。");
    }
}

此时,查看Laptop类的类图:
在这里插入图片描述
在这里插入图片描述

(5)创建测试类TestLaptop

在这里插入图片描述

package net.hw.lesson15;

/**
 * 功能:测试Laptop类
 * 作者:华卫
 * 日期:2020年05月09日
 */
public class TestLaptop {
    public static void main(String[] args) {
        Laptop laptop = new Laptop(); // 实例化对象
        laptop.setName("Aspire V");   // 设置对象属性
        laptop.connectNetwork();      // 调用NetCard接口的方法
        laptop.connectUSB();          // 调用USB接口的方法
        laptop.compute();             // 调用Computer抽象类的方法
    }
}
  • 运行程序,查看结果
    在这里插入图片描述

7、接口特点总结

  • 从JDK 8开始,接口中的方法除了包含抽象方法外,还包含默认方法和静态方法,默认方法和静态方法都可以有方法体,并且静态方法可以直接通过“接口.方法名”来调用。
  • 当一个类实现接口时,如果这个类是抽象类,只需实现接口中的部分抽象方法即可,否则需要实现接口中的所有抽象方法。
  • 一个类可以通过implements关键字同时实现多个接口,被实现的多个接口之间要用英文逗号(,)隔开。
  • 接口之间可以通过extends关键字实现继承,并且一个接口可以同时继承多个接口,接口之间用英文逗号(,)隔开。
  • 一个类在继承一个类的同时还可以实现接口,此时,extends关键字必须位于implements关键字之前。

三、多态

1、多态概述

  • 定义: 在Java中,多态是指不同类的对象在调用同一个方法时所呈现出的多种不同行为。
  • 说明: 通常来说,在一个类中定义的属性和方法被其他类继承或重写后,当把子类对象直接赋值给父类引用变量时,相同引用类型的变量调用同一个方法所呈现出的多种不同形态。
  • 作用: 通过多态,消除了类之间的耦合关系,大大提高了程序的可扩展性和可维护性。
  • 注意: Java的多态性是由类的继承、方法重写以及父类引用指向子类对象体现的。由于一个父类可以有多个子类,多个子类都可以重写父类方法,并且多个不同的子类对象也可以指向同一个父类。这样,程序只有在运行时程序才能知道具体代表的是哪个子类对象,这就体现了多态性。

2、案例演示多态

(1)创建动物类Animal

在这里插入图片描述

package net.hw.lesson15;

/**
 * 功能:动物类
 * 作者:华卫
 * 日期:2020年05月09日
 */
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 + "在吃……");
    }

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

(2)继承Animal类,创建Bird类

在这里插入图片描述

package net.hw.lesson15;

import net.hw.lesson13.Animal;

/**
 * 功能:继承动物类,创建鸟类
 * 作者:华卫
 * 日期:2020年05月09日
 */
public class Bird 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()+ "在玩虫子。");
    }
}

(3)继承Animal类,创建Cat类

在这里插入图片描述

package net.hw.lesson15;

import net.hw.lesson13.Animal;

/**
 * 功能:继承动物类,创建猫类
 * 作者:华卫
 * 日期:2020年05月09日
 */
public class Cat 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()+ "在玩老鼠。");
    }
}

(4)继承Animal类,创建Dog类

在这里插入图片描述

package net.hw.lesson15;

import net.hw.lesson13.Animal;

/**
 * 功能:继承动物类,创建狗类
 * 作者:华卫
 * 日期:2020年05月09日
 */
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() + "在玩飞盘。");
    }
}

(5)创建Student类

在这里插入图片描述

package net.hw.lesson15;

/**
 * 功能:Student类
 *      主要为演示多态
 * 作者:华卫
 * 日期:2020年05月09日
 */
public class Student {
    private String name;

    public String getName() {
        return name;
    }

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

    public void feed(Animal animal) {
        if (animal instanceof Dog) {
            System.out.println(name + "喂养狗崽[" + animal.getName() + "]。");
        } else if (animal instanceof Cat) {
            System.out.println(name + "喂养猫咪[" + animal.getName() + "]。");
        } else if (animal instanceof Bird) {
            System.out.println(name + "喂养鸟儿[" + animal.getName() + "]。");
        } else {
            System.out.println(name + "喂养动物[" + animal.getName() + "]。");
        }
        animal.move();
        animal.eat();
    }
}
  • Studen类的feed()方法,参数是Animal对象,可以指向狗、可以指向猫、也可以指向鸟,然后调用animal.move()与animal.eat()时就会根据传入的不同动物而呈现不同的形态。
  • Java提供了一个关键字instanceof,用来判断一个对象是否是某个类(或接口)的实例或者子类实例。

(6)创建测试类TestStudent

在这里插入图片描述

package net.hw.lesson15;

/**
 * 功能:测试Student类体现的多态
 * 作者:华卫
 * 日期:2020年05月09日
 */
public class TestStudent {
    public static void main(String[] args) {
        Student student = new Student();
        student.setName("张三丰");

        Animal animal1 = new Cat(); // 父类变量指向子类对象
        animal1.setName("欢欢");
        animal1.setAge(2);

        Animal animal2 = new Dog(); // 父类变量指向子类对象
        animal2.setName("瑞瑞");
        animal2.setAge(3);

        Animal animal3 = new Bird(); // 父类变量指向子类对象
        animal3.setName("豆豆");
        animal3.setAge(1);

        // 学生对象调用喂养方法,传入不同的动物对象
        student.feed(animal1);
        student.feed(animal2);
        student.feed(animal3);
    }
}
  • 运行程序,查看结果
    在这里插入图片描述
    在第13行、第17行与第21行分别创建猫、狗与鸟对象,注意,指向的都是父类Animal变量animal1、animal2与animal3,传入学生对象的feed()方法,在feed()方法调用参数对象的move()与eat()方法时,程序在编译时自动识别具体的子类对象,从而选择性地调用对应的方法,这就是Java多态性的体现。由此可见,多态不仅解决了方法同名的问题,而且还使程序变得更加灵活,从而有效地提高程序的可扩展性和可维护性。

3、对象的类型转换

(1)向上转型

  • 在多态的学习中,涉及到将子类对象当做父类类型使用的情况,此种情况在Java的语言环境中称为“向上转型”。
Animal animal1 = new Cat();    // 将Cat类对象当作Animal类型来使用
Animal animal2 = new Dog();    // 将Dog类对象当作Animal类型来使用
Animal animal3 = new Bird();   // 将Bird类对象当作Animal类型来使用

在这里插入图片描述

  • 将子类对象当做父类使用时不需要任何显式地声明,需要注意的是,此时不能通过父类变量去调用子类特有的方法。

(2)向下转型

  • 父类变量调用子类扩展的方法,程序编译时会报错
    在这里插入图片描述

  • 如果父类变量要调用子类扩展的方法,只能向下转型
    在这里插入图片描述

  • 向下转型可能会出现错误:向下转型需要考虑安全性,如果父类引用的对象是父类本身或者其它子类,那么在向下转型的过程中是不安全的,编译不会出错,但是运行时会出现java.lang.ClassCastException错误。
    在这里插入图片描述

  • 它可以使用instanceof来避免出错此类错误即能否向下转型,只有先经过向上转型的对象才能继续向下转型。
    在这里插入图片描述

四、课后作业

任务1、汽车接口、公共汽车类与载重卡车类

编写汽车接口Vehicle,有跑方法run()。编写公共汽车类Bus和载重卡车类Truck实现汽车接口,公共汽车类增加属性:最大载客量,增加报站方法,重写跑方法。载重卡车类增加属性:最大载重量,增加装货、卸货方法,重写跑方法。编写测试类测试2个类,使用汽车类引用公共汽车对象和载重汽车对象,让汽车跑起来。

在这里插入图片描述

任务2、驾驶员类、测试驾驶员类

编写驾驶员(Driver)类,声明属性:姓名。方法:驾驶(drive)方法,方法的参数是任务1的汽车接口,方法体中调用汽车接口的跑方法。测试类中调用驾驶员drive方法时参数传递汽车接口的子类对象,查看输出效果。

实现思路:

  • 把父接口作为方法的参数
  • 该父接口的子类对象作为参数传入

在这里插入图片描述

猜你喜欢

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