在进行编码测试时,遇到了一个问题,需要在Java中初始化一个泛型类型数组。在试图找出如何做到这一点时,我研究了这堆栈溢出问题,它要求我抑制一个似乎不太好的警告,并且可能存在其他类型的安全问题。为了清晰起见,下面是代码:
public class GenSet<E> {
private E[] a;
public GenSet(Class<E> c, int s) {
// Use Array native method to create array
// of a type only known at run time
@SuppressWarnings("unchecked")
final E[] a = (E[]) Array.newInstance(c, s);
this.a = a;
}
E get(int i) {
return a[i];
}
}
这是否意味着我在这个问题上走错了路?在这种情况下可以使用@SuppressWarnings("unchecked")
吗?
我应该只使用ArrayList
吗?
发布于 2014-09-26 01:06:06
在编写良好的代码中,@SuppressWarnings("unchecked")
有时是正确的和不可避免的.创建一个泛型类型数组就是一个例子。如果您可以通过使用泛型的Collection<T>
或List<T>
来解决这个问题,那么我建议您这样做,但是如果只使用一个数组,那么您就无能为力了。
转换为泛型类型是另一种类似的情况。泛型强制转换无法在运行时进行验证,因此您将收到警告,例如
List<Integer> numbers = (List<Integer>) object;
这并不意味着拥有通用类型的模式是糟糕的。它仅仅反映了Java中泛型设计方式的局限性。
为了承认我是故意用警告来编写代码的,我已经将在任何这样的行中添加注释作为一项规则。这使得这一选择显得格外突出。它还使识别这些行变得很容易,因为@SuppressWarnings
并不总是可以直接在目标行上使用。如果它位于方法的顶部,那么哪一行或哪一行生成警告可能并不明显。
((List<Integer>) object).add(1); // warning: [unchecked] unchecked cast
另外,为了明确起见,您不应该使用原始类型(List
而不是List<T>
)来解决这个问题。那是个坏主意。如果启用-Xlint
,它也会产生警告,因此无论如何也不会使情况变得更好。
发布于 2014-09-26 04:37:34
基于您询问是否应该使用ArrayList
的事实,我假设使用数组不是必需的。我将使用ArrayList
或其他集合,并依赖接口而不是实现来引用它:
List<Integer> myInts = new ArrayList<Integer>();
myInts.add(4);
Java泛型的处境很艰难。一方面,Java有一个自我强加(但很好)的目标,就是向后兼容。由于泛型本质上与前1.5字节码不兼容,这将导致类型擦除。本质上,泛型类型在编译时被移除,泛型方法调用和访问由类型强制转换来增强。另一方面,这种限制有时会使泛型引用变得非常混乱,有时您必须对未检查的和原始的类型使用@SuppressWarnings
。
这意味着依赖方法调用的集合可以很好地将集合的用户与存储隔离开来,但是数组被具体化 (这意味着根据语言,数组类型必须在运行时可用)。这与集合相反,正如我提到的,集合中删除了泛型类型。示例:
String[] array1 = new String[1];
Object[] array2 = array1;
尽管引用array2
被声明为Object[]
,但JVM知道它的目标数组只能包含字符串。因为这些类型是可引用的,所以它们本质上与泛型不兼容。
StackOverflow有许多关于Java泛型和数组的问题,下面是我发现的几个好问题:
发布于 2015-06-23 09:06:30
在本例中,
这是一个未检查的强制转换,因为在运行时不知道E
。因此,运行时检查只能检查转换到Object[]
(E[]
的擦除),但实际上不能检查到E[]
本身。(例如,如果E
是Integer
,那么如果对象的实际运行时类是String[]
,则即使它不是Integer[]
,也不会被检查捕获。)
Array.newInstance()
返回Object
,而不是E[]
( E
将是Class
参数的类型参数),因为它可以用于创建原语数组和引用数组。类型变量(如E
)不能表示原语类型,而基元类型数组的唯一超级类型是Object
。表示基本类型的Class
对象是Class
参数化的,其包装类作为类型参数,例如int.class
具有Class<Integer>
类型。但是,如果将int.class
传递给Array.newInstance()
,您将创建一个int[]
,而不是E[]
(即Integer[]
)。但是如果您传递一个表示引用类型的Class<E>
,Array.newInstance()
将返回一个E[]
。
因此,基本上,使用一个Array.newInstance()
调用Class<E>
总是会返回一个E[]
或一个原语数组。原语数组类型不是Object[]
的子类型,因此它将无法对Object[]
进行运行时检查。换句话说,如果结果是Object[]
,则保证为E[]
。因此,即使此强制转换只在运行时检查Object[]
,并且不检查从Object[]
到E[]
的部件,在这种情况下,对Object[]
的检查就足以保证它是E[]
,因此在这种情况下,未检查的部分不是问题,它实际上是一个完全检查的强制转换。
顺便说一句,从您所展示的代码中,您不需要传递类对象来初始化GenSet
或使用Array.newInstance()
。只有当您的类在运行时实际使用类E
时,这才是必要的。但是它没有,它所做的就是创建一个数组(它不会暴露在类的外部),并从中获取元素。这可以使用Object[]
来实现。在取出元素时,只需将其转换为E
(如果只将E
s放入其中,则无需检查元素是否安全):
public class GenSet<E> {
private Object[] a;
public GenSet(int s) {
this.a = new Object[s];
}
@SuppressWarnings("unchecked")
E get(int i) {
return (E)a[i];
}
}
https://softwareengineering.stackexchange.com/questions/257257
复制相似问题