为什么这段代码无法编译

3

这里是一小段代码,但我不明白为什么javac编译不了它。我错过了什么?是否有任何错误?

public class HelloWorld<T> {
    private static enum Type {
    }

    private T value;
    private List<Type> types = new ArrayList<>();

    public T getValue() { return value; }

    public List<Type> getTypes() { return types; }

    public static void main( String[] args ) {
        for ( Type type : new HelloWorld().getTypes() ) { // Error: Type mismatch: cannot convert from element type Object to HelloWorld.Type

        }
    }
}

为什么 getTypes() 返回的是一个原始的 Object 列表,而不是应该返回 Type 列表? 在线编译器链接

你链接到了在线编译器,但没有阅读它的输出吗?它给出的错误是:HelloWorld.java:17: error: incompatible types: Object cannot be converted to Type - Dragondraikk
2
我看到了错误。为什么会发生这种情况?getTypes()没有使用泛型类型参数。该方法的返回类型被明确设置。 - Mirian
1
“Type” 是私有的,而 “getTypes” 公开返回这些私有对象的列表,这个问题只困扰了我吗? - Orace
你已经声明了一个泛型类 HelloWorld,并且在没有参数的情况下进行了初始化:new HelloWorld<TYPE_NEEDED>() - MaxZoom
4个回答

3

我认为这看起来像是编译器的限制。 getTypes 总是返回一个 List<Type>,因此使用原生的 HelloWorld 类型应该没有任何区别。

话虽如此,以下两种解决方案都可以克服错误:

  1. Create a parameterized type of HelloWorld instead of a raw type :

    for (Type type : new HelloWorld<Integer>().getTypes() ) { // any type will do, I chose 
                                                              // Integer arbitrarily to show
                                                              // that it doesn't matter
    
    }
    
  2. Use a local variable to store the List before using it :

    List<Type> types = new HelloWorld().getTypes();
    for (Type type : types) { 
    
    }
    
无论如何,参数化类型应始终优先于原始类型,因此我将使用第一种解决方案(使用在您的类中有意义的任何类型参数)。

1
甚至不带任何参数也可以正常工作,例如 new HelloWorld<>().getTypes(); - Codebender
@Codebender 我没有测试过,因为它需要 Java 7 或更高版本。new HelloWorld<Object>().getTypes() 也可以运行,这很有趣。 - Eran
2
这在我看来似乎是编译器的限制。我发现如果初始化一个原始类型对象并调用返回带有泛型的变量的方法,则该变量也将被转换为原始类型。 - Blip
@MaxZoom,你为什么在Java8中读到它?你能否找到这份文档的链接吗? - Mirian
1
我在这里阅读了相关文献(http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.6)。方法`getTypes(..)`不是泛型方法,因此第三部分适用。 - MaxZoom
显示剩余4条评论

1
当我尝试调试你的代码时,我发现了一些非常有趣的事情。为了解决编译器错误:

错误:类型不匹配:无法从元素类型Object转换为HelloWorld.Type

由于它声明返回的元素是Object类型,因此我决定将其强制转换为List<Type>,如下所示:

public static void main( String[] args ) {
    for ( Type type : (List<Type>)new HelloWorld().getTypes() ) { 

    }
}

这段代码编译成功了,但有警告。为了查看警告信息,我使用了-Xlintjavac命令,并发现以下内容:

HelloWorld.java:15: warning: [rawtypes] found raw type: HelloWorld
        for ( Type type : (List<Type>)new HelloWorld().getTypes() ) {
                                          ^
  missing type arguments for generic class HelloWorld<T>
  where T is a type-variable:
    T extends Object declared in class HelloWorld
HelloWorld.java:15: warning: [unchecked] unchecked cast
        for ( Type type : (List<Type>)new HelloWorld().getTypes() ) {
                                                               ^
  required: List<Type>
  found:    List
2 warnings

在这里,我惊讶地看到了第二个警告。它指出所需的是 List<Type>,但发现是一个 List原始类型。因此,这意味着如果您初始化一个原始类型对象并调用返回具有泛型的变量的方法,则该变量也将转换为原始类型。为了测试这个,我实现了一个名为 HelloWorldTest 的类:

public class HelloWorldTest<T>{
    private T t;

    public HelloWorldTest(T t){
        this.t = t;
    }

    public T getT(){
        return t;
    }
}

然后我更改了你的代码,将条件测试改为:

public class HelloWorld<T> {

    private HelloWorldTest<Integer> test = new HelloWorldTest<>(1);

    public HelloWorldTest<Integer> getTest(){
        return test;
    }

    public static void main( String[] args ) {
        HelloWorldTest<Integer> hello = new HelloWorld().getTest();
    }
}

这个代码可以成功编译,但是会有警告信息,所以使用-Xlint开关进行编译时会得到以下警告:

HelloWorld.java:10: warning: [rawtypes] found raw type: HelloWorld
        HelloWorldTest<Integer> hello = new HelloWorld().getTest();
                                            ^
  missing type arguments for generic class HelloWorld<T>
  where T is a type-variable:
    T extends Object declared in class HelloWorld
HelloWorld.java:10: warning: [unchecked] unchecked conversion
        HelloWorldTest<Integer> hello = new HelloWorld().getTest();
                                                                ^
  required: HelloWorldTest<Integer>
  found:    HelloWorldTest
2 warnings

因此,我们发现HelloWorldTest也被转换为原始类型。

最后我们可以得出结论:如果你初始化一个原始类型对象并调用返回泛型变量的方法,则该变量也将被转换为原始类型

现在当我替换时

    HelloWorldTest<Integer> hello = new HelloWorld().getTest();

带有。
    Integer hello = new HelloWorld().getTest().getT();

正如预期的那样,我收到了错误消息:

HelloWorld.java:10: error: incompatible types: Object cannot be converted to Integer
        Integer hello = new HelloWorld().getTest().getT();
                                                       ^
1 error

最后,如果你用我的HelloWorld类实现中替换主方法为:

    public static void main( String[] args ) {
        String hello = (String) new HelloWorld().getTest().getT();
    }

它成功编译,唯一的警告是:

(保留HTML标签)
HelloWorld.java:10: warning: [rawtypes] found raw type: HelloWorld
            String hello = (String) new HelloWorld().getTest().getT();
                                        ^
  missing type arguments for generic class HelloWorld<T>
  where T is a type-variable:
    T extends Object declared in class HelloWorld
1 warning

这种说法非常误导人,因为它肯定会遇到运行时错误,并再次说明了在泛型中使用原始类型的危险。

0

for 循环更改为为类添加通用类型:

例如:我这里只是举了 'Type' 的例子。

for ( Type type : new HelloWorld<Type>().getTypes() ) {

编辑:

评论中指出,它也可以是“String”(感谢指正)。在您的情况下,应该是该类所需的实际类型。

思路是缺少需要添加的泛型类型。


1
虽然这个答案可能是正确的,但你也应该解释一下为什么会出现错误以及你的回答为什么能够修复它。这样会更有帮助。 - Codebender
1
泛型类型参数 T 在 getTypes 方法中没有被使用,那么它为什么很重要呢?无论泛型类型参数如何,getTypes 应该返回一个 List<Type>(即使是 new HelloWorld<String>().getTypes() 也应该返回一个 List<Type>)。 - Eran
我建议使用 new HelloWorld<?> 而不是 new HelloWorld<Type> - Blip
2
因此,getTypes()方法将返回一个类型为Object的对象 - 为什么?public List<Type> getTypes() { return types; } - 总是返回List<Type>。它为什么会返回Object呢? - Eran
1
这很有趣!我在我的集成开发环境中尝试了一下,当我实例化new HelloWorld()(原始类型)时,整个类型都是原始的。因此,getTypes()突然返回了一个原始的List。这是一种有趣的行为,并且更加证明了永远不要使用原始类型的原因! - Petr Janeček
Eran 的回答中,展示了即使使用 RAW TYPE,这段代码也可以正常工作的原理。 - Blip

0

main 方法中:

for ( Type type : new HelloWorld().getTypes() ) {//Here also you did not mention the type for the new object since 
}

在主方法中试一下这个:
for ( Type type : new HelloWorld<Type>().getTypes() ) {

}

1
当您已经声明了 List<Type> 时,就不需要在 ArrayList 的初始化中添加 Type - Blip
是的,没问题。但是在代码中他写了 new ArrayList<>()。这应该是 new ArrayList() 或者 new ArrayList<Type>()。不然这一行会报错,错误信息可能包括以下内容:"<> 运算符不允许在低于1.7的源级别使用"、"ArrayList 是原始类型。对泛型类型 ArrayList<E> 的引用应该参数化"、"类型安全性:ArrayList 的表达式需要进行未经检查的转换才能符合 "。 - E Do
//这里你根本不需要提及类型: new ArrayList();仍然不正确。在Java 7中,new ArrayList<>()是完全可以的,实际上也是实例化泛型类型的推荐方式。查找“Java 7钻石操作符”。 - Petr Janeček
@Slanec没错。我会更正我的回答。但是我是在6中进行检查的。就像我在上面的评论中提到的,我得到了上述错误。 - E Do

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