Enum<E> 的“潜在堆污染通过 varargs 参数”...为啥?

Posted

技术标签:

【中文标题】Enum<E> 的“潜在堆污染通过 varargs 参数”...为啥?【英文标题】:"Potential heap pollution via varargs parameter" for Enum<E>... why?Enum<E> 的“潜在堆污染通过 varargs 参数”...为什么? 【发布时间】:2016-12-11 09:31:00 【问题描述】:

这个问题特定于使用带有泛型Enum&lt;E&gt;s 的可变参数:

如果我这样定义方法,为什么会收到此警告 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&lt;T&gt;... 的这些问题,但这些答案中显示的场景似乎不适用于Enum&lt;E&gt;...

Potential heap pollution via varargs parameter Type safety: Potential heap pollution via varargs parameter subtrees Potential heap pollution via varargs parameter Eclipse different behaviour for "Unchecked" warning due to "Potential heap pollution via varargs parameter". How to fix?

这个问题的反面是质疑为什么没有警告:

Why doesn't the following method produce potential heap pollution?

示例代码

这是我试图污染堆的方法,但每次错误尝试都会导致 java.lang.ArrayStoreException 而不是污染数组。

我正在使用 Eclipse 4.6.0Java 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;

【问题讨论】:

【参考方案1】:

对于 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[] 的子类型。

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

您的示例有点奇怪,因为您甚至没有使用元素类型为E 的事实,更不用说数组是E[] 的事实了。因此,您可以使用@SafeVarargs,不仅如此,您还可以简单地将数组声明为首先采用Object[]

private static void someMethod(Object... objects)

【讨论】:

谢谢!我可以使用您的示例通过使 foo()someMethod() 返回 E[] 来创建一个受污染的数组,而后者仅返回它接收到的参数。然后访问foo(Foo.A)[0] 得到java.lang.ClassCastException 只是因为数组实际上是一个Enum[],它被转换为一个不兼容的Foo[] 哇,我第一次觉得我理解了那个信息,解释得很好!谢谢!

以上是关于Enum<E> 的“潜在堆污染通过 varargs 参数”...为啥?的主要内容,如果未能解决你的问题,请参考以下文章

`<T extends E>` 形式的通用约束,其中`E` 是`enum`?

Java 中 Enum 如何继承?

Java:从字节码看Enum类型

Java:从字节码看Enum类型

Java:从字节码看Enum类型

Enum深入解析