当你在强制转换为数组类型时,得到泛型数组创建错误,或是未经检查的强制转换警告时,最佳解决方案通常是使用集合类型List <E>而不是数组类型E []。 这样可能会牺牲一些简洁性或性能,但作为交换,你会获得更好的类型安全性和互操作性。
例如,假设你想用带有集合的构造方法来编写一个Chooser类,并且有个方法返回随机选择的集合的一个元素。 根据传递给构造方法的集合,可以使用选择器作为游戏模具,魔术8球或数据源进行蒙特卡罗模拟。 这是一个没有泛型的简单实现:
// Chooser - a class badly in need of generics! public class Chooser { private final Object[] choiceArray; public Chooser(Collection choices) { choiceArray = choices.toArray(); } public Object choose() { Random rnd = ThreadLocalRandom.current(); return choiceArray[rnd.nextInt(choiceArray.length)]; } }要使用这个类,每次调用方法时,都必须将Object的choose方法的返回值转换为所需的类型,如果类型错误,则转换在运行时失败。 我们先根据条目 29的建议,试图修改Chooser类,使其成为泛型的。
// A first cut at making Chooser generic - won't compile public class Chooser<T> { private final T[] choiceArray; public Chooser(Collection<T> choices) { choiceArray = choices.toArray(); } // choose method unchanged }如果你尝试编译这个类,会得到这个错误信息:
Chooser.java:9: error: incompatible types: Object[] cannot be converted to T[] choiceArray = choices.toArray(); ^ where T is a type-variable: T extends Object declared in class Chooser没什么大不了的,将Object数组转换为T数组:
choiceArray = (T[]) choices.toArray();这没有了错误,而是得到一个警告:
Chooser.java:9: warning: [unchecked] unchecked cast choiceArray = (T[]) choices.toArray(); ^ required: T[], found: Object[] where T is a type-variable: T extends Object declared in class Chooser编译器告诉你在运行时不能保证强制转换的安全性,因为程序不会知道T代表什么类型——记住,元素类型信息在运行时会被泛型删除。 该程序可以正常工作吗? 是的,但编译器不能证明这一点。 你可以证明这一点,在注释中提出证据,并用注解来抑制警告,但最好是消除警告的原因(条目 27)。
要消除未经检查的强制转换警告,请使用列表而不是数组。 下面是另一个版本的Chooser类,编译时没有错误或警告:
// List-based Chooser - typesafe public class Chooser<T> { private final List<T> choiceList; public Chooser(Collection<T> choices) { choiceList = new ArrayList<>(choices); } public T choose() { Random rnd = ThreadLocalRandom.current(); return choiceList.get(rnd.nextInt(choiceList.size())); } }这个版本有些冗长,也许运行比较慢,但是值得一提的是,在运行时不会得到ClassCastException异常。
总之,数组和泛型具有非常不同的类型规则。 数组是协变和具体化的; 泛型是不变的,类型擦除的。 因此,数组提供运行时类型的安全性,但不提供编译时类型的安全性,反之亦然。 一般来说,数组和泛型不能很好地混合工作。 如果你发现把它们混合在一起,得到编译时错误或者警告,你的第一个冲动应该是用列表来替换数组。