image.png

概念术语解释:

type variable 类型变量:常用的 T、M、K、V 这些都是,用于代表某个类型,非具体的。

parameterized type 参数化类型:拥有具体类型参数的泛型,它用另外一个类型作为参数;如 List<Integer>

什么是 raw type ?

JLS( Java Language Specification) 给出如下定义:

raw type 被定义为如下情况:

  1. 带有泛型类型声明的名称而没有类型参数列表的类型,如 List
  2. 元素类型是 raw type 类型的数组
  3. raw type R 的非 static 成员类型,不继承于R 的父类或父接口。

下面是一些示例:

  1. public class MyType<E> {
  2. class Inner { }
  3. static class Nested { }
  4. public static void main(String[] args) {
  5. MyType mt; //符合定义① MyType is a raw type,
  6. MyType.Inner inn; //符合定义③ MyType.Inner is a raw type
  7. MyType.Nested nest; //违反定义③ not parameterized type
  8. MyType<Object> mt1; //违反定义① type parameter given
  9. MyType<?> mt2; //违反定义① type parameter given (wildcard OK!)
  10. }
  11. }

泛型是 invariant

  1. Object o = "someString"; // 正常!
  2. Class<Object> klazz = String.class; // 编译错误!
  3. // cannot convert from Class<String> to Class<Object>

依赖于你需要什么类型,你能够用有限界的通配符来代替上面的Object

  1. Class<? extends Number> klazz = Integer.class; // 编译通过!

甚至你还可以这样:

  1. Class<List<String>> klazz =
  2. (Class<List<String>>) new ArrayList<String>().getClass();
  3. // WARNING! Type safety: Unchecked cast from
  4. // Class<capture#1-of ? extends ArrayList> to Class<List<String>>

下面是一段来自 Java Tutorials on Generics 的解释, 很好的描述了:泛型类被所有它的调用者共享;

下面的代码片段打印什么结果?

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

你可能以为是 false 。实际打印 true , 因为所有的泛型实例有相同的运行时类型, 不管他们实际的类型参数是什么。

也就是说没有这种类, List<String>.classList<Integer>.class ,只有 List.class

JLS 15.8.2 Class Literals 中也提到了:

类字面量是一个表达式,它由 classinterfacearray 、或者 primitive typevoid 后面跟着 .class 组成。


:::warning 注意 类型和 .class 之间不可以有泛型类型参数,此外, :::

如果出现下面的使用方式,将会产生编译错误: 类型名是类型变量( type variable )或者参数化类型( parameterized type ),抑或是元素类型是类型变量、参数化类型的数组。

也就是说,下面的也编译不通过

  1. void <T> test() {
  2. Class<?> klazz = T.class; // 编译失败!
  3. //Demo<Integer>.class, List<T>.class , List<Integer>.class,
  4. //Demo<Integer>[].class, Demo<T>[].class 这些同样编译失败!
  5. // Illegal class literal for the type parameter T
  6. }

因此,你不能在类字面量上使用泛型,那样没有意义。

raw type 和 用 <Object> 作为类型参数有什么区别?

下面说明引用自《Effective Java 2nd Edition》,不要在新代码中使用 raw type :

raw type List 和参数化类型 List<Object> 之前的区别是什么, 不严格的讲, 前者丢弃了泛型类型检查,而后者告诉编译器他能够持有任何类型的对象。你能够赋值 List<String> 到类型 List , 但是你无法赋值给 List<Object> . 泛型的子类型 规则中, List<String>raw type List 的子类型,且不是 List<Object> 的子类型.

  1. void appendNewObject(List<Object> list) {
  2. list.add(new Object());
  3. }
  4. void appendObject(List list) {
  5. list.add(new Object());
  6. }
  7. List<String> names = new ArrayList<String>();
  8. appendNewObject(names); //❌ 编译错误
  9. appendObject(names); //✅ 失去类型检查
  10. List ss = new ArrayList<Integer>();
  11. Integer i = ss.get(0); //❌ 编译错误
  12. Object o = ss.get(0); //✅ raw type 放入是没有类型检查,无法知道元素类型,只能取出 Object

由于前面提到的泛型是 invariant ,所以 List<StringList<Object> 不兼容,会产生编译错误。

:::info 总结起来就是,如果你用 raw typeList ,你将失去类型安全检查;但如果你用参数化类型,如 List<Object> ,或者更具体的如 List<Integer> ,则不会。

  1. raw type 可以添加任何元素,只能取出Object 类型,可以引用任何参数化类型。
  2. 可以添加任何元素,只能取出Object 类型,只能引用同参数化类型Object。 :::

    raw type 和 用 <?> 作为类型参数有什么区别?

    List 、List 等等参数化泛型都可以赋值给 List<?>, 这和上面讲的raw type List 有点相似,
    然而,他们有一个重要的不同点: :::info

  3. <?> 不可以添加元素,也只能取出 Object 类型, 可以引用任何参数化类型。

  4. raw type List 可以添加任何元素,只能取出Object 类型,可以引用任何参数化类型。 :::
  5. <?> 作为类型参数有什么区别?

    :::info

    1. 可以添加任何类型元素,但只能取出 Object 类型,只能引用同参数化类型Object。
    2. <?> 不可以添加元素,也只能取出 Object 类型, 可以引用任何参数化类型。 :::
    3. raw type 、<?>、 共同点?

      :::info 只能取出Object对象 :::

      历史代码、第三方库中 raw type 型入参

      对于历史代码、二方包、三方包由于不规范地使用了 raw type 入参,如何提供安全类型检查?

      下面通过Collections.checkedCollection 生成指定集合动态类型安全的视图,任何试图向集合中插入不正确类型的操作,都将产生 ClassCastException

      1. Collection<String> c = new ArrayList<>();
      2. Collections.addAll(c, "apple", "banana");
      3. System.out.println(c);
      4. Collection c2 = c;
      5. c2.add(1); //类型不安全,添加进了integer类型
      6. System.out.println(c2);
      7. String str = c2.get(2); //❌ 运行时异常,消费时才暴露问题
      8. [apple, banana]
      9. [apple, banana, 1]
      1. Collection<String> c = new ArrayList<>();
      2. c = Collections.checkedCollection(c, String.class);
      3. Collections.addAll(c, "apple", "banana");
      4. System.out.println(c);
      5. Collection c2 = c;
      6. c2.add(1);//❌ 运行时异常
      7. System.out.println(c2);
      8. Caused by: java.lang.ClassCastException: Attempt to insert class java.lang.Integer element into collection with element type class java.lang.String
      9. at java.util.Collections$CheckedCollection.typeCheck(Collections.java:3037)
      10. at java.util.Collections$CheckedCollection.add(Collections.java:3080)
      11. at com.logicbig.example.collections.CheckedCollectionExample2.main(CheckedCollectionExample2.java:22)
      12. ... 6 more

      这里通过 Collections.checkedCollection 添加运行时安全检查,可以将错误提前到添加时暴露,便于快速定位问题。

      总结

      消费类型 产生类型 引用类型
      raw type 任意 Object 任意同 raw type 的参数化类型
      <?> Object 任意同 raw type 的参数化类型
      <Object> 任意 Object 只能同 raw type 且参数化类型为Object

      为了让你有个直观的认识,下面演示了上面各种情况的区别:

      1. List<Integer> integers = new ArrayList<>();
      2. List<String> strings = new ArrayList<>();
      3. List<Object> objs = new ArrayList<>();
      4. List<?> wild = new ArrayList<>();
      5. wild.add(1); //❌
      6. Object o2 = wild.get(0); //✅
      7. Integer o2Error = wild.get(0); //❌
      8. wild = strings;//✅
      9. wild = integers;//✅
      10. List<Object> objects = new ArrayList<>();
      11. List<Object> error = new ArrayList<Integer>(); //❌
      12. objects.add(1); //✅
      13. objects.add("str"); //✅
      14. objects = strings; //❌
      15. objects = objs; //✅
      16. Object o3 = objects.get(0); //✅
      17. Integer o3Error = objects.get(0); //❌
      18. List ss = new ArrayList<Integer>();
      19. ss.add(3); //✅
      20. ss.add("str"); //✅
      21. Object o = ss.get(0); //✅
      22. Integer oError = ss.get(0); //❌
      23. ss = integers; //✅
      24. ss = strings; //✅