Java_多态&抽象类&接口

1. 多态

即同一方法可以根据发送对象的不同而采用多种不同的行为方式。

一个对象的实际类型是确定的,但可以指向对象的引用类型有很多。

1.1 多态的概述

什么是多态:

同一个对象,在不同时刻表现出来的不同形态

举例:猫

  我们可以说猫是猫:猫 cat = new 猫();

  我们也可以说猫是动物:动物 animal = new 猫();

  这里猫在不同的时刻表现出来了不同的形态,这就是多态

多态的前提:

  要有继承或实现关系

  要有方法的重写

  要有父类引用指向子类对象

public class Animal {
    public void eat() {
        System.out.println("动物吃东西");
    }
}
//有继承关系
public class Cat extends Animal{
    //有方法的重写
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
}
public class AnimalDemo {
    public static void main(String[] args) {
        //有父类引用指向子类对象
        Animal a = new Cat();
    }
}

1.2 多态中的成员访问特点

成员变量:

  编译看父类,运行看父类

成员方法:

  编译看父类,运行看子类

public class Animal {
    public int age = 40;

    public void eat() {
        System.out.println("动物吃东西");
    }
}
public class Cat extends Animal {
    public int age = 20;
    public int weight = 10;

    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }

    public void playGame() {
        System.out.println("猫捉迷藏");
    }
}
public class AnimalDemo {
    public static void main(String[] args) {
        //有父类引用指向子类对象
        Animal a = new Cat();

        System.out.println(a.age); //40  (成员变量:编译看父类,执行看父类)
//        System.out.println(a.weight); 报错(成员变量:编译看父类,执行看父类)

        a.eat(); //猫吃鱼  (成员方法:编译看父类,执行看子类)
//        a.playGame(); 报错(成员方法:编译看父类,执行看子类)
    }
}

为什么成员变量和成员方法的访问不一样呢?

  因为成员方法有重写,而成员变量没有

1.3 多态的好处和弊端

好处:

  提高程序的扩展性。定义方法时候,使用父类型作为参数;在使用的时候,使用具体的子类型参与操作

弊端:

  不能使用子类的特有成员

public class Animal {
    public void eat() {
        System.out.println("动物吃东西");
    }
}
public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
}
public class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("狗吃骨头");
    }

    public void lookDoor() {
        System.out.println("狗看门");
    }
}
public class Pig extends Animal {
    @Override
    public void eat() {
        System.out.println("猪吃白菜");
    }
}
/*
    动物操作类
 */
public class AnimalOperator {
    /*
    public void useAnimal(Cat c) { //Cat c = new Cat();
        c.eat();
    }

    public void useAnimal(Dog d) { //Dog d = new Dog();
        d.eat();
    }
    */

    public void useAnimal(Animal a) {
        //Animal a = new Cat();
        //Animal a = new Dog();

        a.eat();
//        a.lookDoor(); 错误:不能使用子类的特有成员
    }
}
/*
    测试类
 */
public class AnimalDemo {
    public static void main(String[] args) {
        //创建动物操作类的对象,调用方法
        AnimalOperator ao = new AnimalOperator();
        Cat c = new Cat();
        ao.useAnimal(c);

        Dog d = new Dog();
        ao.useAnimal(d);

        Pig p = new Pig();
        ao.useAnimal(p);
    }
}

1.4 多态中的转型

向上转型:

  父类引用指向子类对象就是向上转型

向下转型:

  格式:子类型 对象名 = (子类型)父类引用;

public class Animal {
    public void eat() {
        System.out.println("动物吃东西");
    }
}
public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }

    public void playGame() {
        System.out.println("猫捉迷藏");
    }
}
public class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("狗吃骨头");
    }

    public void lookDoor() {
        System.out.println("狗看门");
    }
}
public class AnimalDemo {
    public static void main(String[] args) {
        //向上转型
        Animal a = new Cat(); //父类引用指向子类对象
        a.eat();
//        a.playGame(); 错误(编译看父类)

        /*
        //创建Cat类的对象
        Cat c = new Cat(); //内存中有两个Cat类的对象,为了使用playGame方法造两个对象,没必要
        c.eat();
        c.playGame();
        */

        //向下转型
        Cat c = (Cat) a; ////格式:子类型 对象名 = (子类型)父类引用;
        c.eat();
        c.playGame();

        //向上转型
        a = new Dog();
        a.eat();

        //向下转型
        //ClassCastException:类型转换异常
        Cat cc = (Cat) a; //此时内存中a是Dog,狗不能强转为猫
        cc.eat();
        cc.playGame();
    }
}

1.5 多态的案例

需求:请采用多态的思想实现猫和狗的案例,并在测试类中进行测试

思路:

1. 定义动物类(Animal)

  成员变量:姓名,年龄

  构造方法:无参,带参

  成员方法:get/set方法,吃饭()

2. 定义猫类(Cat),继承动物类

  构造方法:无参,带参

  成员方法:重写吃饭()

3. 定义狗类(Dog),继承动物类

  构造方法:无参,带参

  成员方法:重写吃饭()

4. 定义测试类(AnimalDemo),写代码测试

public class Animal {
    private String name;
    private int age;

    public Animal() {
    }

    public Animal(String name, int age) {
        this.name = name;
        this.age = 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 eat() {
        System.out.println("动物吃东西");
    }
}
public class Cat extends Animal {
    public Cat() {
    }

    public Cat(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
}
public class Dog extends Animal {
    public Dog() {
    }

    public Dog(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println("狗吃骨头");
    }
}
public class AnimalDemo {
    public static void main(String[] args) {
        //创建猫类对象进行测试
        Animal a = new Cat();
        a.setName("Tom");
        a.setAge(2);
        System.out.println(a.getName() + "," + a.getAge());
        a.eat();

        a = new Cat("Tom", 5);
        System.out.println(a.getName() + "," + a.getAge());
        a.eat();

        //创建狗类对象进行测试
        Animal a1 = new Dog();
        a1.setName("Wang");
        a1.setAge(3);
        System.out.println(a1.getName() + "," + a1.getAge());
        a1.eat();

        a1 = new Dog("Wang", 3);
        System.out.println(a1.getName() + "," + a1.getAge());
        a1.eat();
    }
}

 

2. 抽象类 

2.1 抽象类的概述

  当我们在做子类共性功能抽取时,有些方法在父类中并没有具体的体现,这个时候就需要抽象类了!

  在Java中,一个没有方法体的方法应该定义为抽象方法,而类中如果有抽象方法,该类必须定义为抽象类! 

2.2 抽象类的特点

抽象类和抽象方法必须使用 abstract 关键字修饰

//抽象类的定义
public abstract class 类名 {}

//抽象方法的定义
public abstract void 方法名();

抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类

抽象类不能直接实例化

  抽象类如何实例化:参照多态的方式,通过子类对象实例化,这叫抽象类多态

抽象类的子类

  要么重写抽象类中的所有抽象方法

  要么是抽象类

/*
    抽象类
 */
//抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
public abstract class Animal {
    //抽象方法
    public abstract void eat();

    public void sleep() {
        System.out.println("睡觉");
    }
}
public class Cat extends Animal {
    //重写抽象类中的所有抽象方法
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
}
//是抽象类
public abstract class Dog extends Animal {
}
/*
    测试类
 */
public class AnimalDemo {
    public static void main(String[] args) {
//        Animal a = new Animal(); 抽象类不能直接实例化
        //抽象类如何实例化:参照多态的方式,通过子类对象实例化,这叫抽象类多态
        Animal a = new Cat();
        a.eat();
        a.sleep();
    }
}

2.3 抽象类的成员特点

成员变量:

  既可以是变量;也可以是常量

构造方法:

  空参构造;有参构造

  (构造方法不能实例化,作用:用于子类访问父类数据的初始化)

成员方法:

  抽象方法(限制子类必须完成某些动作:即子类要么重写父类中的抽象方法;要么子类也为抽象类)

  普通方法(提高代码的复用性)

/*
    抽象类
 */
public abstract class Animal {
    private int age = 20;
    private final String city = "北京";

    public Animal() {
    }

    public Animal(int age) {
        this.age = age;
    }

    public void show() {
        age = 40;
        System.out.println(age);
//        city = "上海"; 错误,city被final修饰,为常量,不可再赋值
        System.out.println(city);
    }

    //父类中的抽象方法限制子类必须完成某些动作
    //即子类要么重写父类中的抽象方法
    //要么子类也为抽象类
    public abstract void eat();
}
public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
}
public class AnimalDemo {
    public static void main(String[] args) {
        Animal a = new Cat();
        a.eat();
        a.show();
    }
}

2.4 抽象类的案例

案例需求:

请采用抽象类的思想实现猫和狗的案例,并在测试类中进行测试

思路:

定义动物类(Animal)

  成员变量:姓名,年龄

  构造方法:无参,带参

  成员方法:get/set方法,吃饭();

定义猫类(Cat),继承动物类

  构造方法:无参,带参

  成员方法:重写吃饭(){…}

定义狗类(Dog),继承动物类

  构造方法:无参,带参

  成员方法:重写吃饭(){…}

定义测试类(AnimalDemo),写代码测试

代码实现:

public abstract class Animal {
    private String name;
    private int age;

    public Animal() {
    }

    public Animal(String name, int age) {
        this.name = name;
        this.age = 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 abstract void eat();
}
public class Cat extends Animal {
    public Cat() {
    }

    public Cat(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
}
public class Dog extends Animal {
    public Dog() {
    }

    public Dog(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println("狗吃骨头");
    }
}
public class AnimalDemo {
    public static void main(String[] args) {
        //创建对象,按照多态的方式
        Animal a = new Cat();
        a.setName("Tom");
        a.setAge(2);
        System.out.println(a.getName() + "," + a.getAge());
        a.eat();

        a = new Cat("Tom", 2);
        System.out.println(a.getName() + "," + a.getAge());
        a.eat();

        Animal a2 = new Dog();
        a2.setName("Wang");
        a2.setAge(3);
        System.out.println(a2.getName() + "," + a2.getAge());
        a2.eat();

        a2 = new Dog("Wang", 3);
        System.out.println(a2.getName() + "," + a2.getAge());
        a2.eat();
    }
}

 

3. 接口

3.1 接口的概述

接口就是一种公共的规范标准,只要符合规范标准,大家都可以通用。

Java中的接口更多的体现在对行为的抽象

3.2 接口的特点

接口用关键字 interface 修饰

public interface 接口名 {}

类实现接口用 implements 表示

public class 类名 implements 接口名 {}

接口不能直接实例化

接口如何实例化:参照多态的方式,通过实现类对象实例化,这叫接口多态。

多态的形式:具体类多态,抽象类多态,接口多态

接口的子类:

  要么重写接口中的所有抽象方法

  要么子类也是抽象类

public class JumppingDemo {
    public static void main(String[] args) {
//        Jumpping j = new Jumpping(); 接口不能直接实例化

        //接口如何实例化:参照多态的方式,通过实现类对象实例化,这叫接口多态。
        Jumpping j = new Cat();
        j.jump();
    }
}
/*
    定义了一个接口
 */
//接口用关键字 interface 修饰
public interface Jumpping {
    public abstract void jump();
}
//类实现接口用 implements 表示
public class Cat implements Jumpping {
    //要么重写接口中的所有抽象方法
    @Override
    public void jump() {
        System.out.println("猫可以跳高了");
    }
}
//要么子类也是抽象类
public abstract class Dog implements Jumpping {
}

3.3 接口的成员特点

成员变量

  只能是常量,默认修饰符:public static final (接口中的成员变量默认被final修饰)

构造方法

  没有,因为接口主要是扩展功能的(对行为进行抽象),而没有具体存在

  一个类如果没有父类,默认继承自 Object 类

成员方法

  只能是抽象方法

  默认修饰符:public abstract

  关于接口中的方法,JDK8和JDK9中有一些新特性,后面再讲解

public interface Inter {
    public int num = 10;
    public final int num2 = 20;

    //    public static final int num3 = 30; //等价于int num3 = 30;
    int num3 = 30;

//    public Inter() {} 错误,接口没有构造方法

    public abstract void method(); //接口中的成员方法只能是抽象方法

    void show(); //接口中的方法默认带有修饰符:public abstract
}
//public class InterImpl implements Inter {  等价于
public class InterImpl extends Object implements Inter {
    public InterImpl() {
        super(); //用的是Object类里面的构造方法
    }

    @Override
    public void method() {
        System.out.println("method");
    }

    @Override
    public void show() {
        System.out.println("show");
    }
}
public class InterfaceDemo {
    public static void main(String[] args) {
        Inter i = new InterImpl();
//        i.num = 20; 错误,接口中的成员变量默认被final修饰
        System.out.println(i.num);
//        i.num2 = 40; 错误,num2被final修饰,为常量,不可重新赋值
        System.out.println(i.num2);

        System.out.println(Inter.num); //可以通过接口名直接访问,说明num默认被静态修饰
        System.out.println(Inter.num3);
    }
}

3.4 接口的案例

案例需求:

对猫和狗进行训练,他们就可以跳高了,这里加入跳高功能。

请采用抽象类和接口来实现猫狗案例,并在测试类中进行测试。

思路:

定义接口(Jumpping)

  成员方法:跳高();

定义抽象动物类(Animal)

  成员变量:姓名,年龄;构造方法:无参,带参;成员方法:get/set方法,吃饭();

定义具体猫类(Cat),继承动物类,实现跳高接口

  构造方法:无参,带参;成员方法:重写吃饭(){…},重写跳高方法(){…}

定义狗类(Dog) ,继承动物类,实现跳高接口

  构造方法:无参,带参;成员方法:成员方法:重写吃饭(){…},重写跳高方法(){…}

定义测试类(AnimalDemo),写代码测试

public interface Jumping {
    public abstract void jump();
}
public abstract class Animal {
    private String name;
    private int age;

    public Animal() {
    }

    public Animal(String name, int age) {
        this.name = name;
        this.age = 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 abstract void eat();
}
public class Cat extends Animal implements Jumping {
    public Cat() {
    }

    public Cat(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }

    @Override
    public void jump() {
        System.out.println("猫跳高了");
    }
}

狗类同理,所以省略

public class AnimalDemo {
    public static void main(String[] args) {
        //创建对象,调用方法
        Jumping j = new Cat();
        j.jump();
        System.out.println("------");

        Animal a = new Cat();
        a.setName("Tom");
        a.setAge(2);
        System.out.println(a.getName() + "," + a.getAge());
        a.eat();
//        a.jump; 错误,因为Animal类中无jump方法,除非强转:((Cat) a).jump();

        a = new Cat("Tom", 2);
        System.out.println(a.getName() + "," + a.getAge());
        a.eat();
        System.out.println("------");

        Cat c = new Cat(); //Cat类继承类Animal类,并且实现了Jumping接口,所以方法最多
        c.setName("加菲");
        c.setAge(3);
        System.out.println(c.getName() + "," + c.getAge());
        c.eat();
        c.jump();
    }
}

3.5 类和接口的关系

类与类的关系:

  继承关系,只能单继承,但是可以多层继承

类与接口的关系:

  实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口

接口与接口的关系:

  继承关系,可以单继承,也可以多继承

public interface Inter1{
}
//接口与接口的关系
//继承关系,可以单继承,也可以多继承。下面是单继承
public interface Inter2 extends Inter1 {
}
//接口与接口的关系
//继承关系,可以单继承,也可以多继承。下面是多继承
public interface Inter3 extends Inter1, Inter2 {
}
//类与接口的关系
//实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
//下面是继承一个类的同时实现多个接口
public class InterImpl extends Object implements Inter1, Inter2, Inter3 {
}

3.6 抽象类和接口的区别

成员区别:

  抽象类:变量,常量;有构造方法;有抽象方法,也有非抽象方法

  接口:常量;抽象方法

关系区别:

  类与类:继承,单继承

  类与接口:实现,可以单实现,也可以多实现

  接口与接口:继承,单继承,多继承

设计理念区别:

  抽象类:对类抽象,包括属性、行为

  接口:对行为抽象,主要是行为

门和警报的例子:

门:都有open()和close()两个动作,这个时候,我们可以分别使用抽象类和接口来定义这个抽象概念

//抽象类
public abstract class Door {
    public abstract void open();
    public abstract void close();
}

//接口
public interface Door {
    void open();
    void close();
}

之后,门有了警报功能:

//抽象类
public abstract class Door {
    public abstract void open();
    public abstract void close();
    public abstract void alarm();
}

继承这个抽象类的子类都会具有警报功能,但有的门不具备有警报功能,所以将open、close、alarm都放到一个抽象类中不好。

//接口
public interface Door {
    void open();
    void close();
    void alarm();
}

需要用到警报功能的类就可以实现这个接口,但是open和close一并实现了,也许这个类并不具备open和close功能,所以这样做也不好。

可以这么做:

public interface Alarm {
    void alarm();
}
public abstract class Door {
    public abstract void open();
    public abstract void close();
}
public class AlarmDoor extends Door implements Alarm {
    public void open() {
        //...
    }
    public void close() {
        //...
    }
    public void alarm() {
        //...
    }
}

 

4. 综合案例

案例需求:

  我们现在有乒乓球运动员和篮球运动员,乒乓球教练和篮球教练。为了出国交流,跟乒乓球相关的人员都需要学习英语。

  请用所学知识分析,这个案例中有哪些具体类,哪些抽象类,哪些接口,并用代码实现。

分析:从具体到抽象

  乒乓球运动员 & 篮球运动员(具体)—— 运动员(抽象)

  乒乓球教练 & 篮球教练(具体) —— 教练(抽象)

  运动员 & 教练 —— 人(抽象)

  乒乓球运动员 & 乒乓球教练 —— 学习英语(接口)

实现:从抽象到具体

使用:使用的是具体的类的对象

 

定义说英语接口:

public interface SpeakEnglish {
    public abstract void speak();
}

定义抽象人类:

//抽象人类
public abstract class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = 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 abstract void eat();
}

定义抽象教练类,继承人类:

//抽象教练类
public abstract class Coach extends Person{
    public Coach() {
    }

    public Coach(String name, int age) {
        super(name, age);
    }

    public abstract void teach();
}

定义抽象运动员类,继承人类:

//抽象运动员类
public abstract class Player extends Person {
    public Player() {
    }

    public Player(String name, int age) {
        super(name, age);
    }

    public abstract void study();
}

定义具体篮球教练类,继承教练类:

public class BasketballCoach extends Coach {
    public BasketballCoach() {
    }

    public BasketballCoach(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println("篮球教练教如何运球和投篮");
    }

    @Override
    public void teach() {
        System.out.println("篮球教练吃饭");
    }
}

定义具体乒乓球教练类,继承教练类,实现说英语接口:

public class PingPangCoach extends Coach implements SpeakEnglish {
    public PingPangCoach() {
    }

    public PingPangCoach(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println("乒乓球教练吃饭");
    }

    @Override
    public void teach() {
        System.out.println("乒乓球教练教如何发球和接球");
    }

    @Override
    public void speak() {
        System.out.println("乒乓球教练说英语");
    }
}

定义具体篮球运动员类,继承运动员类:

public class BasketballPlayer extends Player {
    public BasketballPlayer() {
    }

    public BasketballPlayer(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println("篮球运动员吃饭");
    }

    @Override
    public void study() {
        System.out.println("篮球运动员学习如何运球和投篮");
    }
}

定义具体乒乓球运动员类,继承运动员类,实现说英语接口:

public class PingPangPlayer extends Player implements SpeakEnglish {
    public PingPangPlayer() {
    }

    public PingPangPlayer(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println("乒乓球运动员吃饭");
    }

    @Override
    public void study() {
        System.out.println("乒乓球运动员学习如何发球和接球");
    }

    @Override
    public void speak() {
        System.out.println("乒乓球运动员说英语");
    }
}

定义测试类,写代码测试:

/*
    测试类
 */
public class PersonDemo {
    public static void main(String[] args) {
        //创建对象
        PingPangPlayer ppp = new PingPangPlayer();
        ppp.setName("张三");
        ppp.setAge(24);
        System.out.println(ppp.getName() + "," + ppp.getAge());
        ppp.eat();
        ppp.study();
        ppp.speak();
        System.out.println("----------");

        PingPangPlayer ppp2 = new PingPangPlayer("李四",35);
        System.out.println(ppp2.getName() + "," + ppp2.getAge());
        ppp2.eat();
        ppp2.study();
        ppp2.speak();
        System.out.println("**********");

        BasketballPlayer bp = new BasketballPlayer("王五", 26);
        System.out.println(bp.getName() + "," + bp.getAge());
        bp.eat();
        bp.study();
        System.out.println("----------");

        BasketballCoach bc = new BasketballCoach();
        bc.setName("赵六");
        bc.setAge(32);
        System.out.println(bc.getName() + "," + bc.getAge());
        bc.eat();
        bc.teach();
    }
}

 

posted @ 2021-03-03 15:48  Mayrizon3  阅读(134)  评论(0)    收藏  举报