Java:原始类型与泛型

Java:原始类型与泛型,java,arrays,generics,compiler-warnings,Java,Arrays,Generics,Compiler Warnings,考虑下面的示例代码 /* 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

考虑下面的示例代码

/* 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));
    }
}
你说什么?!上面的代码会运行吗?想想看,是的,它肯定会运行,奇怪的是,上面的代码编译并运行得很好。
这与数组稍有不同,它为您提供编译时和运行时保护,并防止此类情况发生。他们为什么这样做?为什么Java允许泛型输入指定类型以外的值

现在来看看答案。使用
insert
方法将
Integer
添加到列表中是非常安全和允许的,因为它与我们为
myList
变量指定的类型相匹配。但是,当我们尝试将
字符串
插入到一个
数组列表
中,该列表原本只保存
整数
值时,当您尝试对错误添加的
字符串
实例调用特定于
整数
的方法时,出现了一个问题,不是在编译时,而是在运行时

为了理解整个问题及其目的,有一件事您应该理解-JVM不知道您试图在
数组列表中插入
字符串
,该列表仅用于保存
整数
s。
您的所有泛型及其类型安全性都受到编译时间的限制只有
。通过一个名为“类型擦除””的过程,编译器从泛型代码中删除所有类型参数。换句话说,即使你写了这样的东西:

public void insert(List list) {
        list.add(new String("55"));
}
List<Integer> 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    
就编译器而言,它试图告诉您,它怀疑程序中的某些代码可能最终会遇到麻烦


总而言之,将泛型看作编译时保护。编译器使用类型信息(在参数中指定的类型)确保不会将错误的内容插入集合(或用户定义的泛型类型),也不会从错误的引用类型获取值所有的通用保护都是编译时的就是这样。

编译时泛型几乎涵盖了所有需要泛型类型断言的情况。对于少数需要在运行时泛型类型安全中进行设计的情况,可以存储运行时类型令牌(RTTT),即
Class
的实例,其中
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
}
公共类Foo{
私人期末班rttt;
公共食品(rttt类){
如果(rttt==null){
抛出新的IllegalArgumentException(“null rttt”);
}
this.rttt=rttt;
}
//RTTT处理运行时类型安全
}
除此之外,大多数时候不需要运行时泛型,Java在引入泛型时遇到了一个问题。让它们在运行时可执行(“可修改”)要么会破坏一堆遗留代码,要么会使语言变得极其复杂。默认情况下,使泛型只在编译时运行,允许遗留代码作为原始类型存在,并且仍然与编写更好的代码共存


这里有一个折衷的因素,比如写原始类型的诱惑。不要那样做。我们有时不得不围着未经检查的转换跳舞。但它几乎总是能完成任务。

我会更改你问题的标题,以便更清楚地解释你在解释什么;也许把它改为“Java:原始类型vs.泛型”。另外,在Stackoverflow文档中,这可能是一个有用的东西,所以可以检查是否添加了类似的内容(可能已经添加了)。我还没有检查……您是否发现文档中已经包含了这个主题?回答得很好。如果可能的话,我会不止一次投票。
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
}