多态

多态

java多态的具体体现是父类型引用指向子类型对象,由于程序执行时的动态绑定,导致了同一种行为(方法)产生了不同的效果。下面通过一个常被拿来用的多态相关的例子,来加强对多态的理解。

例题

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

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

class C extends B{
}

class D extends B{
}

问题

		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)); //1
        System.out.println("2--" + a1.show(c)); //2
        System.out.println("3--" + a1.show(d)); //3
        System.out.println("4--" + a2.show(b)); //4
        System.out.println("5--" + a2.show(c)); //5
        System.out.println("6--" + a2.show(d)); //6
        System.out.println("7--" + b.show(b));  //7
        System.out.println("8--" + b.show(c));  //8
        System.out.println("9--" + b.show(d));  //9

上述代码的输出结果?

答案

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

分析

我找到了网上对于这个例题的解析的一个大众的观点

当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。这句话对多态进行了一个概括。其实在继承链中对象方法的调用存在一个优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。

就我自己理解而言,这个优先级的说法存在一些问题:那就是a2.show(a1)会出现什么结果?按优先级分析的结果是A and A,但是正确结果是 B and A,这样来看这个优先级的说法只在编译阶段有效。其实,就编译阶段而言,其也具有一定的迷惑性,这个在文章后边部分进行说明。下面我根据自己的理解从程序的编译运行角度来分析这个例题。

首先,我们需要了解一下多态涉及的几个概念:

  • 向上转型(upcasting)
    子类型–>父类型
    又被称为:自动类型转换。
  • 向下转型(downcasting)
    父类型–>子类型
    又被称为:强制类型转换。
  • 无论是向上转型还是向下转型,都需要两种类型之间必须要有继承关系。

java程序永远都分为编译阶段和运行阶段。编译无法通过,根本无法运行。编译阶段编译器检查引用的数据类型,当此类型中存在调用的方法时,编译通过,这个过程称为静态绑定,编译阶段绑定。只有静态绑定成功才有后续运行。运行阶段,JVM堆内存中存储的真实对象是new的子对象,运行阶段调用的方法也是子对象的方法,此时发生了程序的动态绑定,运行阶段绑定。

之后,我们进行程序的分析:
我们知道,继承时,子类会拥有父类的所有属性和方法,当然父类的私有属性和方法子类是无法访问的。B类继承了A类但是重写了show(A obj)方法,所以在这个程序中,实际上现在D、C、B类中包含的方法有:

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

对于a1.show(b):a1是A类型的引用数据类型,b是B类型的引用数据类型。编译阶段,会查找A类中的方法来进行匹配,没有show(B obj)的方法,此时会进行自动类型转换B->A,所以匹配到show(A obj),此时进行了静态绑定。运行阶段,堆内存中存储的真实对象是A类型,所以会调用A的show(A)方法,显示结果解就是 A and A。a1.show( c )和a1.show(d)也是一样。

对于a2.show(b):a2是A类型的引用数据类型,b是B类型的引用数据类型。编译阶段,会查找A类中的方法来进行匹配,没有show(B obj)的方法,此时会进行自动类型转换B->A,所以匹配到show(A obj),此时进行了静态绑定。运行阶段,堆内存中存储的真实对象是B类型,所以会调用B的show(A)方法,显示结果解就是 B and A。a2.show( c )是一样的,a2.show(d)在编译阶段不需要类型转换,直接能匹配到show(D obj)方法,运行时调用B类的show(D obj)方法。剩余的几个也采用同样的分析即可。

最后,我还要说一下在使用多态时的自动类型转换的注意点,那就是逐级转换。我们可以在A类中添加一个方法:

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

在B类中也添加一个方法:

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

此时b.test( c )会输出什么?答案是test–test–A and B。B类继承A类,所以也包含了test(B obj)方法,此时传入C类型的引用,匹配不到,就去逐级进行自动类型转换,C–>B,之后就直接匹配到了。

关于最后一点,我需要说一下分析开始处提到过的优先级的问题。
展示一下那个优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
注意:在利用这个优先级进行分析时,默认了每个类中的方法就是可以直接看到的在类内定义的方法,不包含继承的方法(这只是用来分析的观察角度,实际上子类是继承了父类所有的方法)。

在最后一个例子中,按照对应关系编译时查找的顺序为:B类中的test( C )(无)–>A类中的test( C )(无)–>B类中的test(B)(无)–>???。之后应该是A类中的test(B),还是B类中的B类中的test(A)?从结果上来看明显是A类中的test(B),这就说明了每次super后都要按顺序进行this.show((super)O)、super.show((super)O)这两者的判断。还有,对于第二级super.show(O),需要将引用的所有父类型查找完毕后,才继续下一级,这是因为子类继承父类是拥有了父类所有的方法,父类的方法就是子类的方法。
从上述的分析中可以看出涉及到这种复杂的判断时,这个优先级无疑变更具迷惑性。所以,判断多态时的程序执行结果,还是直接按照编译–>运行的分析,一步步来,更直观和准确。

结论

判断多态时的程序执行结果:

  1. 找出所有子类所拥有的所有方法;
  2. 判断编译阶段所绑定的方法(即引用所属的类中的方法);
  3. 判断运行阶段所绑定的方法(即实际new的对象所属的类中第2步所绑定的同名方法);
  4. 根据实际调用的方法得出最终结果。

完整代码

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

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

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


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

class C extends B{
}

class D extends B{
}

class Ttest {
    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)); //1
        System.out.println("2--" + a1.show(c)); //2
        System.out.println("3--" + a1.show(d)); //3
        System.out.println("4--" + a2.show(b)); //4
        System.out.println("5--" + a2.show(c)); //5
        System.out.println("6--" + a2.show(d)); //6
        System.out.println("7--" + b.show(b));  //7
        System.out.println("8--" + b.show(c));  //8
        System.out.println("9--" + b.show(d));  //9
        
        System.out.println("0--" + a2.show(a1));
        System.out.println("test--" + b.test(c));
    }
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值