跳至主要內容

Java泛型

pptg大约 3 分钟

1. 基本使用

Java为了代码复用,设计了泛型以让多种数据类型执行相同的代码,使用场景如下:

// 1. 类
class Container<T>{}
class Container<K,V>{}

// 2. 接口
interface Info<T>{
    public T getVar();
}

// 3. 方法
public <T> T getObject(Class<T> c) {}
xxx.getObject(Class.forName("com.xx.xx"));

泛型方法的意义在于:泛型类要在实例化的时候就指明类型,如果想换一种类型,不得不重新new一次,可能不够灵活;而泛型方法可以在调用的时候指明类型,更加灵活

2. 上下限

下面代码中,B是A的子类,所以funcA(b)满足了里氏替换原则,不会报错。但下面用泛型的则会报错

class A{}
class B extends A {}

// 如下两个方法不会报错
public static void funA(A a) {
    // ...          
}
public static void funB(B b) {
    funA(b);
    // ...             
}

// 如下funD方法会报错
public static void funC(List<A> listA) {
    // ...          
}
public static void funD(List<B> listB) {
    funC(listB); 
    // ...             
}

此时使用extends即可解决,表示泛型可以是A或者A的子类

public static void funC(List<? extends A> listA) {
    // ...          
}
public static void funD(List<B> listB) {
    funC(listB); // OK
    // ...             
}

同理,<? super A>代表A的自身或者其父类

3. 类型擦除

Java中的泛型是伪泛型,在编译阶段会进行类型擦出(Type Erasure),其原则是:

  • 消除类型参数声明,即删除<>及其包围的部分
  • 根据类型参数的上下界替换类型为原生态类型:如果无上下界则替换为Object,如果存在上下界则根据子类替换原则取类型参数的最左边限定类型(即父类)
  • 为了保证类型安全,必要时插入强制类型转换代码
  • 自动产生“桥接方法”以保证擦除类型后的代码仍然具有泛型的“多态性”

注意泛型不能在静态变量或方法上使用,因为泛型是实例化时指定的

例如:

// 擦除前
public Info<T extends Number> {
    private T value;
}

// 擦除后
public Info {
    private Number value;
}

4. 桥接方法

对于下面的泛型类及其子类,因为存在类型擦除,所以PairT实际上变成了Object,而此时DateInter因为DateObject不匹配,理论上并不能算作是重写(因为参数类型变了,所以是重载)。

class Pair<T> {  
    private T value;  

    public T getValue() {  
        return value;  
    }  
}

class DateInter extends Pair<Date> {  

    @Override  
    public Date getValue() {  
        return super.getValue();  
    }  
}

但实际上不是这样的,因为编译器为我们在子类自动生成了桥方法,我们调用的Date getValue会自动通过Object getValue去调用父类

class com.tao.test.DateInter extends com.tao.test.Pair<java.util.Date> {  
  com.tao.test.DateInter();  
    Code:  
       0: aload_0  
       1: invokespecial #8                  // Method com/tao/test/Pair."<init>":()V  
       4: return  

  public java.util.Date getValue();    //我们重写的getValue方法  
    Code:  
       0: aload_0  
       1: invokespecial #23                 // Method com/tao/test/Pair.getValue:()Ljava/lang/Object;  
       4: checkcast     #26                 // class java/util/Date  
       7: areturn  

  public java.lang.Object getValue();     //编译时由编译器生成的桥方法  
    Code:  
       0: aload_0  
       1: invokevirtual #28                 // Method getValue:()Ljava/util/Date 去调用我们重写的getValue方法;  
       4: areturn  
}