多态

210 阅读4分钟

名词解释

  1. 个人见解:“多态”,即多种子类形态。
  2. 《Java编程思想》中的总结:多态意味着“不同的形式”。在面向对象的程序设计中,我们持有从基类继承而来的相同接口,以及使用该接口的不同形式:不同版本的动态绑定方法。

语言设计

后期绑定

  1. 在运行时,根据对象的类型进行绑定。
  2. 编译器一直不知道对象的类型信息。
  3. 必须在对象中安置某种“类型信息”(原文)
  4. 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,或者父类和子类的域的名称不同。所以一般不会出现这种情况。但是列举在这里,有助于理解多态的后期绑定是怎么回事。

静态方法

同样的,静态方法也不具有多态性。 静态方法是与类。而并非与单个的对象相关联的。

构造器和多态

构造函数是一个静态函数,不具有多态性。

构造器的调用顺序

在构造器内部,我们必须确保所有使用的成员都已经构建完毕。为确保这一目的,唯一的办法是首先调用基类构造器。

类的初始化顺序

  1. 访问类的任何static成员或者调用构造器,都导致了类及其父类的加载。
  2. 类加载后,初始化类及其父类的static成员。
  3. 如果是调用了构造器,则从基类到子类,顺序初始化对象的成员和执行构造方法。
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是如何实现多态的呢?

区别重写和重载

  1. 重写是多态的行为,发生在类的继承关系中。子类重写父类的方法,名为override。
  2. 重载是类内部的行为,声明多个方法名相同,参数列表不同的的方法,名为overload。
  3. 在运行时,才能由JVM确定,对象实际调用的是哪个重写的方法。
  4. 在编译时,就能区别出调用的是哪个重载的方法,因为方法签名不一样。

运行时多态是如何实现的

上面说到

必须在对象中安置类型信息

那就是RTTI(Runtime Type Identification),运行时类型信息。