【问题标题】:Confused by Java generics requiring a cast对需要强制转换的 Java 泛型感到困惑
【发布时间】:2014-02-24 12:02:52
【问题描述】:

我对以下代码感到困惑:

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class GenericsTest<T extends List> {

  public void foo() {
    T var = (T) new LinkedList();
  }

  public static void main(String[] args) {
      GenericsTest<ArrayList> gt1 = new GenericsTest<ArrayList>();
      gt1.foo();
      System.out.println("Done");
  }
}

T 的运行时类型似乎是 java.util.List,无论我传递给构造函数的 Type 参数是什么。

那么为什么编译器在分配 var 时需要强制转换为 T?它不应该在编译时知道 LinkedList 可以分配给 List 吗?

我知道代码是伪造的,并且我理解为什么它在运行时有效,尽管它看起来不应该。令我困惑的部分是为什么编译器要求我在赋值时输入 (T)?然而,它编译得非常好,没有虚假的演员表。

大概,编译器理解擦除。似乎编译器也应该能够在没有强制转换的情况下编译代码。

【问题讨论】:

    标签: java generics casting


    【解决方案1】:

    LinkedList 可以分配给List,但它可能不能分配给T

    在某些方面,您的测试代码不应该工作 - 它只是因为强制转换被编译器有效地删除,因为在执行时无用(由于类型擦除)。它在编译时很有用,因为它要求您(开发人员)向编译器保证您正在做一些它无法检查的有效事情。碰巧的是,您所做的是无效的,但是编译器无法知道这一点,并且在没有更多信息的情况下无法在执行时检查它。不过,您可以提供该信息:

    import java.util.ArrayList;
    import java.util.LinkedList;
    import java.util.List;
    
    public class GenericsTest<T extends List> {
    
      Class<T> clazz;
    
      public GenericsTest(Class<T> clazz) {
        this.clazz = clazz;
      }
    
      public void foo() {
        T var = clazz.cast(new LinkedList());
      }
    
      public static void main(String[] args) {
        GenericsTest<ArrayList> gt1 = 
            new GenericsTest<ArrayList>(ArrayList.class);
        gt1.foo();
        System.out.println("Done");
      }
    }
    

    现在这将失败并出现适当的异常 - 类型信息在执行时仍然可用,我们正在使用它以安全的方式执行转换。

    请注意,如果您在编译时带有适当的警告,编译器会告诉您您的原始代码并没有真正检查任何内容。

    有关此主题和许多其他主题的更多信息,请查看相当全面的Java Generics FAQ

    【讨论】:

    • 我试图链接到 Angelika Langer 的常见问题解答,但它有一种令人讨厌的倾向,即每次尝试时都会冻结 IE7
    • 感谢您的回复。我知道代码是伪造的,不应该工作。我无法理解的是为什么编译器会在删除 (T) 时给出错误,即使您指出 (T) 毫无意义并且将始终有效。
    • 这不是毫无意义的——它让开发人员承认这些类型不一定合适。添加演员表后的警告告诉开发人员演员表实际上只是一个创可贴;还有一个问题等着你。
    【解决方案2】:

    在发帖人的评论中,

    但是,大概编译器知道 那为什么它需要 投射到 (T)。有没有可能的方法 演员阵容会失败吗?

    这个演员不会失败。但是编译器警告说,这个代码设置了一个定时炸弹,用ClassCastException炸毁其他地方

    在示例中,没有理由使用泛型,因为没有一个 API 使用类型变量 T。看看更现实的泛型应用。

       public class GenericsTest<T extends List> {
    
     3   public T foo() {
     4     T var = (T) new LinkedList();
     5     return var;
     6   }
    
     8   public static void main(String... argv) {
     9     GenericsTest<ArrayList> gt1 = new GenericsTest<ArrayList>();
    10     gt1.foo();
    11     System.out.println("Test one okay");
    12     ArrayList<?> list = gt1.foo();
    13     System.out.println("Test two okay");
    14   }
    
       }
    

    ClassCastException 在第 12 行被抛出。ClassCastException,没有演员表?调用代码完全正确。无效转换,即错误,位于被调用方法的第 4 行。但异常是在遥远的某个时间和地点引发的。

    Java 泛型的目的是确保代码是类型安全的。如果所有代码在没有“未经检查”警告的情况下编译,则保证不会在运行时引发ClassCastException。但是,如果您依赖的库编写不正确,就像这个例子一样,承诺就会被打破。

    【讨论】:

      【解决方案3】:

      那是因为type erasure;在编译时,泛型参数会变成它的下限。

      编译器试图告诉你有问题。如果它做了你认为应该做的事,你会得到一个ClassCastException——你不能把LinkedList转换成ArrayList

      【讨论】:

      • 感谢您的链接。这就解释了为什么运行时类型是 List... 而我没有得到 ClassCastException。但是,大概编译器知道那为什么需要强制转换为 (T)。演员阵容有没有可能失败?
      【解决方案4】:

      我以为你来自 C#?在这种情况下,Java 泛型不是 C# 泛型。

      当您提供泛型类型参数时,Java 不会生成强类型类,它只是使用适用于所有可能提供的泛型类型参数组合的最不常见的命名类型。

      因此,在这种情况下使用 List,因为您指定了 extends List

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-11-28
        • 1970-01-01
        • 1970-01-01
        • 2011-06-07
        • 2011-02-02
        • 1970-01-01
        • 2018-08-30
        相关资源
        最近更新 更多