多态,面向对象的三大特性之一,多态的三个条件:继承,方法重写和父类引用指向子类对象。
编译看类型,运行看对象,这就是多态。但是多态仅针对方法,属性是不会有多态的,为什么呢?是故意的还是不小心?
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升级,系统就出现莫名其妙的问题,无法追溯,这太可怕了。
如果属性支持多态,那么多态就破坏了封装,私有的东西外部可以任意访问和修改,面向对象体系就崩溃了。