名词解释
- 个人见解:“多态”,即多种子类形态。
- 《Java编程思想》中的总结:多态意味着“不同的形式”。在面向对象的程序设计中,我们持有从基类继承而来的相同接口,以及使用该接口的不同形式:不同版本的动态绑定方法。
语言设计
后期绑定
- 在运行时,根据对象的类型进行绑定。
- 编译器一直不知道对象的类型信息。
- 必须在对象中安置某种“类型信息”(原文)
- Java除static方法和final方法(private方法属于final方法)之外,其他所有的方法都是后期绑定。
域和静态方法
域和静态方法不具有多态性。多态只针对普通方法调用。
域
class Super{
public int filed = 0;
public int getFiled(){ return filed; }
}
class Sub extends Super{
public int filed = 1;
public int getFiled(){ return filed; }
public int getSuperFiled(){ return super.filed; }
}
public class FiledAccess{
public static void main(String[] args){
Super super = new Sub()
// Output super.filed = 0,super.getFiled() = 1
System.out.println("super.filed = " + super.filed + "," + "super.getFiled() = " + super.getFiled());
Sub sub = new Sub()
// Output sub.filed = 1,sub.getFiled() = 1
System.out.println("sub.filed = " + sub.filed + "," + "sub.getFiled() = " + sub.getFiled());
}
}
任何域访问操作都是由编译器解析的,因此不是多态
在实际开发中,一般声明成员是private,或者父类和子类的域的名称不同。所以一般不会出现这种情况。但是列举在这里,有助于理解多态的后期绑定是怎么回事。
静态方法
同样的,静态方法也不具有多态性。 静态方法是与类。而并非与单个的对象相关联的。
构造器和多态
构造函数是一个静态函数,不具有多态性。
构造器的调用顺序
在构造器内部,我们必须确保所有使用的成员都已经构建完毕。为确保这一目的,唯一的办法是首先调用基类构造器。
类的初始化顺序
- 访问类的任何static成员或者调用构造器,都导致了类及其父类的加载。
- 类加载后,初始化类及其父类的static成员。
- 如果是调用了构造器,则从基类到子类,顺序初始化对象的成员和执行构造方法。
public class Glyph {
void draw(){
System.out.println("Glyph draw");
}
Glyph(){
System.out.println("Glyph before draw");
draw();
System.out.println("Glyph after draw");
}
}
public class RoundGlyph extends Glyph {
private int radius = 1;
public RoundGlyph(){
System.out.println("RoundGlyph radius:" + radius);
}
@Override
void draw(){
System.out.println("RoundGlyph radius:" + radius);
}
}
public class Test1 {
public static void main(String[] args) {
new RoundGlyph();
}
}
/**
Output
Glyph before draw 调用导出类的构造方法,先调用了基类的构造方法
RoundGlyph radius:0 基类调用了被子类覆盖的方法。此时导出类的构造方法还没执行,成员还未初始化
Glyph after draw
RoundGlyph radius:1 导出类构造方法执行,成员的初始化,在构造函数的第一行代码执行前已完成。
*/
字节码层面看构造
public RoundGlyph();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method Glyph."<init>":()V
4: aload_0
5: iconst_1
6: putfield #2 // Field radius:I
9: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
12: new #4 // class java/lang/StringBuilder
15: dup
16: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V
19: ldc #6 // String RoundGlyph radius:
21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: aload_0
25: getfield #2 // Field radius:I
28: invokevirtual #8 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
31: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
34: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
37: return
从字节码来看,也证实了,类的成员的赋值初始化操作,在执行对象初始化的时候,在字节码层面是init()方法,顺序:基类成员初始化-》基类构造函数执行-》导出类成员初始化-》导出类构造函数执行。
Java是如何实现多态的呢?
区别重写和重载
- 重写是多态的行为,发生在类的继承关系中。子类重写父类的方法,名为override。
- 重载是类内部的行为,声明多个方法名相同,参数列表不同的的方法,名为overload。
- 在运行时,才能由JVM确定,对象实际调用的是哪个重写的方法。
- 在编译时,就能区别出调用的是哪个重载的方法,因为方法签名不一样。
运行时多态是如何实现的
上面说到
必须在对象中安置类型信息
那就是RTTI(Runtime Type Identification),运行时类型信息。