Java多态深入

一、前言

Java 多态的3个必要条件是:1.要有继承;2.要有方法的重写;3父类引用指向子类的对象

我对这三点深信不疑。但是,很多博客都说多态分为两种,一种是编译时多态,还有一种是运行时多态。其实多态用的最多的还是运行时多态,也称作动态绑定,既然很多地方都提到了这两种,那就都来总结一下


二、编译时多态

方法重载都是编译时多态,根据实际参数的数据类型、个数和次序,Java在编译时能够确定执行重载方法中的哪一个。

如果对应引用都是指向本类实例对象的时候,即为编译时多态。如下面的 people,指向它的类实例,然后去类中去寻找重载的方法

class People {
    public People() {
        System.out.println("people constructor");
    }

    public void print() {
        System.out.println("This is people");
    }
}

class Man extends People {
    public Man() {
        System.out.println("man constructor");
    }

    //语句1
    public void print() {
        System.out.println("This is man");
    }
}

public class Test2 {
    public static void main(String[] args) {
        People people = new People();
        people.print();
    }
}

注意一个细节,如果我把 People 类里的 print() 方法删除,就会报错,说明 People 类只是在当前类中去寻找重载方法


三、运行时多态

运行时多态指系统根据调用该方法的实例类型来决定选择调用哪个方法(多态指的基本就是运行时多态)

1.

简单的说,调用哪个方法,是根据 new 后面那个类型来判断的,都是调用 new 后面那个类(子类)覆盖的方法

我们把上面的例子改一下,主方法里面改成这样,同时把 Man 类里面的 print 方法暂时注释(语句1),我们调用以下方法

Man man = new Man();
man.print();

结果是:

people constructor
man constructor
This is man

这是为什么呢?如果按照上面所说的编译时方法,去找当前类的重载时方法,那么很显然会报错,可是这里却明应报错,反而去了它的父类 People 中去寻找有没有匹配的类,显然这一次是找到了

我们在看一个例子,同样把语句1给暂时注释

People man1 = new Man();
man1.print();

结果还是

people constructor
man constructor
This is man

可见此时执行的还是父类的方法。程序运行时,Java 先从所属的类中寻找匹配的方法,如果当前没有定义该方法,则沿着继承关系逐层向上寻找,依次在父类或者祖先类中寻找匹配方法,找到后便输出,没找到则报错。该过程就称为动态绑定

其实用的最多的一种运行时多态就是 toString 方法,因为该方法是 Object 类的,因为每个类都隐式的继承 Object 类,我们可以看一个例子

class Animal {
    @Override
    public String toString() {
        return "animal";
    }
}

class Bird extends Animal {
    //语句1
    /*@Override
    public String toString() {
        return "bird";
    }*/
}

public class Test3 {

    public static void main(String[] args) {
        Bird bird = new Bird();
        String s = bird.toString();
        System.out.println(s);
    }

}

结果是:animal,如果语句1正常存在,结果是:bird。说明该类会一层一层的向上寻找是否有类重写了 toString 方法,如果有,就按照重写的输出,如果没有,就直接到 Object 类的 toString 方法

2.

如果父类的 print 方法是 static 类型的会如何,我们同样看一个例子

class Animal {

    public String type = "A";

    public void printOther() {
        System.out.println("This is animal");
    }

    public static void print() {
        System.out.println("This is static animal");
    }
}

class Bird extends Animal {

    public String type = "B";

    public void printOther() {
        System.out.println("This is bird");
    }

    public static void print() {
        System.out.println("This is static bird");
    }
}

public class Test3 {

    public static void main(String[] args) {
        Animal animal = new Bird();
        System.out.println(animal.type);
        animal.printOther();
        animal.print();

        Bird bird1 = new Bird();
        System.out.println(bird1.type);
        bird1.printOther();
        bird1.print();
    }

}

结果是:

A
This is bird
This is static animal
B
This is bird
This is static bird

很奇怪,为什么同样的两个方法名,结果却不同呢?

这里就要牵扯到两个概念,隐藏覆盖

覆盖是针对非静态方法的,而隐藏是针对成员变量和静态方法的,只有覆盖才会发生动态绑定,而隐藏则不会发生动态绑定

当发生隐藏的时候,是和类的声明类型有关的,声明类型是什么类,就调用该声明类的属性和方法,而不会发生动态绑定,属性只能被隐藏,不能被覆盖


四、向上、向下转型

**向上转型:**子类引用的对象转换为父类类型,此时父类对象可以是接口
**向下转型:**父类引用的对象类型转为子类类型

4.1 向上转型

但是需要注意的是,如果子类单独定义一个方法,且该方法在父类中没有,那么此时父类的对象是访问不到该方法的

直接通过例子来看

class Animal {
    public void name() {
        System.out.println("this is animal");
    }
}

class Dog extends Animal {
    public void name() {
        System.out.println("this is dog");
    }

    public void run() {
        System.out.println("dog run");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog();   //语句1 
        animal.name();   //语句2
//      animal1.run();   //语句3
    }
}

结果是:this is dog

此时把子类 Dog 类型向上转为了它的父类 Animal 类型,因为 name 方法在父类中已经定义了,因此子类相当于重写父类方法,此时调用的子类重写后的方法。而子类中的 run 方法,因为是单独存在的,父类中没有,那么父类对象访问这个方法,因此语句3会报错

4.2 向下转型

在向下转型过程中,分为两种情况

情况一:如果在转之前,父类的引用已经指向子类对象了,那么必然可以向下转型

情况二:如果父类引用的对象依然是父类本身,那么即使该引用调用了子类的方法,编译时不报错,也会在运行时报错

Animal animal = new Dog();

//语句1
Dog dog = (Dog) animal;
dog.name();
dog.run();

//语句2
Animal animal = new Animal();
Dog dog1 = (Dog) animal;
dog1.name();
dog1.run();

语句1可以指向成功,结果是:this is dog,dog tun。这个很好理解,本身 animal 引用就是 Dog 类型的,它当然可以向下转型成 Dog 类型了,此时调用的当然就是 Dog 类的方法

语句2因为 animal1 一直都是 Animal 类,因此也不能转为它的子类 Dog 类,即使 Dog 类是它的子类,此时虽然编译的时候没有错误,但是允许的时候,会报 java.lang.ClassCastException: edu.just.inherit.Animal1 cannot be cast to edu.just.inherit.Dog 这个错误


五、参考

https://www.cnblogs.com/liujinhong/p/6003144.html
https://blog.csdn.net/why_still_confused/article/details/51295707
https://blog.csdn.net/snow_7/article/details/51579278
https://www.cnblogs.com/dolphin0520/p/3803432.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值