代码改变世界

Java Generics And Collections笔记(Part III)

2018-02-23 15:59  秋风将涌起的某夜  阅读(182)  评论(0)    收藏  举报

5 Evolution, not Revolution

泛型代码和legacy代码生成相同的class文件。

skipped

6. Reification

reify翻译为具体化,计算机科学中,reification可理解为run-time类型信息的显式表示。Java中,数组具体化组成元素类型(component type),但是泛型类型不会具体化类型参数信息。

6.1 Reifiable Types

以下类型是可具体化的(reifiable):

  • primitive type,如int
  • 非参数化的类或者接口,如String, Runnable
  • 类型参数是unbounded wildcards的参数化类型,如List<?>
  • raw type,如List
  • 组成元素类型可具体化的数组,如int[],int[][],List[],List<?>[]

以下类型是不可具体化的:

  • 类型变量,如T
  • 具有类型实参的参数化类型,如List<String>,Map<String, Integer>
  • 有界限(bound)的参数化类型,如Comparable<? super String>, List<? extends Number>

有趣的事,List<? extends Object> 和 List<?> 是等价的,但是前者不可具体化,后者可具体化。

6.2 Instance Tests and Casts

实例的类型检查和类型转换都是在run-time检查类型,也以来依赖reification。

o instanceof Integer;
o instanceof List<E>; //编译错误

参考abstractList的equals实现:

public boolean equals(Object o) {
    if (o == this)
        return true;
    if (!(o instanceof List))
        return false;

    ListIterator<E> e1 = listIterator();
    ListIterator<?> e2 = ((List<?>) o).listIterator();
    while (e1.hasNext() && e2.hasNext()) {
        E o1 = e1.next();
        Object o2 = e2.next();
        if (!(o1==null ? o2==null : o1.equals(o2)))
            return false;
    }
    return !(e1.hasNext() || e2.hasNext());
}

第四行代码使用了raw type的List,推荐使用wildcard type的List<?>,后者可以提供类型保证。

In the most cases, a cast to a type that is not reifiable is flagged with an unchecked warning, whereas an instance test against a type that is not reifiable is always caught as an error.

It is illegal to cast a list of objects to a list of strings, so the cast must take place in two steps. First, cast the list of objects into a list of wildcard type; this cast is safe. Second, cast the list of wildcard type into a list of strings; this cast is permitted but generates an unchecked warning:

6.3 Exception Handling

catch语句会检查抛出的异常是不是给定的类型,和类型检查相似,异常类型必须也是可具体化的。

6.4 Array Creation

数组具体化了组成元素类型,保留了run-time时组成元素的类型,因而可以检测赋值到数组的值是否是允许的,如下:

Integer[] ints = new Integer[] {1, 2, 3};
Number[] nums = ints;
nums[0] = 3.14; // ArrayStoreException

Java中,数组时协变的,第二行代码没问题,但是第三行代码抛出异常,3.14是double类型的,和数组元素的具体化类型不兼容。

也因为类型变量不是可具体化的类型,因此创建泛型数组会出现编译错误,如下:

T[] arr = new T[0];
List<Integer>[] xxs = new List<Integer>[0];

generic arrays are prob- lematic because generics are implemented via erasure, but erasure is beneficial because it eases evolution.

6.5 The Principle of Truth in Advertising

尝试将集合转化为数组,但是创建泛型数组会出错,可以通过unchecked cast强制转换,如下:

public static <T> T[] toArray(Collection<T> c) {
    T[] a = (T[])new Object[c.size()];
    // ... copy to a
    return a;
}

这段代码会有unchecked cast的警告,这个警告也不应该被忽视,如下代码会报错:

List<String> ss = Arrays.asList("one", "two");
String[] a = toArray(ss); // java.lang.ClassCastException

toArray(ss)的返回的数组的组成元素类型是Object,Object[]转化为String[]会失败。

原因就是类型擦除,toArray方法中的类型参数被丢掉,然后用Object代替T,并插入类型转换,如下:

public static Object[] toArray(Collection c) {
    Object[] a = (Object[])new Object[c.size()];
    // ... copy to a
    return a
}

调用会变成如下:

List ss = Arrays.asList("one", "two");
String[] a = (String[])toArray(ss);

the cast that fails may be in a different part of the source code than was responsible for the unchecked warning!

为此,我们要遵循

The Principle of Truth in Advertising: the reified type of an array must be a subtype of the erasure of its static type.

简单的来说,数组的run-time具体化类型必须是声明的类型的子类。可参考这个链接

创建泛型数组的正确方法是利用反射。参考如下代码,或者jdk中Arrays.copyOf

public static <T> T[] toArray(Collection<T> c, T[] a) {
    if (a.length < c.size()) {
        a = (T[]) Array.newInstance(a.getClass().getComponentType(), c.size());
    }
    // ... copy to a
    return a;
}

在java.lang.reflect.Array中,newInstance定义如下:

public static Object newInstance(Class<?> componentType, int length)
    throws NegativeArraySizeException {
    return newArray(componentType, length);
}

返回值类型是Object,而不是Object[],原因是newInstance可能返回int[]等原生类型的数组。

在Collection中,toArray的签名如下

Object[] toArray();
<T> T[] toArray(T[] a);

前者返回的数组的具体化类型是Object,后者和入参数组的具体化类型一致。

同样为了获取数组的具体化类型,函数签名可以如下:

public static <T> T[] toArray(Collection<T> c, Class<T> k)
6.6 The Principle of Indecent Exposure

Although it is an error to create an array with a component type that is not reifiable, it is possible to declare an array with such a type and to perform an unchecked cast to such a type.

a library should never publicly expose an array with a nonreifiable type.

Principle of Indecent Exposure: never publicly expose an array where the components do not have a reifiable type.

参考如下代码:

List<Integer>[] intLists = 
    (List<Integer>[])new List[] {Arrays.asList(1)}; // unchecked cast
List<? extends Number>[] numLists = intLists; 
numLists[0] = Arrays.asList(1.01);
int n = intLists[0].get(0); // class cast exception!

this is a case where an unchecked cast in one part of the program may lead to a class cast error in a completely different part, where the cast does not appear in the source code but is instead introduced by erasure.