在 Java的类型擦除中,擦除的概念是什么?

在 Java的类型擦除中,擦除的概念是什么?

45667 次浏览

这基本上是通过编译器技巧在 Java 中实现泛型的方式。编译后的泛型代码 事实上只在谈到 T(或其他类型参数)时使用 java.lang.Object,并且有一些元数据告诉编译器它确实是泛型类型。

当你根据一个泛型类型或方法编译一些代码时,编译器会计算出你的真正意思(例如,T的类型参数是什么) ,并在 编译时验证你做的是正确的事情,但是发出的代码只是按照 java.lang.Object说话——编译器在必要时生成额外的强制转换。在执行时,List<String>List<Date>是完全相同的; 额外的类型信息是由编译器提供的 被抹去了

比较一下 C # ,它在执行时保留信息,允许代码包含与 T.class等价的 typeof(T)表达式——除了后者是无效的。(两者之间还有进一步的区别。NET 泛型和 Java 泛型,请注意。)在处理 Java 泛型时,类型擦除是许多“奇怪”警告/错误消息的来源。

其他资源:

据我所知(作为一个 .NET家伙) ,JVM没有泛型的概念,所以编译器用 Object 替换类型参数,并为您执行所有的强制转换。

这意味着 Java 泛型只不过是语法上的糖,对于通过引用传递时需要装箱/拆箱的值类型,它不会提供任何性能改进。

为了完成已经非常完整的 Jon Skeet 的答案,您必须认识到 一个 href = “ http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html # jls-4.6”rel = “ noReferrer”> 类型擦除的概念源于对 与以前版本的 Java 的兼容性的需求。

最初在 EclipseCon 2007(不再可用)上提出,兼容性包括以下几点:

  • 源兼容性(很高兴有... ...)
  • 二进制兼容性(必须有!)
  • 迁移兼容性
    • 现有的程序必须继续工作
    • 现有库必须能够使用泛型类型
    • 一定是!

原答案:

因此:

new ArrayList<String>() => new ArrayList()

有人提出了一个更大的 翻译: 奇芳,校对: 奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳。具体化是“视抽象概念为真实”,其中语言结构应该是概念,而不仅仅是句法糖。

我还应该提到 Java6的 checkCollection方法,它返回指定集合的动态类型安全视图。任何插入错误类型的元素的尝试都会导致立即出现 ClassCastException

提供编译时(静态)类型检查,但是可以通过未检查的强制转换来破坏这种机制语言中的泛型机制。

通常这不是问题,因为编译器会对所有这些未检查的操作发出警告。

但是,有时仅仅进行静态类型检查是不够的,比如:

  • 当集合传递给第三方库时,必须避免库代码通过插入错误类型的元素破坏集合。
  • ClassCastException表示程序失败,表示类型不正确的元素被放入参数化集合。不幸的是,异常可能在插入错误元素之后的任何时候发生,因此它通常很少或根本没有提供关于问题的真正来源的信息。

更新于2012年7月,将近四年之后:

现时(2012年)的详情载于「 API 迁移兼容规则(签名测试)

Java 编程语言使用擦除来实现泛型,从而确保遗留版本和泛型版本通常生成相同的类文件,除了一些关于类型的辅助信息。 二进制兼容性不会中断,因为可以用泛型类文件替换遗留类文件,而无需更改或重新编译任何客户端代码。

为了方便与非泛型遗留代码的接口,还可以将参数化类型的擦除用作类型。 这种类型称为 原始类型(Java 语言规格3/4.8)。 允许原始类型也确保了源代码的向下兼容。

根据这一点,以下版本的 java.util.Iterator类都是向后兼容的二进制和源代码:

Class java.util.Iterator as it is defined in Java SE version 1.4:


public interface Iterator {
boolean hasNext();
Object next();
void remove();
}


Class java.util.Iterator as it is defined in Java SE version 5.0:


public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}

补充已经补充的 Jon Skeet 的回答..。

有人提到,通过擦除实现泛型会导致一些恼人的限制(例如没有 new T[42])。还有人提到,这样做的主要原因是字节码的向后兼容性。这(大多数情况下)也是正确的。所生成的字节码-目标1.5与去糖类型化-目标1.4有些不同。从技术上讲,甚至有可能(通过巨大的技巧)获得对泛型类型实例化 在运行时的访问,证明字节码中确实存在某些东西。

更有趣的一点(尚未提出)是,使用擦除实现泛型在高级类型系统可以完成的工作方面提供了相当大的灵活性。一个很好的例子就是 Scala 的 JVM 实现对 CLR。在 JVM 上,可以直接实现高级类型,因为 JVM 本身对泛型类型没有限制(因为这些“类型”实际上不存在)。这与 CLR 形成了鲜明的对比,CLR 具有参数实例化的运行时知识。由于这个原因,CLR 本身必须对如何使用泛型有一些概念,这样就可以抵消使用未预料到的规则来扩展系统的尝试。因此,Scala 在 CLR 上的高级类型是通过在编译器内部模拟的一种奇怪的擦除形式来实现的,这使得它们与普通的旧版本不完全兼容。NET 泛型。

如果您想在运行时执行不规则的操作,那么擦除可能不太方便,但是它确实为编译器编写者提供了最大的灵活性。我猜这也是为什么这种情况不会很快消失的原因之一。

作为一个旁注,实际看到编译器在执行擦除时在做什么是一个有趣的练习——这使得整个概念更容易理解。有一个特殊的标志,您可以传递编译器来输出已经删除了泛型并插入了强制转换的 java 文件。举个例子:

javac -XD-printflat -d output_dir SomeFile.java

-printflat是传递给生成文件的编译器的标志。(-XD部分告诉 javac把它交给实际执行编译的可执行 jar,而不仅仅是 javac,但是我跑题了... ...) -d output_dir是必需的,因为编译器需要一些地方来放置新的代码。Java 文件。

当然,这不仅仅是擦除; 所有编译器自动完成的工作都在这里完成。例如,还插入了默认构造函数,新的 foreach 样式的 for循环被扩展为常规的 for循环,等等。很高兴看到自动发生的小事情。

擦除,字面意思是从已编译的字节码中擦除源代码中存在的类型信息。让我们用一些代码来理解这一点。

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;


public class GenericsErasure {
public static void main(String args[]) {
List<String> list = new ArrayList<String>();
list.add("Hello");
Iterator<String> iter = list.iterator();
while(iter.hasNext()) {
String s = iter.next();
System.out.println(s);
}
}
}

如果你编译这段代码,然后用 Java 反编译器反编译它,你会得到这样的东西

import java.io.PrintStream;
import java.util.*;


public class GenericsErasure
{


public GenericsErasure()
{
}


public static void main(String args[])
{
List list = new ArrayList();
list.add("Hello");
String s;
for(Iterator iter = list.iterator(); iter.hasNext(); System.out.println(s))
s = (String)iter.next();


}
}

有很好的解释。我只是添加了一个例子来说明类型擦除是如何与反编译器一起工作的。

原创课程,

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




public class S<T> {


T obj;


S(T o) {
obj = o;
}


T getob() {
return obj;
}


public static void main(String args[]) {
List<String> list = new ArrayList<>();
list.add("Hello");


// for-each
for(String s : list) {
String temp = s;
System.out.println(temp);
}


// stream
list.forEach(System.out::println);
}
}

从它的字节码反编译代码,

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Objects;
import java.util.function.Consumer;


public class S {


Object obj;




S(Object var1) {
this.obj = var1;
}


Object getob() {
return this.obj;
}


public static void main(String[] var0) {


ArrayList var1 = new ArrayList();
var1.add("Hello");




// for-each
Iterator iterator = var1.iterator();


while (iterator.hasNext()) {
String string;
String string2 = string = (String)iterator.next();
System.out.println(string2);
}




// stream
PrintStream printStream = System.out;
Objects.requireNonNull(printStream);
var1.forEach(printStream::println);




}
}