Java零基础入门笔记12-Java多态

1、概述

  • 1.所谓多态,即多种形态。它是面向对象程序设计语言中最核心的特征。从某种意义上来说,封装和继承都是为了多态而准备的。
  • 2.生活中有很多多态的现象。
    • 比如说猫、狗和兔子,它们喜欢吃的东西各有不同,叫声也不一样。
    • 再比如说,我们按下键盘上的F1键,针对当前工作窗口的不同,它也会显示不同帮助文档。比如我们打开的是Eclipse的工作界面,则打开的便是Eclipse的帮助文档;若我们打开的Office工作界面,则打开的是Office的帮助文档,等等。
    • 总之,就是同样的一种行为在不同的对象上,会产生不同的显示结果,这便是生活当中的多态。
  • 3.而在程序设计中,多态就意味着允许不同类的对象对同一消息作出不同的响应。
  • 4.多态可分为编译时多态运行时多态
    • 编译时多态:也称为设计时多态,就是编译器在编译状态就可以进行不同行为的区分,通常是通过方法重载的方式去实现的。
    • 运行时多态:指的是程序运行时动态决定调用哪个方法。而我们一般在Java当中所说到的多态大多指的就是运行时多态
  • 5.多态的实现要有两个必要条件:
    1. 满足继承关系
    2. 父类引用指向子类对象

2、多态的实现

2.1、实体类的编写

  • 1.新建一个名为PolyProj的Java项目,然后在项目中新建两个包,分别为:com.cxs.animalcom.cxs.test分别用于存放动物类和测试类。
  • 2.新建动物的父类Animal,代码如下:
public class Animal {
    private String name;
    private int month;

    public Animal() {
    }

    public Animal(String name, int month) {
        this.name = name;
        this.month = month;
    }

    public String getName() {
        return name;
    }

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

    public int getMonth() {
        return month;
    }

    public void setMonth(int month) {
        this.month = month;
    }

    public void eat() {
        System.out.println("动物都有吃东西的能力");
    }
}
  • 3.新建一个Cat类并继承自父类Animal,代码如下:
public class Cat extends Animal {
    private double weight;

    public Cat() {
    }

    public Cat(String name, Integer month, double weight) {
        super(name, month);// 或者用set方法进行赋值
        this.weight = weight;
    }

    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }

    public void run() {
        System.out.println("小猫快乐的奔跑");
    }

    @Override
    public void eat() {
        System.out.println("猫爱吃鱼");
    }
}
  • 4.新建一个Dog类也继承自Animal类。
public class Dog extends Animal {
    private String sex;

    public Dog() {
    }

    public Dog(String name, Integer month, String sex) {
        this.setName(name);
        this.setMonth(month);
        this.sex = sex;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public void sleep() {
        System.out.println("小狗有午睡的习惯");
    }

    @Override
    public void eat() {
        System.out.println("狗喜欢吃肉");
    }
}
  • 5.在test包下新建Test类并勾选main方法,用于代码测试。
public class Test {
    public static void main(String[] args) {
        Animal one = new Animal();// Animal的引用指向了一个具体的Animal实例
        Animal two = new Cat();// Animal的引用指向一个具体的子类Cat实例
        Animal three = new Dog();// Animal的引用指向一个具体的子类Dog实例

        one.eat();
        two.eat();
        three.eat();
    }
}
  • 6.运行代码,结果如下所示。容易看出,虽然都是Animal类型的引用,但是随着程序在运行时,根据实例化的对象类型的不同而执行了不同的操作,这便是在Java当中多态的表现。
    这里写图片描述

2.2、向上转型

  • 在上面的代码中,如Animal two=new Cat();为向上转型。向上转型又叫自动转型、隐式转型。向上转型就是父类引用指向子类实例,也就是将子类的对象赋值给父类的对象。
  • 向上转型是安全的,因为任何子列都继承并接受了父类的方法。我们也知道,所有的猫都属于猫的父类——动物,这是可行的;但是向下转型则不行,通俗来说就是,所有的动物都是猫这种说法不成立,并且向下转型必须通过强制类型转换。

2.2.1、向上转型的应用

  • 1.当一个子列对象向上转型父类类型后,就被当成了父类的对象,所能调用的方法会减少,只能调用子类重写父类的方法及父类派生的方法(如get()、set()方法),而不能调用子类独有的方法。代码演示略。
  • 2.父类中的静态方法是不允许被子类重写的。例如在上面的例子,我们再父类Animal的eat()方法前加上static进行修饰(当然子类重写方法时也需加上static修饰),此时会发现子类重写的eat()方法报错。
    这里写图片描述
  • 3.我们把注解去掉之后,发现错误消失,但此时已非重写父类的方法,而变成了自己独有的方法。代码演示略。

2.2.2、向上转型与动态绑定

  • 多态的实现可以通过向上转型和动态绑定机制来完成,向上转型实现了将子类对象向上转型为父类类型,而动态绑定机制能识别出对象转型前的类型,从而自动调用该类的方法,两者相辅相成。
  • 绑定就是将一个方法调用同一个方法所在的类连接到一起。绑定分为静态绑定和动态绑定。
    • 静态绑定:在程序运行之前进行绑定(由编译器和链接程序完成的),也叫做前期绑定。
    • 动态绑定:在程序运行期间由JVM根据对象的类型自动地判断应该调用哪个方法,也叫做后期绑定。
  • 1.静态绑定例子:比如上面的代码,父类Animal派生出来两个子列Cat和Dog,两个类都重写了父类中eat()方法,在测试类中静态绑定的方式调用方法eat()。
Cat cat=new Cat();
cat.eat();
Dog dog=new Dog();
dog.eat();
  • 2.这种调用方式是在代码中指定的,编译时编译器就知道,cat调用的是Cat的eat(),dog调用的就是Dog的eat()。
  • 3.动态绑定的例子:我们再测试类中编写如下代码。
public class Test {
    public static void main(String[] args) {
        // 生成父类对象数组,数组长度为5
        Animal[] animals = new Animal[5];

        for (int i = 0; i < animals.length; i++) {
            int n = (int) (Math.random() * 2);// 随机产生从0到1中的一个数
            switch (n) {
            case 0:
                animals[i] = new Cat();
                break;
            case 1:
                animals[i] = new Dog();
                break;
            }
        }
        // 循环输出,循环体中每个对象分别调用eat()方法
        for (int i = 0; i < animals.length; i++) {
            animals[i].eat();
        }

    }
}
  • 4.运行代码结果如下所示。
    这里写图片描述
  • 5.此时Animal类中随机生成Cat类和Dog类的对象,编译器不能根据代码直接确定调用哪个类中的eat()方法,直到运行时才能根据产生的随机数n的值来确定animal[i]到底代表哪一个子类的对象,这样才能最终确定调用的是哪个类中eat()方法,这就是动态绑定。

2.3、向下转型

向下转型,也称为强制类型转换,也是为了使子类引用指向父类对象。此时子类便可调用自己特有的方法了。

  • 1.修改Test类中的代码,如下所示。
public class Test {
    public static void main(String[] args) {
        Animal one = new Animal();// Animal的引用指向了一个具体的Animal实例
        Animal two = new Cat();// Animal的引用指向一个具体的子类Cat实例
        Animal three = new Dog();// Animal的引用指向一个具体的子类Dog实例

        one.eat();
        two.eat();
        three.eat();

        Cat cat = new Cat();
        cat.eat();
        Dog dog = new Dog();
        dog.eat();
        System.out.println("===================");

        // 向下转型:强制类型转换
        Cat four = (Cat) two;
        four.eat();
        four.run();
    }
}
  • 2.运行代码,结果如下所示。
    这里写图片描述
  • 3.还有一点需要注意,兄弟类(子类)之间不能进行强制类型转换,否则会出现类型转换异常。

2.4、instanceof运算符

instanceof运算符用来判断对象是否可满足某个特定类型实例特征。

  • 1.修改上面的代码。
public class Test {
    public static void main(String[] args) {
        Animal one = new Animal();// Animal的引用指向了一个具体的Animal实例
        Animal two = new Cat();// Animal的引用指向一个具体的子类Cat实例
        Animal three = new Dog();// Animal的引用指向一个具体的子类Dog实例

        one.eat();
        two.eat();
        three.eat();

        Cat cat = new Cat();
        cat.eat();
        Dog dog = new Dog();
        dog.eat();
        System.out.println("===================");

        if (two instanceof Cat) {
            // 向下转型:强制类型转换
            Cat four = (Cat) two;
            four.eat();
            four.run();
            System.out.println("two可以转换为Cat类型");
        }

        if (two instanceof Dog) {
            Dog five = (Dog) two;
            five.eat();
            five.sleep();
            System.out.println("two可以转换为Dog类型");
        } else {
            System.out.println("two不能转换为Dog类型");
        }
    }
}
  • 2.运行代码,发现新增的代码并没有运行,再次证明了兄弟类之间不能强制转换,并且利用instanceof可以避免出现异常,从而提高向下转型的安全性。
    这里写图片描述

3、类型转换案例

  • 1.在animal包下新建一个主人类Master,添加喂养方法,代码如下。
public class Master {
    public void feed(Cat cat) {
        cat.eat();
        cat.run();
    }

    public void feed(Dog dog) {
        dog.eat();
        dog.sleep();
    }
}
  • 2.在test包下新建一个MasterTest测试类,代码如下。
public class MasterTest {
    public static void main(String[] args) {
        Master master = new Master();
        Cat cat = new Cat();
        Dog dog = new Dog();

        master.feed(cat);
        master.feed(dog);
    }
}
  • 3.运行代码,结果如下。
    这里写图片描述
  • 4.下面我们对Master中的代码进行改进(此处将之前的代码删除,也可注释一下即可)。
public class Master {
    public void feed(Animal animal) {
        if (animal instanceof Cat) {
            Cat cat = (Cat) animal;
            cat.eat();
            cat.run();
        } else if (animal instanceof Dog) {
            Dog dog = (Dog) animal;
            dog.eat();
            dog.sleep();
        }
    }
}
  • 5.切换至MaterTest类,此时再鼠标放于feed()方法上,如下图所示,当然也可以动手调试一下。运行结果同上面的运行结果。
    这里写图片描述
  • 6.在Master中添加以下方法。
public Dog hasManyTime() {
        System.out.println("主人休闲时间比较多,适合养狗");
        return new Dog();
    }

    public Cat hasLittleTime() {
        System.out.println("主人平常比较忙,适合养猫");
        return new Cat();
    }
  • 7.修改MasterTest测试类。
public class MasterTest {
    public static void main(String[] args) {
        Master master = new Master();

        boolean isManyTime = true;
        Animal temp;
        if (isManyTime) {
            temp = master.hasManyTime();
        } else {
            temp = master.hasLittleTime();
        }
        System.out.println(temp);
    }
}
  • 8.运行代码。
    这里写图片描述
  • 9.改进Master中的代码。
public Animal raise(boolean isManyTime) {
    if (isManyTime) {
        System.out.println("主人休闲时间比较多,适合养狗");
        return new Dog();
    }else {
        System.out.println("主人平常比较忙,适合养猫");
        return new Cat();
    }
}
  • 10.修改MasterTest测试类。运行结果同上。
public class MasterTest {
    public static void main(String[] args) {
        Master master = new Master();

        boolean isManyTime = true;
        Animal temp = master.raise(isManyTime);
        System.out.println(temp);
    }
}

总结:以上两种方式各有优劣,请根据实际情况进行取舍。

4、抽象类与抽象方法

  • 1.基本概念:在面向对象的概念中,所有的对象都是通过类来描述的,但并不是说所有的类都是用来描述对象的,当一个类中没有足够的信息以描绘一个具体的对象时,这样的类就是抽象类。
  • 2.例如Animal就是一个抽象的概念,不同的子类(如猫、狗等)在吃的具体表现形式是不一样的,可提供抽象方法来被不同的子类所实现。
//抽象类Animal
public abstract class Animal {
    public abstract void eat();//抽象方法
}
  • 3.从上面的例子可以看出,抽象类是用关键字abstract修饰的。抽象类中的方法也是用abstract修饰,该方法被称为抽象方法。
  • 4.抽象类和抽象方法的特点
    • 抽象方法不允许直接实例化,换句话说就是抽象类不能创建对象,只能作为其他类的父类。但可以通过向上转型指向实例化、
    • 如上所示,抽象方法只有声明,不能有实现,也就是仅有方法头,而没有方法体和操作实现。
  • 5.定义抽象类的意义:
    • 为其子类提供一个公共的类型(父类引用指向子类对象);
    • 封装了类中重复的内容(成员变量和方法);
    • 将父类设计成抽象类后,既可以借由父子继承关系限制子类的设计随意性,在一定程度上避免了无意义父类的实例化。
  • 6.注意事项:
    • 含有抽象方法的类只能被定义为抽象类。
    • 抽象类不一定包含抽象方法。
    • 在抽象类中的成员方法可以包括一般方法和抽象方法。
      • 抽象类不能被实例化,抽象类的构造方法主要用于被其子类调用。
      • 一个类继承抽象类后,必须实现其所有抽象方法,否则也必须是抽象类,不同的子类对父类的抽象方法可以有不同的实现。
      • 即使父类是具体的,但其子类也可以是抽象的。如Object是具体的,但可以创建抽象子类。
      • 抽象方法不能用static和private修饰;对于类,不能同时用final和abstract修饰,因为final关键字使得类不可继承,而abstract修饰的类如果不可以继承将没有任何意义,两者放在一起会造成冲突。

猜你喜欢

转载自blog.csdn.net/chaixingsi/article/details/82078793