Java面向对象三大特性一:多态详解

多态

多态(polymorphism):指的是同一个方法调用,由于对象不同可能会有不同的行为。现实生活中,同一个方法,具体实现会完全不同。

所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。

多态的要点

  • 多态是方法的多态,不是属性的多态(多态和属性无关)

  • 多态的存在要有3个必要条件:继承、方法重写、父类引用指向子类对象

  • 父类引用指向子类对象后,用该父类引用调用子类重写的方法,多态就出现了。

多态的类型

  • 编译时多态:编译时多态是静态的,主要是指方法的重载,根据参数列表的不同来区分不同的方法调用,通过编译之后会变成两个不同的方法,并不能真正算作多态。

  • 运行时多态:主要运用到动态绑定。就是在运行时动态的确定调用的是哪一个方法。

对象的转型

父类引用指向子类对象,我们称这个过程为向上转型,属于自动类型转换。

向上转型后的父类引用变量只能调用它编译类型的方法,不能调用它运行时类型的方法。这时,我们就需要进行类型的强制转换,我们称之为向下转型。

多态的好处

  • 可替换性(substitutability):多态对已存代码具有可替换性。例如,draw函数对圆Circle类工作,对其他任何圆形几何体,如圆环,也同样工作。

  • 可扩充性(extensibility):多态对代码具有可扩充性,增加新的子类不影响已存在类的多态性、继承性以及其他特性的运行和操作,实际上新加子类更容易获得多态的功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。

  • 接口性(interface-ability):多态是超类通过方法签名,向子类提供了一个共同的接口,由子类来完善或者覆盖它而实现的。

  • 灵活性(flexibility):在应用中体现了灵活多样的操作,提高了使用效率

  • 简化性(simplicity):多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。

示例

编译时多态示例

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

方法覆盖表现出两种多态性,当对象引用本类实例时,为编译时多态,否则为运行时多态。

代码示例如下

public class Test1 {

    public static void main(String[] args) {
        Person p = new Person();
        Man m = new Man();
        System.out.println(p.toString()); //编译时多态,执行Person类的toString()
        System.out.println(m.toString()); //编译时多态,执行Man类的toString()
    }
}

class Person {
    public String toString() {
        String name = "Person";
        return name;
    }
}

class Man extends Person {
    public String toString() {
        String name = "Man";
        return name;
    }
}

输出结果为:

Person
Man

可以看出,当对象引用本类实例时,调用本类对应的方法,这就是编译时多态。

运行时多态示例

当以下父类对象p引用子类实例时,p.toString)执行哪个类的toString()方法呢,在编译时编译器无法判定

Person p = new Man();   
p.toString();

程序运行时,Java从实例所属的类开始寻找匹配的方法执行,如果当前类中没有匹配的方法,则沿着继承关系逐层向上,依次在父类或各祖先类中寻找匹配方法,直到Object类。寻找p.toString()匹配执行方法的过程如下图所示。
这里写图片描述
在看一个经典例子,参考文章:http://www.cnblogs.com/chenssy/p/3372798.html

代码示例如下

public class A {
    public String show(D obj) {
        return ("A and D");
    }

    public String show(A obj) {
        return ("A and A");
    } 

}

public class B extends A{
    public String show(B obj){
        return ("B and B");
    }

    public String show(A obj){
        return ("B and A");
    } 
}

public class C extends B{

}

public class D extends B{

}

public class Test {
    public static void main(String[] args) {
        A a1 = new A();
        A a2 = new B();
        B b = new B();
        C c = new C();
        D d = new D();

        System.out.println("1--" + a1.show(b));
        System.out.println("2--" + a1.show(c));
        System.out.println("3--" + a1.show(d));
        System.out.println("4--" + a2.show(b));
        System.out.println("5--" + a2.show(c));
        System.out.println("6--" + a2.show(d));
        System.out.println("7--" + b.show(b));
        System.out.println("8--" + b.show(c));
        System.out.println("9--" + b.show(d));      
    }
}

输出结果为:

1--A and A
2--A and A
3--A and D
4--B and A
5--B and A
6--A and D
7--B and B
8--B and B
9--A and D

这个结果开始没有完全答对,在结合大神chenssy关于这块的分析后,终于弄懂了,将这些结果逐一分析,如有不对,欢迎拍砖~

这里有句话是这样的:当超类对象引用变量引用子类对象时,是由被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。例如System.out.println("4--" + a2.show(b))这行代码,其中a2为超类对象引用变量,new B()为子类对象,即a2引用new B(),那么a2.show(b)是由被引用对象的类型(即B类)来决定调用谁的成员方法,根据上面第一个示例中提到的运行时多态中有提到:Java从实例所属的类开始寻找匹配的方法执行,那么先从B类中找有show(b)匹配的方法。

其实在继承链中对象方法的调用存在一个优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。

那么,按照这个优先级我们来逐一分析每一个输出结果:

  • 输出(1–A and A):a1.show(b),a1是A类型的引用变量,所以this就代表了A,a1.show(b),B的超类是A,所以在A中找show(A obj),因此结果就是A and A

  • 输出(2–A and A):a1.show(c),a1是A类型的引用变量,所以this就代表了A,a1.show(b),C的超类有A和B,所以在A中找show(A obj),因此结果就是A and A

  • 输出(3–A and D):a1.show(d),a1是A类型的引用变量,所以this就代表了A,a1.show(d),在A中找show(D obj),因此结果就是A and D

  • 输出(4–B and A):a2.show(b),a2是A类型的引用变量,所以this就代表了A,a2.show(b),它在A类中找发现没有找到,于是到A的超类中找(super),由于A没有超类(Object除外),所以跳到第三级,也就是this.show((super)O),B的超类是A,所以(super)O为A,this同样是A,这里在A中找到了show(A obj),同时由于a2是B类的一个引用且B类重写了show(A obj),因此最终会调用子类B类的show(A obj)方法,结果也就是B and A。按照上面给出的继承链中对象方法的调用存在一个优先级为:A.show(B obj) –> A.show(B的超类即A) –> 父类引用指向子类对象(B.show(A))

  • 输出(5–B and A):a2.show(c),a2是A类型的引用变量,所以this就代表了A,a2.show(c),它在A类中找发现没有找到,于是到A的超类中找(super),由于A没有超类(Object除外),所以跳到第三级,也就是this.show((super)O),C的超类有B、A,所以(super)O为B、A,this同样是A,这里在A中找到了show(A obj),同时由于a2是B类的一个引用且B类重写了show(A obj),因此最终会调用子类B类的show(A obj)方法,结果也就是B and A。按照上面给出的继承链中对象方法的调用存在一个优先级为:A.show(C obj) –> A.show(C的超类即B、A) –> 父类引用指向子类对象(B.show(A))

  • 输出(6–A and D):a2.show(d),a2是A类型的引用变量,所以this就代表了A,a2.show(d),这里在A中找到了show(D obj)方法,结果也就是A和D。

  • 输出(7–B and B):b.show(b),b是B类型的引用变量,所以this就代表了B,b.show(b),它在B类中找到了show(B obj)方法,结果也就是B and B

  • 输出(8–B and B):b.show(c),b是B类型的引用变量,所以this就代表了B,b.show(c),C的超类是B,它在B类中没找到了show(c)方法,于是到A的超类中找show(c),发现也没有找到对应的方法,最后在B类中找到了show(B obj)方法,结果也就是B and B。按照上面给出的继承链中对象方法的调用存在一个优先级为:B.show(C obj) –> A.show(C obj) –> B.show(C的超类即B)

  • 输出(9–A and D):b.show(d),b是B类型的引用变量,所以this就代表了B,b.show(d),它在B类中没找到了show(d)方法,于是到A的超类中找show(d)方法,最终找到了,结果也就是B and B。按照上面给出的继承链中对象方法的调用存在一个优先级为:B.show(D obj) –> A.show(D obj)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值