泛型是什么?泛型的英文是 generics,generic 的意思是通用,而翻译成中文,泛应该意为广泛,型是类型。所以泛型就是能广泛适用的类型。

但泛型还有一种较为准确的说法就是为了参数化类型,或者说可以将类型当作参数传递给一个类或者是方法。

​​举个经典的例子:

List<String> l1 = new ArrayList<>();
List<Integer> l2 = new ArrayList<>();
System.out.println(l1.getClass() == l2.getClass());

请问,上面代码最终结果输出的是什么?不了解泛型的和很熟悉泛型的同学应该能够答出来,而对泛型有所了解,但是了解不深入的同学可能会答错。

正确答案是 true


上面的代码中涉及到了泛型,而输出的结果缘由是类型擦除


泛型的定义和使用

1.泛型类

​例如定义一个泛型类:

public class Test<T> {
    T field1;
}

出于规范,Java 建议我们用单个大写字母来代表类型参数。常见的如:

  1. T 代表一般的任何类。
  2. E 代表 Element 的意思,或者 Exception 异常的意思。
  3. K 代表 Key 的意思。
  4. V 代表 Value 的意思,通常与 K 一起配合使用。
  5. S 代表 Subtype 的意思。

只要在对泛型类创建实例的时候,在尖括号中赋值相应的类型便是。T 就会被替换成对应的类型,如 String 或者是 Integer。你可以相像一下,当一个泛型类被创建时,内部自动扩展成下面的代码。  

// Test<String> test = new Test<>();
public class Test<String> {
  String field1;
}

当然,泛型类不只能接收一个类型参数,也可以接收多个类型的参数。

public class Multitype<E, T> {
    E value1;
    T value2;

    public E getValue1() {
        return value1;
    }

    public T getValue2() {
        return value2;
    }
}

2.泛型方法

public class Test1 {
    public <T> void testMethod(T t) {
    }
}

泛型方法与泛型类稍有不同的地方是,类型参数也就是尖括号那一部分是写在返回值前面的。<T>中的 T 被称为类型参数,而方法中的 T 被称为参数化类型,它不是运行时真正的参数。

当然,声明的类型参数,其实也是可以当作返回值的类型的。

public <T> T testMethod1(T t) {
    return null;
}

3.泛型类与泛型方法的共存

public class Test1<T> {
    public void testMethod(T t) {
        System.out.println(t.getClass().getName());
    }

    public <T> T testMethod1(T t) {
        return t;
    }
}

上面代码中,Test1<T>是泛型类,testMethod 是泛型类中的普通方法,而 testMethod1 是一个泛型方法。而泛型类中的类型参数与泛型方法中的类型参数是没有相应的联系的,泛型方法始终以自己定义的类型参数为准

所以,针对上面的代码,我们可以这样编写测试代码。

Test1<String> t = new Test1();
t.testMethod("generic");
Integer i=t.testMethod1(new Integer(1));

泛型类的实际类型参数是 String,而传递给泛型方法的类型参数是 Integer,两者不想干。

但是,为了避免混淆,如果在一个泛型类中存在泛型方法,那么两者的类型参数最好不要同名。比如,Test1<T>代码可以更改为这样

public class Test1<T> {
    public void testMethod(T t) {
        System.out.println(t.getClass().getName());
    }

    public <E> E testMethod1(E e) {
        return e;
    }
}

4.泛型接口

public interface Iterable<T> {
}

​泛型接口和泛型类差不多,此处不再赘述。


通配符 ?

除了用 <T>表示泛型外,还有 <?>这种形式。 被称为通配符。

无限定通配符 <?>

​无限定通配符经常与容器类配合使用,它其中的 ? 其实代表的是未知类型,所以涉及到 ? 时的操作,一定与具体类型无关。

public void testWildCards(Collection<?> collection) {
}

上面的代码中,方法内的参数是被无限定通配符修饰的 Collection 对象,它隐略地表达了一个意图或者可以说是限定,那就是 testWidlCards() 这个方法内部无需关注 Collection 中的真实类型,因为它是未知的。所以,你只能调用 Collection 中与类型无关的方法。


我们可以看到,当 <?>存在时,Collection 对象丧失了 add() 方法的功能,编译器不通过。
我们再看代码。 

List<?> list = new ArrayList<String>();
List.add("abc"); // 编译不通过

 有人说,<?>提供了只读的功能,也就是它删减了增加具体类型元素的能力,只保留与具体类型无关的功能。它不管装载在这个容器内的元素是什么类型,它只关心元素的数量、容器是否为空?我想这种需求还是很常见的吧。

有同学可能会想,<?>既然作用这么渺小,那么为什么还要引用它呢? 

个人认为,提高了代码的可读性,程序员看到这段代码时,就能够迅速对此建立极简洁的印象,能够快速推断源码作者的意图。

<? extends T>

<?>代表着类型未知,但是我们的确需要对于类型的描述再精确一点,我们希望在一个范围内确定类别,比如类型 A 及 类型 A 的子类都可以。例如:

public static <T extends CommonEnum> T getEnumBycode(Class<T> clazz, int code) {
    for (T e : clazz.getEnumConstants())
        if (code == e.getCode())
            return e;
    return null;
}

可以获取枚举的名称,当然这个 T 类实现了 CommonEnum 接口中的 getCode() 方法。

<? super T>  与 <? extends T> 相对应。