【问题标题】:enums and generic methods in javajava中的枚举和泛型方法
【发布时间】:2009-10-09 01:33:25
【问题描述】:

Java 泛型系统中的一些极端情况仍然存在问题。

我有这个方法(我只对签名感兴趣):

 interface Extractor<RETURN_TYPE> {
    public <U extends Enum<U>> RETURN_TYPE extractEnum(final Class<U> enumType);
}

(想想一个接口,它的实现有时会提取一个 EnumSet 有时一个实现会提取一个 JComboBox 等)

我想用在运行时获得的类来调用它,所以我只是这样称呼它:

 public static <RETURN_TYPE> RETURN_TYPE extractField(final Extractor<RETURN_TYPE> extractor, final Field field) {
    final Class<?> type = field.getType();
    if (type.isEnum())
        return extractor.extractEnum(/* error here*/type.asSubclass(Enum.class));
    throw new RuntimeException("the rest of the visitor is not necessary here");
}

我收到一条奇怪的错误消息: 不兼容的类型 找到:java.lang.Object 必需:RETURN_TYPE

消息的位置,如果就在调用的开始括号之后,在类型的“t”之前。

如果我从非通用上下文中调用它,它可以工作:

    Integer extractField(final Extractor<Integer> extractor, final Field field) {
        final Class<?> type = field.getType();
        if (type.isEnum())
            return extractor.extractEnum(type.asSubclass(Enum.class));
        throw new RuntimeException("the rest of the visitor is not necessary here");
    }

请问有人对此问题有解释和解决方案吗?

这是一个完整的文件,供想要玩它的人使用:

public class Blah {
    interface Extractor<RETURN_TYPE> {
        public <U extends Enum<U>> RETURN_TYPE extractEnum(final Class<U> enumType);
    }

    public static <RETURN_TYPE> RETURN_TYPE extractField(final Extractor<RETURN_TYPE> extractor, final Field field) {
        final Class<?> type = field.getType();
        if (type.isEnum())
            return extractor.extractEnum(/* error here*/type.asSubclass(Enum.class));
        throw new RuntimeException("the rest of the visitor is not necessary here");
    }

    public static Integer extractField(final Extractor<Integer> extractor, final Field field) {
        final Class<?> type = field.getType();
        if (type.isEnum())
            return extractor.extractEnum(type.asSubclass(Enum.class));
        throw new RuntimeException("the rest of the visitor is not necessary here");
    }
}

提前致谢,

妮可

【问题讨论】:

  • 你的问题让我想起了一部老花生漫画。莱纳斯:“我觉得我的舌头不行了!我想说 brlalalala 但它总是说出来 brzazazaa!”露西:“我认为你的整个大脑都在工作。”
  • 我不得不说......没有可编译的玩具示例(没有毫无意义的抽象),很难理解这个问题。
  • Jonathan > 重点是它不能编译。我将添加一个完整的课程。
  • 你暗示它不编译,但它为我编译(带有未经检查的警告)。
  • 有意思,请问你用的是什么JDK?对我来说,Intellij 没有放任何红色,但编译器给了我一个错误。我在 mac os x (32bits) 上使用 java 1.6.0_15

标签: java generics enums crtp


【解决方案1】:

实际上,如果这是您的编译器中的错误,我不会感到惊讶。通过认真使用泛型(你正在做的事情,结合参数化方法、有界通配符和泛型的其他“高级”使用),去年我在 javac 中遇到了两三个问题(令人讨厌的是,同一个单元通常在 IDE 中编译得很好)。

在您的情况下,我相当确定这是一个错误,因为编译器抱怨的部分是 extractor.extractEnum 返回 Object 而不是 RETURN_TYPE。不管它对你的枚举方法参数做了什么疯狂的推断......它知道从类型签名中提取器是一个Extractor&lt;RETURN_TYPE&gt;,所以你应该总是可以说return extractor.extractEnum(...);

该死的证据是,即使您使用 null 参数调用该方法(从而完全消除了参数中枚举泛型的任何潜在并发症),编译器仍然会抱怨。特别是,它现在说它认为 Extractor 的返回类型是 U&lt;RETURN_TYPE&gt;,这显然是垃圾。

一般来说,解决这些问题的解决方案是引入一些显式强制转换。如果您将 extractEnum 的输出转换为 RETURN_TYPE,编译器会高兴吗? 编辑:不,真的不是——它抱怨U&lt;RETURN_TYPE&gt;RETURN_TYPE 是不可转换的——eep...

如果您使用的是最近的 1.6 编译器,我建议您将此报告给 Sun,因为这是 javac 的一个大问题。这是一个非常简短的测试用例来练习它:

public class Test {
  interface Sub<O> {
    public <I extends Enum<I>> O method(final Class<I> enumType);
  }

  public static <O> O go(final Sub<O> sub) {
    return sub.method(null);
  }
}

附:一般约定是使用单个大写字母来指定泛型类型参数。我不会说“我是对的,你错了”,但请记住,与使用 Extractor 相比,我发现您的代码更难阅读和遵循。 (从 Hemal 对他的回答的措辞来看,这对他来说也是一样的。)

【讨论】:

  • 好的,感谢您的深入分析。但是我有一个苹果,所以我想他们会把我的虫子扔掉。我没有检查就买了你的解释,这是有道理的。为了清楚起见,我添加了长变量类型名称,因为人们可能不理解这个变量的意义,在我的代码中我也使用一个字母,即使我不确定这个约定是否真的很好,它也被广泛接受。
【解决方案2】:

我无法推断出最初的问题。

Extractor.extract 有两个类型参数,U 必须是 EnumT,这是任意类型,我是否正确?在通用调用中,VV 既是 T 又是 U?如果UVV,则参数必须是Class&lt;VV&gt;,而不是Class&lt;Enum&gt;。以下为我编译,但如您所见,通用方法需要提供 Class&lt;VV&gt; 的实例

class Outer {
  static class Extractor<T> {
    public <U extends Enum<U>> T extract(final Class<U> lala) {
      return null;
    }
    // two type parameters, T and U
    // U must be an enum
    // T is arbitrary class
  }

  static <VV extends Enum<VV>> VV extract(final Extractor<VV> extractor, Class<VV> vvClass) {
    final Class<?> type = null;
    return extractor.extract(vvClass);
    // Outer.extract returns VV 
    // T -> VV
    // it seems VV is also U
  }
}

【讨论】:

  • 不,抱歉以难以理解的方式陈述我的问题,我还找不到任何简化。所以,T 是提取器的返回类型,U 是静态枚举类型。在我的使用点中,我创建了一个在提取的数据类型上仍然通用的方法,但从 Field.getType() 获取要声明的枚举类型。
【解决方案3】:

看起来你的Field.getType( ) 只会返回一个通用类。因为您将尝试使用已删除类型信息的类型,所以此函数将不得不发出 "unchecked" 警告,并且泛型接口上的所有类型信息都将被删除。

请记住,使用类型擦除后,您的界面如下所示:

interface Extractor {
    public Object extractEnum( final Class enumType );
}

所以,因为所有类型信息都被擦除了,extractEnum 的返回类型是java.lang.Object,所以你必须添加一个特定的演员表。这正是您收到的错误消息。

这是修改后的代码示例。

@SuppressWarnings( "unchecked" )
public static <RETURN_TYPE> RETURN_TYPE extractField(
       final Extractor<RETURN_TYPE> extractor,
       final Field field
    )
{
    final Class type = field.getType(); // unchecked

    if (type.isEnum())
    {
        // needs cast
        return (RETURN_TYPE) extractor.extractEnum( type ); // unchecked
    }
    throw new RuntimeException("the rest of the visitor is not necessary here");
}

忽略此评论:回答有关为什么会出现编译错误的原始问题。这是一个方钉成圆孔的问题。 type.asSubclass( Enum.class )返回Class&lt; ? extends Enum &gt;,还是和接口调用所期望的Class&lt; U &gt;不一样。

【讨论】:

  • 是的,无论如何我都必须在这个类中处理一个警告。但这并不能解释为什么我会出现这个错误。
  • 顺便说一句,在我的环境(Eclipse,JDK 1.6.0)中,具有显式 Integer 类型的第二种方法也没有编译。您的系统上的具体错误消息可能会有所不同,但根本问题与我在上一段中描述的相同。
  • 我可以将所有警告集中在一个语句中,感谢您的想法: (RETURN_TYPE)extractor.extractEnum((Class) type) 但我认为您的方钉想法不正确。看看这个:stackoverflow.com/questions/1458779/how-to-cast-to-crtp-in-java 它有效,我在我的代码中使用了 2-3 次。您是否将这些警告作为错误提出?
  • 我检查了 type.asSubclass( Enum.class ) 将很好地传递给 extractEnum 调用。所以我的方钉想法确实是错误的。但这里的主要问题是,在您的代码中,类型擦除已经发生。正因为如此,使用 Generic vs. Raw 类型不会给您带来任何好处(您仍然会收到“未检查”警告)。但是,因为您已经测试过“类型”是 Enum 的实例,所以对 asSubclass( Enum.class ) 的调用是完全多余的。这就是为什么您应该为“类型”变量使用原始类类型并将其直接传递给 extractEnum
  • 还有一条评论。您应该注意并且尽可能不要生成“未经检查”的警告,但有时您确实需要删除类型信息。当您遇到这种情况时,请记住,擦除为 Raw 类型几乎总是更好(如果您无论如何都要违反法律,请大范围地这样做)。在我看来,您的用例完全可以遵循这样的模式。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多