
概念术语解释:
type variable类型变量:常用的 T、M、K、V 这些都是,用于代表某个类型,非具体的。
parameterized type参数化类型:拥有具体类型参数的泛型,它用另外一个类型作为参数;如List<Integer>。
什么是 raw type ?
JLS( Java Language Specification) 给出如下定义:
raw type 被定义为如下情况:
- 带有泛型类型声明的名称而没有类型参数列表的类型,如
List- 元素类型是
raw type类型的数组raw typeR 的非static成员类型,不继承于R 的父类或父接口。
下面是一些示例:
public class MyType<E> {class Inner { }static class Nested { }public static void main(String[] args) {MyType mt; //符合定义① MyType is a raw type,MyType.Inner inn; //符合定义③ MyType.Inner is a raw typeMyType.Nested nest; //违反定义③ not parameterized typeMyType<Object> mt1; //违反定义① type parameter givenMyType<?> mt2; //违反定义① type parameter given (wildcard OK!)}}
泛型是 invariant
Object o = "someString"; // 正常!Class<Object> klazz = String.class; // 编译错误!// cannot convert from Class<String> to Class<Object>
依赖于你需要什么类型,你能够用有限界的通配符来代替上面的Object。
Class<? extends Number> klazz = Integer.class; // 编译通过!
甚至你还可以这样:
Class<List<String>> klazz =(Class<List<String>>) new ArrayList<String>().getClass();// WARNING! Type safety: Unchecked cast from// Class<capture#1-of ? extends ArrayList> to Class<List<String>>
下面是一段来自 Java Tutorials on Generics 的解释, 很好的描述了:泛型类被所有它的调用者共享;
下面的代码片段打印什么结果?
List <String> l1 = new ArrayList<String>();List<Integer> l2 = new ArrayList<Integer>();System.out.println(l1.getClass() == l2.getClass());你可能以为是
false。实际打印true, 因为所有的泛型实例有相同的运行时类型, 不管他们实际的类型参数是什么。
也就是说没有这种类, List<String>.class 、 List<Integer>.class ,只有 List.class 。
JLS 15.8.2 Class Literals 中也提到了:
类字面量是一个表达式,它由
class、interface、array、或者primitive type、void后面跟着.class组成。
:::warning
注意 类型和 .class 之间不可以有泛型类型参数,此外,
:::
如果出现下面的使用方式,将会产生编译错误: 类型名是类型变量(
type variable)或者参数化类型(parameterized type),抑或是元素类型是类型变量、参数化类型的数组。
也就是说,下面的也编译不通过
void <T> test() {Class<?> klazz = T.class; // 编译失败!//Demo<Integer>.class, List<T>.class , List<Integer>.class,//Demo<Integer>[].class, Demo<T>[].class 这些同样编译失败!// Illegal class literal for the type parameter T}
因此,你不能在类字面量上使用泛型,那样没有意义。
raw type 和 用 <Object> 作为类型参数有什么区别?
下面说明引用自《Effective Java 2nd Edition》,不要在新代码中使用 raw type :
raw typeList和参数化类型List<Object>之前的区别是什么, 不严格的讲, 前者丢弃了泛型类型检查,而后者告诉编译器他能够持有任何类型的对象。你能够赋值List<String>到类型List, 但是你无法赋值给List<Object>. 泛型的子类型 规则中,List<String>是raw typeList的子类型,且不是List<Object>的子类型.
void appendNewObject(List<Object> list) {list.add(new Object());}void appendObject(List list) {list.add(new Object());}List<String> names = new ArrayList<String>();appendNewObject(names); //❌ 编译错误appendObject(names); //✅ 失去类型检查List ss = new ArrayList<Integer>();Integer i = ss.get(0); //❌ 编译错误Object o = ss.get(0); //✅ raw type 放入是没有类型检查,无法知道元素类型,只能取出 Object
由于前面提到的泛型是 invariant ,所以 List<String 和 List<Object> 不兼容,会产生编译错误。
:::info
总结起来就是,如果你用 raw type 如 List ,你将失去类型安全检查;但如果你用参数化类型,如 List<Object> ,或者更具体的如 List<Integer> ,则不会。
- raw type 可以添加任何元素,只能取出Object 类型,可以引用任何参数化类型。
可以添加任何元素,只能取出Object 类型,只能引用同参数化类型Object。 ::: raw type和 用<?>作为类型参数有什么区别?List
、List 等等参数化泛型都可以赋值给 List<?>, 这和上面讲的raw type List 有点相似,
然而,他们有一个重要的不同点: :::info<?> 不可以添加元素,也只能取出 Object 类型, 可以引用任何参数化类型。
raw typeList可以添加任何元素,只能取出Object 类型,可以引用任何参数化类型。 :::和 <?>作为类型参数有什么区别?:::info
可以添加任何类型元素,但只能取出 Object 类型,只能引用同参数化类型Object。 - <?> 不可以添加元素,也只能取出 Object 类型, 可以引用任何参数化类型。 :::
raw type 、<?>、
共同点? 历史代码、第三方库中
raw type型入参对于历史代码、二方包、三方包由于不规范地使用了
raw type入参,如何提供安全类型检查?下面通过
Collections.checkedCollection生成指定集合动态类型安全的视图,任何试图向集合中插入不正确类型的操作,都将产生ClassCastException。Collection<String> c = new ArrayList<>();Collections.addAll(c, "apple", "banana");System.out.println(c);Collection c2 = c;c2.add(1); //类型不安全,添加进了integer类型System.out.println(c2);String str = c2.get(2); //❌ 运行时异常,消费时才暴露问题[apple, banana][apple, banana, 1]
Collection<String> c = new ArrayList<>();c = Collections.checkedCollection(c, String.class);Collections.addAll(c, "apple", "banana");System.out.println(c);Collection c2 = c;c2.add(1);//❌ 运行时异常System.out.println(c2);Caused by: java.lang.ClassCastException: Attempt to insert class java.lang.Integer element into collection with element type class java.lang.Stringat java.util.Collections$CheckedCollection.typeCheck(Collections.java:3037)at java.util.Collections$CheckedCollection.add(Collections.java:3080)at com.logicbig.example.collections.CheckedCollectionExample2.main(CheckedCollectionExample2.java:22)... 6 more
这里通过
Collections.checkedCollection添加运行时安全检查,可以将错误提前到添加时暴露,便于快速定位问题。总结
消费类型 产生类型 引用类型 raw type任意 Object任意同 raw type的参数化类型<?>❌ Object任意同 raw type的参数化类型<Object>任意 Object只能同 raw type且参数化类型为Object
为了让你有个直观的认识,下面演示了上面各种情况的区别:
List<Integer> integers = new ArrayList<>();List<String> strings = new ArrayList<>();List<Object> objs = new ArrayList<>();List<?> wild = new ArrayList<>();wild.add(1); //❌Object o2 = wild.get(0); //✅Integer o2Error = wild.get(0); //❌wild = strings;//✅wild = integers;//✅List<Object> objects = new ArrayList<>();List<Object> error = new ArrayList<Integer>(); //❌objects.add(1); //✅objects.add("str"); //✅objects = strings; //❌objects = objs; //✅Object o3 = objects.get(0); //✅Integer o3Error = objects.get(0); //❌List ss = new ArrayList<Integer>();ss.add(3); //✅ss.add("str"); //✅Object o = ss.get(0); //✅Integer oError = ss.get(0); //❌ss = integers; //✅ss = strings; //✅
