java多态解析

多态,面向对象的三大特性之一,多态的三个条件:继承,方法重写和父类引用指向子类对象。

编译看类型,运行看对象,这就是多态。但是多态仅针对方法,属性是不会有多态的,为什么呢?是故意的还是不小心?

package java_basis.polymorphism;
public class Parent {
    protected String name = "p";
    public static void main(String[] args) {
        Parent p = new Child();
        p.print();
    }
    protected void print(){
        System.out.println("this.name="+this.name);
        System.out.println("this.getName()="+this.getName());
        System.out.println("this.getClass()="+this.getClass());
    }
    public String getName(){
        return this.name;
    }

}
class Child extends Parent{
    private String name = "c";
    @Override
    public String getName() {
        return this.name;
    }
}
输出结果:
this.name=p
this.getName()=c
this.getClass()=class java_basis.polymorphism.Child

很明显,方法调用是多态,子类重写了方法,this.getName()=c。可能让人困惑的是this.name=p,下面会说一下我的一些理解。下面是字节码的输出结果

javap -verbose Parent.class
字节码结果:
 protected void print();
    descriptor: ()V
    flags: ACC_PROTECTED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: aload_0
         4: getfield      #3                  // Field name:Ljava/lang/String;
         7: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        10: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        13: aload_0
        14: invokevirtual #9                  // Method getName:()Ljava/lang/String;
        17: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        20: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        23: aload_0
        24: invokevirtual #10                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
        27: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        30: return
      LineNumberTable:
        line 9: 0
        line 10: 10
        line 11: 20
        line 12: 30
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      31     0  this   Ljava_basis/polymorphism/Parent;

可以看到this.name对应 4: getfield 指令

this.getName对应14: invokevirtual 指令

从字节码层面来说,对于属性的访问是编译期确定的,也就是引用是哪个类,this.field就是那个类,而方法调用是invokevirtual,运行期才会确定,这里会有一个方法链,一直向上找。那么对于属性的访问能否做到多态呢?我认为可以做到,但是会出很多问题,这个也破坏了封装性,下面举一个例子。

假设外部提供了一个类P3.class给你使用,你可以继承这个类,重写方法,增加新的逻辑。

P3继承了P2,然后继承了P1,然后是P0

继承链:P3 -> P2 -> P1 -> P0 -> Object

假设你写了C -> P3,你定义了一个新的属性a,如果继承链上某个父类也有一个属性a

假设父类方法里通过this.a能访问到子类的属性,这时候就有很大的问题:

1. 你并不了解所有父类的实现细节,但是你新增的属性影响了原有的逻辑,这个甚至会导致代码无法正常工作,而你只是加了个属性而已,这样写代码是不是非常痛苦

2. 父类如果通过this.a可以访问你新增的属性,那也可以修改,重写赋值,比如this.a = new Object,但是你并不知道,即使这是一个私有属性,其他类还是悄悄改变了它,出问题你甚至都没办法查找具体原因,这种情况是不是很可怕。拿spring举例,继承一个spring提供的类是很常见的作法,但spring是一个复杂的体系,继承链会很长,难道我写一段代码,要往上找,把所有的源码都看懂吗?如果旧的项目要做一次spring升级,系统就出现莫名其妙的问题,无法追溯,这太可怕了。

如果属性支持多态,那么多态就破坏了封装,私有的东西外部可以任意访问和修改,面向对象体系就崩溃了。

你可能感兴趣的:(jvm)