尽管创建一个不可赋予的组件类型的数组是错误的,但可以声明一个具有这种类型的数组,并对此类型执行未经检查的转换。 必须谨慎使用这些功能,如果使用不当,有 必要了解可能出现的问题。 特别是,一个库不应公开公开一个具有不可确定类型的数组。
回想一下,2.5
节介绍了为什么需要物化的一个例子:
Integer[] ints = new Integer[] {1};
Number[] nums = ints;
nums[0] = 1.01; // 数组存储异常
int n = ints[0];
这将整数数组赋给一个数组数组,然后尝试将一个 double
存储到数组数组中。 该尝试引发数组存储异常,因为该检查与实体类型有关。 这也是一样,因为否则最后
一行会尝试将 double
存储到整数变量中。
下面是一个类似的例子,数组数组被数组列表所取代:
List<Integer>[] intLists = (List<Integer>[])new List[] {Arrays.asList(1)}; // 未经检查的转换
List<? extends Number>[] numLists = intLists;
numLists[0] = Arrays.asList(1.01);
int n = intLists[0].get(0); // 类抛出异常!
这将整数列表分配给数组列表,然后尝试将双列表存储到数组列表中。 这次尝试的存储不会失败,即使它应该,因为针对被指定类型的检查是不充分的:被指定的信息只
包含删除类型,表示它是一个 List
数组,而不是一个 List<Integer>
。因此,编译成功,程序意外地在别处失败。
例 6-1
。 避免不可接受类型的数组
DeceptiveLibrary.java:
import java.util.*;
public class DeceptiveLibrary {
public static List<Integer>[] intLists(int size) {
List<Integer>[] intLists = (List<Integer>[]) new List[size]; // 未经检查的转换
for (int i = 0; i < size; i++)
intLists[i] = Arrays.asList(i+1);
return ints;
}
}
InnocentClient.java:
import java.util.*;
public class InnocentClient {
public static void main(String[] args) {
List<Integer>[] intLists = DeceptiveLibrary.intLists(1);
List<? extends Number>[] numLists = intLists;
numLists[0] = Arrays.asList(1.01);
int i = intLists[0].get(0); // 类抛出异常!
}
}
例 6-1
给出了一个类似的例子,分为两类,以说明设计不佳的图书馆如何为无辜的客户创造问题。 名为 DeceptiveLibrary
的第一个类定义了一个静态方法,该
方法返回给定大小的整数列表数组。 由于不允许通用数组的创建,因此使用原始类型 List
的组件创建数组,并使用强制类型为组件提供参数化类型
List<Integer>
。 演员阵容会产生一个未经检查的警告:
%javac -Xlint:unchecked DeceptiveLibrary.java
DeceptiveLibrary.java:5: warning: [unchecked] unchecked cast
found : java.util.List[]
required: java.util.List<java.lang.Integer>[]
(List<Integer>[]) new List[size]; // unchecked cast
^
1 warning
由于该数组确实是一个整数列表数组,因此该数组似乎是合理的,并且您可能认为可以安全地忽略此警告。 正如我们将要看到的,你无视这个警告!
第二个类叫做 InnocentClient
,它有一个类似于前面例子的主要方法。 由于未检查的强制转换出现在库中,因此在编译此代码时不会发出未经检查的警告。 但是,
运行代码会用双精度列表覆盖整数列表。 尝试从整数列表中提取整数会导致通过擦除隐式插入的强制转换失败:
%java InnocentClient
Exception in thread "main" java.lang.ClassCastException: java.lang.Double
at InnocentClient.main(InnocentClient.java:7)
如前一节所述,此错误消息可能会令人困惑,因为该行看起来不包含演员表!
为了避免这个问题,你必须坚持以下原则:不雅暴露的原则:
从不公开暴露一个阵列,其中组件不具有可调整类型。
再次,这是一种情况,在程序的一部分中未经检查的转换可能导致在完全不同的部分发生类转换错误,其中转换不会出现在源代码中,而是通过擦除引入。由于此类错误 可能会非常混乱,因此必须谨慎使用未经检查的列表。
广告真相原则与不雅暴露原则密切相关。第一个要求数组的运行时类型被适当地赋值,第二个要求数组的编译时类型必须是可赋值的。
即使在Java泛型的设计者中,也要花费一些时间来理解不雅暴露原则的重要性。例如,反射库中的以下两种方法违反了该原则:
TypeVariable<Class<T>>[] java.lang.Class.getTypeParameters()
TypeVariable<Method>[] java.lang.Reflect.Method.getTypeParameters()
遵循前面的模型,创建自己的Innocent Client版本并不难,它会在没有投射的地方抛出类抛出错误,在这种情况下,正式Java库会播放 DeceptiveLibrary
的角
色! (在出版时,这个错误的补救措施正在考虑之中,可能的解决方法是从 TypeVariable
中删除类型参数,以便这些方法返回一个指定类型的数组,或者用列表替
换数组。
不要以同样的方式被抓到 - 一定要严格遵循恶意曝光原则在您自己的代码中!