【0】README

0.1) 本文描述+源代码均 转自 core java volume 1, 旨在理解 java泛型程序设计 的 Varargs 警告+不能实例化类型变量 的知识;


【1】 Varargs 警告

1.1)一个相关问题: 向参数个数可变的方法传递一个泛型类型的实例;

  • 1.1.1)考虑以下方法, 它的参数个数是可变的:
public static<T> void addAll(Collection<T) coll, T...ts)
{
    ......
}
  • 1.1.2)应该记得, 实际上参数ts 是一个数组, 包含所有实参, 考虑以下调用:
Collection<Pair<String>> table = ......;
Pair<String> pair1 = ....;
Pair<String> pair2 = ....;
addAll(table, pair1, pair2);
  • 1.1.3)为了调用上述方法: java 虚拟机必须建立一个 Pair 数组, 这就违反了前面的规则, 不过, 对于这种case , 你只会得到一个警告, 而不是 错误;

1.4)可以采用以下两种方法来抑制这种警告(Methods):(解决方法)

  • M1)一种方法是 为 包含 addAll 调用的方法增加 标注
@SuppressWarnings("unchecked");
  • M2)在java se 7中, 还可以用 @SafeVarargs 直接标注addAll 方法:
@SafeVarargs
public static <T> void addAll(Collection<T> coll, T...ts);
  • 现在就可以用 泛型类型来调用这个方法了;

Annotation)

  • A1)可以使用 @SafeVarargs 标注来消除创建泛型数组的有关限制, 方法如下:
@SafeVarargs
static <E> E[] array(E... array)
{
    return array;
}
  • A2)现在可以调用:
Pair<String>[] table = array(pair1, pair2);
  • 这看起来方便, 不过隐藏着危险, 如下代码:
Object[] objarray = table;
objarray[0] = new Pair<Employee>();
  • 能顺利运行而不会出现 ArrayStoreException 异常 (因为数组存储只会检查擦除的类型), 但在处理 table[0] 时 你会在别处得到一个异常;

【2】不能实例化类型变量

2.1)不能使用像 new T(…), new T[…] 或 T.class 这样的表达式中的类型变量。

  • 2.1.1)看个荔枝:(下面的 Pair 构造器就是非法的)
public Pair()
{
    first = new T();
    second = new T(); // ERROR
}
  • 2.1.2)类型擦除将T 改变成 Object, 而且, 本意肯定不希望调用 new Object()。
  • 2.1.3)但是可以通过反射调用 Class.newInstance 方法来构造泛型对象:
然而,我们却不能调用: first = T.class.newInstance();//ERROR
  • 2.1.4)因为, 表达式 T.class 是不合法的, 必须像下面这样设计 API 以便可以支配Class 对象:
public static<T> Pair<T> makePair(Class<T> cl)
{
    try{ return new Pair<>(c1.newInstance(), c1.newInstance()) }
    catch(Exception ex) {return null;}
}
  • 2.1.5)以上方法可以按照下列方式调用:
Pair<String>p = Pair.makePair(String.class);
  • Attention) Class 类本身就是泛型, 如, String.class 是一个 class《String》 的实例(事实上, 它是唯一的实例)。 因此, makePair 方法能够推断出 pair 的类型;
  • 2.1.6) 不能构造一个 泛型数组:
public  static <T extends Comparable> T[] minmax(T[] a) { T[] mm = new T[2]; ...} //ERROR
  • 类型擦除会让这个方法永远构造 Object[2]数组;

2.2)如果数组仅仅是作为一个类的私有实例域, 就可以将这个数组声明为 Object[], 并且在获取元素时进行类型转换。

  • 2.2.1)看个荔枝: 如 ArrayLIst 可以这样实现:
public class ArrayList<E>
{
    private Object[] elements;
    ...
    @SuppressWarnings("unchecked")
    public E get(int n)
    {
        return (E) elements[n]; //获取元素时进行类型转换
    }
    pubic void set(int n, E e)
    {
        elements[n] =e;
    }
}
  • 2.2.2)实际的实现没有这么清晰:
public class ArrayList<E>
{
    paivate E[] elements;
    ...
    public ArrayList(){ elements = (E[])new Object[0]; }
}
  • 2.2.3)这里, 强制类型转换 E[] 是一个假想, 而类型 擦除使其无法察觉;

2.3)由于 minmax方法 返回 T[] 数组, 使得这一技术无法施展, 如果掩盖这个类型会有运行时错误。假设实现代码:

public static <T extends Comparable> T[] minmax(T...a)
{
    Object[] mm = new Object[2];
    ...
    reutrn (T[]) mm;
}

2.4)调用String[] ss = minmax(“tom”, “dick”, “Harry”);
对上述代码的分析(Analysis):

  • A1)编译时不会有任何警告。 但Object[] 引用赋给 String[] 变量时, 将会发生 ClassCastException 异常;
  • A2)在这种case下, 利用反射,调用 Array.newIntance;
public static <T extemds Comparable> T[] minmax(T... a)
{
    T[] mm = (T[])mm.newInstance(a.getClass().getComponentType(), 2);
}
  • A3)ArrayList 类的toArray方法就没有这么幸运了。 它需要生成一个 T[] 数组, 但没有成分类类型。 因此, 有下面两种不同的形式:
Object[] toArray();
T[] toArray(T[] result);
  • 第二个方法接收一个数组参数。 如果数组足够大, 就是用这个数组。 否则, 用result 的成分类型构造一个足够大的 新数组;