【问题标题】:Java: Raw Types vs. Generics [duplicate]Java:原始类型与泛型 [重复]
【发布时间】:2017-03-20 02:36:57
【问题描述】:

考虑下面的示例代码

/* The new Java 5 or later Generic code */
class TestInserter {
    public static void main(String[] ar) {
        List<Integer> myList = new ArrayList<Integer>();
        myList.add(20);
        myList.add(42);
        Inserter ins = new Inserter();
        ins.insert(myList);
    }
}

/* Legacy Code */
class Inserter {
    public void insert(List list) {
        list.add(new Integer(55));
    }
}

编译和执行上述代码将运行良好,编译器或 JVM 没有任何抱怨。 non type-safe insert() 方法在我们的type-safe ArrayList 中插入一个新的整数对象。但是,如果我将插入方法更改为这样的:

public void insert(List list) {
        list.add(new String("55"));
}

怎么说?!上面的代码会运行吗?想一想,是的,它肯定会运行,奇怪,但是上面的代码编译并运行得很好。 这与 Arrays 略有不同,后者为您提供编译时和运行时保护并防止此类事情发生。为什么他们这样做泛型?为什么 Java 允许泛型放入指定类型以外的值?!

【问题讨论】:

  • 我会更改您问题的标题,以更清楚地解释您要解释的内容;也许将其更改为“Java:原始类型与泛型”。此外,这在 Stackoverflow 文档中可能很有用,因此请检查是否添加了类似的内容(可能有)。
  • 我还没有检查...您是否发现文档中已经涵盖了该主题?

标签: java arrays generics compiler-warnings


【解决方案1】:

现在来回答。使用insert 方法将Integer 添加到我们的列表中是非常安全和允许的,因为它匹配我们为myList 变量指定的类型。但是当我们尝试将 String 插入到仅包含 Integer 值的 ArrayList 中时,就会出现问题,不是在编译时而是在运行时,当您尝试在错误添加的String 实例上调用Integer 特定的方法。

为了理解整个问题及其目的,您应该了解一件事 - JVM 不知道您试图将 String 插入到仅包含 @ 的 ArrayList 987654331@s.您的所有泛型及其类型安全性仅限于编译时。通过称为“type erasure”的过程,编译器从泛型代码中删除所有类型参数。换句话说,即使你写了这样的东西:

List<Integer> myList = new ArrayList<>();

编译完成后变为如下:

List myList = new ArrayList();

但是,他们为什么要这样保留泛型?

答案很简单!如果不是因为这种奇怪的行为,那么早期 Java 版本的遗留代码就会被破坏,百万 Java 开发人员将不得不编辑数以万亿计的旧 Java 代码以使其再次工作!

但不要责怪编译器;当您尝试运行代码时,编译器会尝试通过以下警告向您发出警告:

$> javac TestInserter.java  
Note: TestInserter.java uses unchecked or unsafe operations.  
Note: Recompile with -Xlint:unchecked for details.

当您按照编译器的要求执行以下操作时:

$> javac -Xlint:unchecked TestInserter.java
TestInserter.java:15: warning: [unchecked] unchecked call to add(E) as a member of the raw type List  
        list.add(new String("55"));  
                 ^  
where E is a type-variable:  
  E extends Object declared in interface List  
1 warning    

就编译器而言,它试图告诉您它怀疑您的程序中的某些代码可能最终会出现问题。

总结一下,泛型只是编译时保护。编译器使用类型信息(参数中指定的类型)来确保您不会将错误的东西插入到集合(或用户定义的泛型类型)中,并且不会从错误的引用类型中获取值。 所有通用保护都是编译时间!就是这样。

【讨论】:

  • 很好的答案。如果可能的话,我会多次投票。
【解决方案2】:

编译时泛型几乎涵盖了需要泛型类型断言的所有情况。对于需要在运行时泛型类型安全中进行设计的极少数情况,您可以存储 运行时类型标记 (RTTT),Class&lt;T&gt; 的实例,其中 T 是泛型参数。

public class Foo<T> {
  private final Class<T> rttt;
  public Foo(Class<T> rttt) {
    if (rttt == null) {
      throw new IllegalArgumentException("null rttt");
    }
    this.rttt = rttt;
  }
  // etc. RTTT handles runtime type safety
}

除了大多数时候不需要运行时泛型之外,Java 在引入泛型时也遇到了问题。使它们在运行时可强制执行(“可修改”)要么会破坏一堆遗留代码,要么会使语言变得极其复杂。默认情况下,使泛型仅编译时允许遗留代码作为原始类型存在,并且仍然与编写更好的代码共存。

这里有妥协的成分,比如写原始类型的诱惑。不要那样做。我们有时不得不绕着未经检查的转换跳舞。但它几乎总是能完成工作。

【讨论】:

    猜你喜欢
    • 2018-06-12
    • 2012-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多