Eclipse编译器和javac之间的差异 - 枚举,接口和泛型

6
以下代码在Eclipse中编译(并按预期运行测试):
import java.util.EnumSet;
public class EnumTest {

    static enum Cloneables implements Cloneable {
        One, Two, Three;
    }

    public <T extends Cloneable> T getOne(Class enumType) {
        EnumSet<? extends T> set = EnumSet.allOf(enumType);
        return set.iterator().next();
    }
}

然而,使用javac(JDK 7)直接编译或通过Maven进行编译都会导致以下错误:

type argument ? extends T is not within bounds of type-variable E

坦白说,枚举+接口+类型参数(泛型)同时使用的复杂性在我编写代码时困扰了我,但我认为我最终已经把它搞定了。
目标是编写像这样的调用代码:
Cloneable something = enumTest.getOne(Cloneables.class);

例如,在Eclipse中,以下测试编译并通过:
@Test
public void testGetFirst() {
    assertSame(Cloneables.One, getOne(Cloneables.class));
}

任何关于“正确”的提示,Eclipse还是javac,都会受到赞赏。
如果有关于实现该想法的其他方法的建议也将不胜感激:将类作为方法参数传递给EnumSet.allOf(),并确定EnumSet中枚举对象的类型。
顺便说一下,请不要批评这个方法的目的;我已经从更有用/有意义的代码中简化了它。我不想讨论“从枚举类型中找到第一个元素”的优点 - 这不是这个问题的重点。

1
看起来这可能是javac(JDK 1.7.0_60)中的一个错误。下面接受的答案是一种解决方法(并且实际上是更干净的代码)。请参阅https://bugs.eclipse.org/bugs/show_bug.cgi?id=456459#c7中的详细分析。 - E-Riz
1个回答

3

您需要确保T是枚举类型,否则它将不符合EnumSet的约束条件:

public <T extends Enum<T> & Cloneable> T getOne(Class enumType) 

此外,在EnumSet中不需要通配符,也不应使用原始的Class类型:
public <T extends Enum<T> & Cloneable> T getOne(Class<T> enumType) {
    EnumSet<T> set = EnumSet.allOf(enumType);
    return set.iterator().next();
}

1
我甚至不知道你可以在声明类型参数时应用“&”运算符。即使我已经写了近18年的Java代码,今天我也学到了新东西! - E-Riz
顺便提一下,我使用 Class 的原始形式是因为如果没有 T 扩展 Enum,Eclipse 编译器就不会接受 Class 类型参数的任何值。因此,使用原始形式是不知道如何将 CloneableEnum 结合起来用于 T 的副作用。 - E-Riz
有趣的是,Eclipse 不允许我将集合声明为 EnumSet<T> - 它抱怨说 *"Bound mismatch: The type T is not a valid substitute for the bounded parameter <E extends Enum<E>> of the type EnumSet<E>"*。幸运的是,EnumSet<? extends T> 可以工作并且是等效的。 - E-Riz
1
@E-Riz:不,实际上,边界应该是 T extends Enum<T> & Cloneable。然后你可以再次删除通配符。 - SLaks
@E-Riz 鉴于Java在2004年添加了泛型,你只有10年(将近11年)的时间来学习它们:-)。我发现泛型是Java中最复杂的领域。使用它们并不那么糟糕,但是当涉及到声明任何泛型方法/类时,可能会非常可怕。 - Joseph K. Strauss

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接