此问题特定于使用带有通用f Enum<E>
的varargs:
如果我定义类似的方法,为什么会收到此警告Type safety: Potential heap pollution via varargs parameter elements
:
<E extends Enum<E>> void someMethod(E... elements)
与此相反:
<E extends Enum<E>> void someMethod(E[] elements)
因此,在声明方法@SafeVarargs
之前我应该注意什么?
此问题类似于有关Collection<T>...
的这些问题,但这些答案中显示的方案似乎不适用于Enum<E>...
:
这个问题的反面质疑为什么没有警告:
这是我试图污染堆的原因,但是每次不良尝试都会导致java.lang.ArrayStoreException
而不是污染的数组。
我正在使用 Eclipse 4.6.0 和 Java JDK 8u74 。
public static void main(String[] args) {
Foo[] x = { Foo.A };
someMethod(x);
Foo y = x[0]; // How does one get a ClassCastException here?
}
private static enum Foo {
A, B, C,
}
private static enum Bar {
X, Y, Z,
}
// This produces a "Type safety" warning on 'elements'
private static <E extends Enum<E>> void someMethod(E... elements) {
Object[] objects = elements;
// Test case 1: This line throws java.lang.ArrayStoreException
objects[0] = "";
// Test case 2: Program terminates without errors
objects[0] = Foo.A;
// Test case 3: This line throws java.lang.ArrayStoreException
objects[0] = Bar.X;
}
答案 0 :(得分:13)
varargs方法存在警告,因为varargs方法可能导致在调用站点创建隐式数组,而采用数组参数的版本不会。 varargs的工作方式是它使编译器在填充了变量参数的调用站点创建一个数组,然后将其作为单个数组参数传递给该方法。该参数的类型为E[]
,因此创建的数组应为E[]
。
首先,在您的示例中的调用站点中,您根本没有使用变量参数功能。您正在直接传递变量参数数组。因此在这种情况下没有隐式数组创建。
即使您使用了变量参数功能,例如使用someMethod(Foo.A);
,隐式数组创建将创建一个具有可重新类型的数组,即调用站点处的变量参数类型为Foo
,这是编译时已知的具体类型,所以数组创建很好。
问题仅在于调用站点的变量参数类型也是泛型类型或类型参数。例如,像:
public static <E extends Enum<E>> void foo(E obj) {
someMethod(obj);
}
然后编译器需要创建这个泛型类型或类型参数的数组(它需要创建一个E[]
),但是如你所知,Java中不允许创建通用数组。它将创建一个数组,其中组件类型是泛型类型的擦除(在此示例中它将是Enum
),因此它将传递错误类型的数组(在此示例中,传递的数组应该是一个E[]
,但数组的实际运行时类将是Enum[]
,它不是E[]
的子类型。
这种潜在的错误类型的数组并不总是一个问题。大多数情况下,varargs方法将简单地迭代数组并从中获取元素。在这种情况下,数组的运行时类是无关紧要的;重要的是元素是E
类型(实际上它们是)。如果是这种情况,您可以声明方法@SafeVarargs
。但是,如果您的方法实际使用传递的数组的运行时类(例如,如果您将varargs数组作为类型E[]
返回,或者您使用类似Arrays.copyOf()
的内容来创建具有相同运行时的数组class),然后数组的错误运行时类将导致问题,你不能使用@SafeVarargs
。
你的例子有点奇怪,因为,你甚至没有使用元素类型为E
的事实,更不用说数组为E[]
的事实。所以你可以使用@SafeVarargs
,不仅如此,你可以简单地将数组声明为首先采用Object[]
:
private static void someMethod(Object... objects)