泛型与多态

2022/02/15

多态

多态是面向对象编程语言中的重要特征,它允许基类的指针或引用指向派生类的对象,而在具体访问时实现方法的动态绑定,从而提供了另一维度的接口与实现分离。

Java中方法调用有两类,动态方法调用和静态方法调用。而多态就是属于动态方法调用。

方法调用绑定

将一个方法调用和方法主体关联起来称作绑定。若绑定发生在程序运行前,叫做前期绑定。当父类引用指向子类对象,或形参为父类对象但实参为子类对象时,编译器就无法得知具体调用的是哪个方法。此时就引入了动态绑定。结果就是编译器仍然不知道对象的类型,但是方法调用机制能找到正确的方法并调用。

我们知道final关键字可以防止方法被重写,也因此它关闭了“动态绑定”,通常情况下Java中出了static和final方法,其它所有的方法都是动态绑定。

重载

重载不完全属于多态,和多态的概念之间有交叉。

在Java中重载一个方法,除了名字要与原方法相同以外,还要有一个不同的方法签名,签名是方法参数在常量表中的引用集合。

特别注意

  1. List 与 List 在泛型擦除后,都是 List,是相同参数;
  2. 在class文件中,返回值是方法签名的一部分,返回值不同,可以共存于一个class中。

泛型

泛型的本质是为了参数化类型,在泛型的使用过程中,操作的数据类型被指定为一个参数,这种参数类型何以用在类、接口和方法中。

泛型的意义在于编写更通用的代码。

泛型擦除

如果把C++中模板看作真整的泛型,那Java的泛型则是不彻底的。Java设计之初并没有考虑泛型,在1.5以后才引入了泛型概念,为了兼容性,Java采用了擦除实现泛型。

基于擦除的实现中,泛型类型只有在静态检测期间才出现,在此之后,Java中所有的泛型都会被替换为非泛型上界。

泛型擦除与桥方法

由于泛型信息在编译时会被擦除,在多态的实现中,方法绑定会出现问题,观察下面的代码示例:

class Holder<T>{
    private T value;
    public T get(){
        return value;
    }
    public void set(T value){
        this.value = value;
    }
}

class Simple{

}

class SimpleHolder extends Holder<Simple>{

    @Override
    public Simple get() {
        return super.get();
    }

    @Override
    public void set(Simple value) {
        super.set(value);
    }
}

对于Holder类的泛型参数在类型擦除后都会变成Object

class Holder<Object>{
    private Object value;
    public Object get(){
        return value;
    }
    public void set(Object value){
        this.value = value;
    }
}

而在子类 SimpleHolder 中的 get/set 方法的参数却是Simple,方法的签名已经不匹配。为了解决这个问题,维持泛型类型的多态性,java编译器会生成一个桥方法:

class SimpleHolder extends Holder<Simple>{

    public Object get() {
        return get();
    }

    public void set(Object value) {
        set((Simple)value);
    }
}

留言: