Java中泛型中的擦除概念是什么?
Java中泛型中的擦除概念是什么?基本上是泛型通过编译器技巧在Java中实现的方式。编译后的泛型代码实际上只使用Java中泛型中的擦除概念是什么?,java,generics,Java,Generics,Java中泛型中的擦除概念是什么?基本上是泛型通过编译器技巧在Java中实现的方式。编译后的泛型代码实际上只使用java.lang.Object(或其他类型参数),并且有一些元数据告诉编译器它确实是泛型类型 当您针对泛型类型或方法编译某些代码时,编译器会计算出您真正的意思(即T的类型参数是什么),并在编译时验证您所做的事情是否正确,但是,发出的代码同样只是用java.lang.Object的术语说话——编译器在必要时生成额外的强制转换。在执行时,列表和列表完全相同;额外的类型信息已被编译器删除
java.lang.Object
(或其他类型参数),并且有一些元数据告诉编译器它确实是泛型类型
当您针对泛型类型或方法编译某些代码时,编译器会计算出您真正的意思(即T
的类型参数是什么),并在编译时验证您所做的事情是否正确,但是,发出的代码同样只是用java.lang.Object
的术语说话——编译器在必要时生成额外的强制转换。在执行时,列表
和列表
完全相同;额外的类型信息已被编译器删除
将此与C#进行比较,C#在执行时保留信息,允许代码包含诸如typeof(T)
之类的表达式,这相当于T.class
——但后者无效。(请注意,.NET泛型和Java泛型之间还有进一步的区别。)在处理Java泛型时,类型擦除是许多“奇怪”警告/错误消息的来源
其他资源:
- (PDF-强烈推荐;链接可能需要定期更改)
这意味着Java泛型只不过是语法糖,对于通过引用传递时需要装箱/取消装箱的值类型,它不会提供任何性能改进。要完成Jon Skeet已经非常完整的答案,您必须认识到的概念源于与以前版本的Java兼容的需要 最初在EclipseCon 2007(不再提供)上展示,兼容性包括以下几点:
- 源代码兼容性(很好有…)
- 二进制兼容性(必须有!)
- 迁移兼容性
- 现有计划必须继续发挥作用
- 现有库必须能够使用泛型类型
- 一定有李>
new ArrayList<String>() => new ArrayList()
补充已经补充的Jon Skeet答案 已经提到,通过擦除实现泛型会导致一些恼人的限制(例如,no
newt[42]
)。还有人提到,采用这种方式的主要原因是字节码的向后兼容性。这(大部分)也是事实。目标1.5生成的字节码与目标1.4生成的字节码略有不同。从技术上讲,甚至可以(通过巨大的诡计)在运行时访问泛型类型实例化,从而证明字节码中确实存在某种东西
更有趣的一点(尚未提出)是,使用擦除实现泛型在高级类型系统可以实现的方面提供了更大的灵活性。Scala的JVM实现与CLR就是一个很好的例子。在JVM上,可以直接实现更高的类型,因为JVM本身对泛型类型没有任何限制(因为这些“类型”实际上是不存在的)。这与CLR不同,CLR具有参数实例化的运行时知识。正因为如此,CLR本身必须对泛型的使用有一定的概念,从而使使用意外规则扩展系统的尝试无效。因此,CLR上的Scala高级类是使用编译器内部模拟的一种奇怪的擦除形式实现的,这使得它们与普通的.NET泛型不完全兼容
当您想在运行时做一些不愉快的事情时,擦除可能会很不方便,但它确实为编译器编写人员提供了最大的灵活性。我猜这就是为什么它不会很快消失的部分原因。作为一个旁注,这是一个有趣的练习,可以看到编译器在执行擦除时正在做什么——这使整个概念更容易理解。有一个特殊的标志可以传递给编译器,以输出已删除泛型并插入强制类型转换的java文件。例如:
javac -XD-printflat -d output_dir SomeFile.java
-printflat
是传递给生成文件的编译器的标志。(-XD
部分告诉javac
将其交给实际执行编译的可执行jar,而不仅仅是javac
,但我离题了……)之所以需要-d输出目录
,是因为编译器需要一些地方来放置新的.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);
}
}
}
有很好的解释。我只添加了一个示例来说明类型擦除如何与反编译器一起工作 原始类
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);
}
}
Java泛型无论如何都不能表示值类型——没有列表这样的东西。然而,Java中根本没有pass-by-reference,它是严格意义上的pass-by-value(该值可能是一个引用)。请注意,向后兼容可以在没有类型擦除的情况下实现,但也不能在Java程序员学习一组新集合的情况下实现。这正是.NET所走的路线。换句话说,这是第三个bu
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);
}
}