Java面向对象--三大基本特征之多态性

半城伤御伤魂 提交于 2020-03-18 21:29:37

Java面向对象的三大基本特征是:封装(Encapsulation)、继承(Inheritance)和多态(Polymorphism),这三大基本特征是学习Java面向对象开发的基础和重点。本篇博客将详细介绍这三大基本特征中多态性的用法。


☍ 多态性(Polymorphism)

多态性,是面向对象中最重要的概念,也是三大基本特征中最难的一个。简单的理解就是同一个行为具有多个不同表现形式或形态的能力,或者说多态就是同一个接口,使用不同的实例而执行不同操作。

Java多态

▴ 对象的多态性

☃ 父类的引用指向子类的对象(可以直接应用在抽象类和接口上)

☃ 子类的对象可以替代父类的对象使用

  ☄ 一个变量只能有一种确定的数据类型

  ☄ 一个引用类型变量可能指向(引用)多种不同类型的对象

Person p = new Student();  //父类Peson变量p指向Student对象

▴ Java引用变量有两个类型

☃ 编译时类型:由声明该变量时使用的类型决定(看‘=’左边)

☃ 运行时类型:由实际赋给该变量的对象决定(看‘=’右边)

//父类 --> 子类    Person-->Student-->GirlStudent
//                                -->BoyStudent
编译时类型   Person p1 = new Student();   运行时类型
           Person p2 = new GirlStudent();
           Person p3 = new BoyStudent();
           Student s1 = new GirlStudent();
           Student s2 = new BoyStudent();

◌ 若编译时类型和运行时类型不一致,就出现了对象的多态性

▴ 多态情况

☃ 看左边:看的是父类的引用,ctrl + 鼠标选中指定方法进入的是父类(父类中不具备子类特有的方法)

☃ 看右边:看的是子类的对象(实际运行的是子类重写父类的方法)

✦ 通过多态创建的对象拥有父类全部的属性和方法以及子类重写父类的方法,但是不具备子类特有的方法

✦ 多态是针对方法重写的,对于属性没用,最终调用的依然是父类的属性(即使存在同名属性)

public class Test{
    public static void main(String args[]){
        Person p = new Student();
        System.out.println(p.id);   //输出为1111
    }
}
class Person{
    int id = 1111;
}
class Student extends Person{
    int id = 1122;
}

▾ 向上转型(upcasting)

☃ 子类可看做是特殊的父类 , 所以父类类型的引用可以指向子类的对象:向型上转型(upcasting)

☃ 向上转型时,父类变量指向子类对象,但是却无法使用子类特有的方法和属性,能够使用子类重写父类的方法

▴ 多态性的应用场景

☃ 多态性的使用是虚拟方法调用(Virtual Method Invocation)

☃ 虚拟方法调用:子类中定义了与父类同名同参数的方法(方法的重写),在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的

☃ 方法声明的形参类型为父类类型,可以使用子类的对象作为实参调用该方法

public class AnimalTest {
    public static void main(String[] args) {
        AnimalTest test = new AnimalTest();
        test.func(test.getInstance(1));
    }
    //动态返回Animal对象(多态)
    public static Animal getInstance(int key) {
        switch (key) {
        case 1:
            return new Cat();
        case 2:
            return new Dog();
        default:
            return null;
        }
    }
    //掉用对象方法
    public void func(Animal animal) {
        animal.eat();
        animal.shout();
    }
}
//Animal类
class Animal{
    public void eat() {
        System.out.println("动物吃东西");
    }
    public void shout() {
        System.out.println("动物叫");
    }
}
//Dog类继承Animal
class Dog extends Animal{
    public void eat() {
        System.out.println("狗吃骨头");
    }
    public void shout() {
        System.out.println("汪汪汪");
    }
    public void dog() {
        System.out.println("🐕很忠诚");
    }
}
//Cat类继承Animal
class Cat extends Animal{
    public void eat() {
        System.out.println("猫吃🐟");
    }
    public void shout() {
        System.out.println("喵喵喵");
    }
    public void cat() {
        System.out.println("🐱很可爱");
    }
}

✦ 多态是运行时行为而不是编译时行为

▴ 从编译和运行的角度看重载、重写和多态

☃ 重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法 同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类
和子类的,即子类可以重载父类的同名不同参数的方法。
所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,
这称为“早绑定”或“静态绑定”

☃ 而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法(通过方法的重写实现),这称为“晚绑定”或“动态绑定”

✦ 引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚绑定,它就不是多态。”

▴ 多态总结

☃ 多态作用:提高了代码的通用性,常称作接口重用

☃  前提:需要存在继承或者实现关系,有方法的重写

☃ 成员方法:

  ☄ 编译时:要查看引用变量所声明的类中是否有所调用的方法

  ☄ 运行时:调用实际new的对象所属的类中的重写方法。

☃ 成员变量:不具备多态性,只看引用变量所声明的类

▴ 多态方式创建对象调用子类特有的属性和方法

有了对象的多态性后,运行时内存中实际加载了子类特有的属性和方法,但是由于变量声明为父类类型,导致编译时只能调用父类中声明国的属性和方法,子类特有的属性和方法不能调用。

▾ 向下转型(lowcasting)

☃ 父类对象转换为子类对象,通过强制类型转换实现

//Man man = (Man)new Person(); ClassCastException异常
//Person person = new Person();
//Man man1 = (Man)p;  ClassCastException异常
/*向下转型要求子类变量要与强制转换的父类核心上要一致,即经过向上转型的父类变量才能
  进行向下转型,不能直接将父类对象强转后赋给子类变量(子类引用不能指向父类对象)*/
Person p = new Man();
//向下转型
Man m = (Man)p;
//m可以调用Man类所有的属性和方法了

☃ 有人会问为什么不开始时就声明为子类而是通过向下转型,因为在实际开发中方法声明的是父类对象形参,未使用时还不确定具体是使用哪个子类对象,当确定子类对象时要使用子类特有的成员便可以通过向下转转型实现了

➥ 从子类到父类的类型转换可以自动进行

➥ 从父类到子类的类型转换必须通过造型( 强制类型转换实现)

➥ 无继承关系的引用类型间的转换是非法的

➥ 向下转型可以使用父类的方法和属性,还可以使用子类特有的方法和属性,如果子类有和父类同名的属性则调用子类的属性,如果子类重写了父类的方法,则调用子类重写后的方法

数据转型

✦ 父类引用可以指向子类对象,而子类引用不能指向父类对象。

✦ 把子类对象直接赋给父类引用叫upcasting向上转型,向上转型不用强制转换;把指向子类对象的父类引用赋给子类引用叫向下转型(downcasting),要强制转换。

✦ 使用强制转型会有风险,基本数据类型的强制转型会导致数据精度损失,类的强制转换可能会出现ClassCastException异常(非从父类转换子类也非子类转换为父类,如Man和Woman都继承子Person但是两者不可向上转型也不可向下转型)。

▾ instanceof关键字的使用

☃ 引入instanceof关键字的目的就是为了防止出现ClassCastException异常,在使用类的强制类型转换时使用

☃ 语法:x instanceof A(检查x变量是否是类A的对象,返回值为boolean型)

☃ 要求x所属的类与类A必须是同类、子类和父类(包括间接父类)的关系,否则编译错误

☃ 如果x属于类A或类A的子类B,x instanceof A值为true;如果x属于类A的父类C,x instanceof A值为false

public void method1(Person e){
    if(e instanceof Person){
        //处理Person类及其子类对象
    }
    if(e instanceof Man){
        //处理Man类及其子类对象
    }
    if(e instanceof Student){
        //处理Student类及其子类对象
    }
}
/*
    class Animal{}
    class Dog extends Animal{}
    calss Cat extends Animal{}
*/
Animal animal = new Cat();
//向下转型(类的强制类型转换):instanceof判断引用类型变量所属类是否与A类是同类或者子类关系
//Dog dog = (Dog) animal;  //ClassCastException异常,不构成子父类关系
Animal animal2 = new Dog();
Animal animal3 = new Animal();
Cat cat = (Cat)animal;
Dog dog = (Dog)animal2;
//向上转型多态x-A构成子类关系true
System.out.println(animal instanceof Animal);  
//向上转型多态构成x-A同类关系true
System.out.println(animal instanceof Cat);  
//x-A同类关系true
System.out.println(animal3 instanceof Animal); 
//向下转型x-A同类关系true
System.out.println(cat instanceof Cat);  
//向下转型x-A子类关系true
System.out.println(dog instanceof Animal);
//向下转型x-A父类关系false
System.out.println(animal3 instanceof Dog); 

//自动类型转换
Cat cat2 = new Cat();
Animal animal4 = cat2;
System.out.println(cat2 == animal4);  //true
//animal4.cat();  编译错误

本博客与CSDN博客༺ཌ༈君☠纤༈ད༻同步发布

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!