"通过varargs参数的潜在堆污染"为Enum <E> ...为什么?

ant*_*tak 8 java enums variadic-functions

这个问题特定于使用带有泛型的varargs Enum<E>:

Type safety: Potential heap pollution via varargs parameter elements如果我像这样定义方法,为什么会收到此警告:

<E extends Enum<E>> void someMethod(E... elements)
Run Code Online (Sandbox Code Playgroud)

与此相反:

<E extends Enum<E>> void someMethod(E[] elements)
Run Code Online (Sandbox Code Playgroud)

因此,在声明方法之前我应该​​注意什么@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;
}
Run Code Online (Sandbox Code Playgroud)

new*_*cct 16

varargs方法存在警告,因为varargs方法可能导致在调用站点创建隐式数组,而采用数组参数的版本则不会.varargs的工作方式是它使编译器在填充了变量参数的调用站点创建一个数组,然后将其作为单个数组参数传递给该方法.参数将具有类型E[],因此创建的数组应为a E[].

首先,在您的示例中的调用站点中,您根本不使用变量参数功能.您正在直接传递变量参数数组.因此在这种情况下没有隐式数组创建.

即使您使用了变量参数功能,例如with someMethod(Foo.A);,隐式数组创建也会创建一个具有可重新类型的数组,即在调用站点处已知变量参数类型,Foo这是编译时已知的具体类型,所以阵列创建很好.

仅当调用站点处的变量参数类型也是泛型类型或类型参数时,问题才出现.例如,类似于:

public static <E extends Enum<E>> void foo(E obj) {
    someMethod(obj);
}
Run Code Online (Sandbox Code Playgroud)

然后编译器需要创建这个泛型类型或类型参数的数组(它需要创建一个E[]),但是如你所知,Java中不允许创建通用数组.它会创建一个数组,其组件类型是泛型类型的擦除(在这个例子中它将是Enum),因此它将传递一个错误类型的数组(在这个例子中,传递的数组应该是一个E[],但是数组的实际运行时类将Enum[]不是其子类型E[].

这种潜在的错误类型的数组并不总是一个问题.大多数情况下,varargs方法将简单地迭代数组并从中获取元素.在这种情况下,数组的运行时类是无关紧要的; 重要的是元素是类型E(实际上它们是).如果是这种情况,您可以声明您的方法@SafeVarargs.但是,如果您的方法实际使用传递的数组的运行时类(例如,如果您将varargs数组作为类型返回E[],或者您使用类似于Arrays.copyOf()创建具有相同运行时类的数组),那么错误的运行时类数组将导致问题,你不能使用@SafeVarargs.

你的例子有点奇怪,因为,你甚至没有使用元素类型E的事实,更不用说数组的事实了E[].所以你可以使用@SafeVarargs,不仅如此,你可以简单地将数组声明为Object[]第一个位置:

private static void someMethod(Object... objects)
Run Code Online (Sandbox Code Playgroud)

  • 哇,我第一次感觉我理解了这条信息,解释得很好!谢谢你! (2认同)