2017-03-20 44 views
3

考虑下面怪异的行为:泛型VS阵列

/* 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任何投诉。 insert()方法在我们的type-safe ArrayList中插入一个新的Integer对象。但是,如果我将插入方法更改为如下所示:

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

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

+0

我会改变你的问题的标题,以更清楚地解释你解释的是什么;也许可以将其改为“** Java:原始类型与泛型**”。另外,在Stackoverflow文档中这可能是一件有用的事情,因此可能检查是否添加了类似的东西(它可能有)。 – ostrichofevil

回答

2

现在的答案。使用insert方法将Integer添加到我们的列表中是相当安全和允许的,因为它与我们为我们的myList变量指定的类型相匹配。但是,当我们尝试将String插入ArrayList,而该值只能保存Integer值时,则存在一个问题,不是在编译时,而是在运行时,当您尝试调用Integer特定的方法对错误地添加String实例。

有一件事你应该为了了解这整个问题,其目的理解 - JVM有不知道你试图插入一个String成意指ArrayList只举行Integer秒。所有的仿制药和它们的类型安全都是仅限编译时间。通过称为“类型擦除”的过程,编译器从通用代码中移除所有类型参数。换句话说,即使你写的是这样的:

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  

至于编译器担心,它试图告诉你,它怀疑你的程序中的一些代码可能最终陷入困境。

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

0

编译时泛型涵盖了几乎所有需要泛型类型断言的情况。对于需要在运行时泛型类型安全中设计的罕见情况,可以存储运行时类型令牌(RTTT),Class<T>的实例,其中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在引入泛型时遇到了问题。使它们在运行时可执行(“可授权”)会破坏一堆遗留代码或使语言复杂化。仅在默认情况下编译泛型允许遗留代码生存为原始类型,并且仍然与更好编写的代码共存。

那里有一个妥协的元素,比如写入原始类型的诱惑。不要这样做。我们有时必须围绕未经检查的转换跳舞。但它几乎总能完成工作。