当前位置:  开发笔记 > 编程语言 > 正文

Java泛型类型擦除:何时以及发生了什么?

如何解决《Java泛型类型擦除:何时以及发生了什么?》经验,为你挑选了6个好方法。

我在Oracle的网站上读到了Java的类型擦除.

什么时候发生类型擦除?在编译时还是运行时?当班级加载?当类被实例化时?

很多站点(包括上面提到的官方教程)都说在编译时会发生类型擦除.如果在编译时完全删除了类型信息,那么当调用使用泛型的方法而没有类型信息或错误的类型信息时,JDK如何检查类型兼容性?

考虑以下示例:Say class A有一个方法,empty(Box b).我们编译A.java并获取类文件A.class.

public class A {
    public static void empty(Box b) {}
}
public class Box {}

现在我们创建另一个类B,该类empty使用非参数化参数(原始类型)调用该方法:empty(new Box()).如果我们编译B.javaA.class在类路径中,javac的是足够聪明,引发警告.所以A.class 一些类型信息存储在其中.

public class B {
    public static void invoke() {
        // java: unchecked method invocation:
        //  method empty in class A is applied to given types
        //  required: Box
        //  found:    Box
        // java: unchecked conversion
        //  required: Box
        //  found:    Box
        A.empty(new Box());
    }
}

我的猜测是类加载时会发生类型擦除,但这只是猜测.那么什么时候发生?



1> Jon Skeet..:

类型擦除适用于泛型的使用.人们确实在类文件元数据说的方法/类型是否通用的,什么约束等,但仿制药的时候使用,他们转换成编译时检查和执行时间蒙上.所以这段代码:

List list = new ArrayList();
list.add("Hi");
String x = list.get(0);

编译成

List list = new ArrayList();
list.add("Hi");
String x = (String) list.get(0);

在执行时,无法找到T=String列表对象 - 信息消失了.

...但是List界面本身仍然宣称自己是通用的.

编辑:只是为了澄清,编译器并保留有关的信息变量是一个List-但您仍然无法找出T=String列表对象本身.


@Rogerio:你怎么知道对象来自哪里?如果你有一个类型为`List <?的参数?扩展InputStream>`你怎么知道它创建时的类型?即使你*可以*找出存储参考的字段类型,你为什么要这样做?为什么您应该能够在执行时获取有关该对象的所有其他信息,而不是其泛型类型参数?你似乎试图让类型擦除成为这个微小的东西,它不会真正影响开发人员 - 而在某些情况下我发现它是一个非常重要的*问题.
不,即使在使用泛型类型时,也可能存在运行时可用的元数据.无法通过Reflection访问局部变量,但对于声明为"List l"的方法参数,将在运行时提供类型元数据,可通过Reflection API获得.是的,"类型擦除"并不像许多人想的那么简单......
@Rogerio:当我回复你的评论时,我相信你在能够获得*变量*的类型和能够获得*对象*的类型之间感到困惑.对象本身不知道它的类型参数,即使该字段有.

2> Richard Gome..:

编译器负责在编译时理解泛型.编译器还负责在我们称为类型擦除的过程中抛弃对泛型类的这种"理解" .一切都发生在编译时.

注意:与大多数Java开发人员的信念相反,尽管采用非常有限的方式,仍可以保留编译时类型信息并在运行时检索此信息.换句话说:Java确实以非常有限的方式提供了具体化的泛型.

关于类型擦除

请注意,在编译时,编译器具有可用的完整类型信息,但生成字节代码时,通常会在称为类型擦除的过程中有意删除此信息.由于兼容性问题,这是通过这种方式完成的:语言设计者的意图是提供完整的源代码兼容性和平台版本之间的完全字节代码兼容性.如果以不同方式实现,则在迁移到较新版本的平台时,您必须重新编译旧版应用程序.完成它的方式,保留所有方法签名(源代码兼容性),您不需要重新编译任何东西(二进制兼容性).

关于Java中的reified通用

如果需要保留编译时类型信息,则需要使用匿名类.关键是:在匿名类的非常特殊的情况下,可以在运行时检索完整的编译时类型信息,换言之,意思是:具体化的泛型.这意味着当涉及匿名类时,编译器不会丢弃类型信息; 此信息保存在生成​​的二进制代码中,运行时系统允许您检索此信息.

我写了一篇关于这个主题的文章:

http://rgomes-info.blogspot.co.uk/2013/12/using-typetokens-to-retrieve-generic.html

关于上述文章中描述的技术的注释是,对于大多数开发人员而言,该技术是模糊的.尽管它工作正常并且运行良好,但大多数开发人员对此技术感到困惑或不安.如果您有共享代码库或计划向公众发布代码,我不建议使用上述技术.另一方面,如果您是代码的唯一用户,则可以利用此技术为您提供的强大功能.

示例代码

上面的文章包含示例代码的链接.



3> erickson..:

如果您有一个泛型类型的字段,则其类型参数将编译到该类中.

如果您有一个接受或返回泛型类型的方法,那么这些类型参数将被编译到类中.

此信息是编译器用于告诉您无法将a传递Boxempty(Box)方法的信息.

该API是复杂的,但你可以通过检查用类似方法反射API这种类型的信息getGenericParameterTypes,getGenericReturnType以及,对田,getGenericType.

如果您有使用泛型类型的代码,则编译器会根据需要(在调用方中)插入强制转换来检查类型.通用对象本身只是原始类型; 参数化类型被"擦除".因此,在创建a时new Box(),没有关于对象中的Integer类的信息Box.

Angelika Langer的FAQ是我在Java Generics中看到的最佳参考.


实际上,它是编译到类中的字段和方法的_formal_泛型类型,即典型的"T".要获得泛型类型的_real_类型,必须使用["anonymous-class trick"](http://stackoverflow.com/questions/1901164/get-type-of-a-generic-parameter-in-java -with反射/ 15999255#15999255).

4> Eugene Yokot..:

Java语言中的泛型是一个非常好的指南.

泛型由Java编译器实现为称为擦除的前端转换.您可以(几乎)将其视为源到源的转换,从而将通用版本loophole()转换为非泛型版本.

所以,它是在编译时.JVM永远不会知道ArrayList您使用了哪个.

我还建议Skeet先生的答案是什么是Java中泛型的擦除概念?



5> Vinko Vrsalo..:

类型擦除发生在编译时.什么类型的擦除意味着它会忘记泛型类型,而不是每种类型.此外,仍然会有关于通用类型的元数据.例如

Box b = new Box();
String x = b.getDefault();

转换为

Box b = new Box();
String x = (String) b.getDefault();

在编译时.您可能会收到警告,不是因为编译器知道通用类型是什么类型,而是相反,因为它不够了解所以它不能保证类型安全.

此外,编译器会在方法调用中保留有关参数的类型信息,您可以通过反射检索这些信息.

本指南是我在这个主题上发现的最好的.



6> 小智..:

术语“类型擦除”实际上并不是对Java泛型问题的正确描述。类型擦除本身并不是一件坏事,实际上对于性能而言这是非常必要的,并且经常用于多种语言,例如C ++,Haskell,D。

厌恶之前,请记得从Wiki正确定义类型擦除

什么是类型擦除?

类型擦除是指在运行时执行之前从程序中删除显式类型注释的加载时过程。

类型擦除是指丢弃在设计时创建的类型标签或在编译时推断的类型标签,以使二进制代码中的已编译程序不包含任何类型标签。对于每种编译为二进制代码的编程语言来说都是这种情况,除非在某些情况下需要运行时标签。这些例外包括所有存在类型(可引用的Java参考类型,多种语言中的任何类型,联合类型)。删除类型的原因是程序被转换为某种类型的单类型语言(二进制语言仅允许位),因为类型仅是抽象,并断言其值的结构和适当的语义来处理它们。

因此,这是正常的自然回报。

Java的问题有所不同,并导致其如何统一。

关于Java的常见陈述没有泛型泛型也是错误的。

Java确实进行了验证,但是由于向后兼容而以错误的方式进行了验证。

什么是具体化?

从我们的维基

验证是将有关计算机程序的抽象概念转换为显式数据模型或以编程语言创建的其他对象的过程。

归一化是指通过专门化将抽象的东西(参数类型)转换为具体的东西(混凝土类型)。

我们通过一个简单的例子来说明这一点:

具有定义的ArrayList:

ArrayList
{
    T[] elems;
    ...//methods
}

是一个抽象,详细地讲是一个类型构造函数,当专门用于具体类型时,它会被“重新定义”,例如整数:

ArrayList
{
    Integer[] elems;
}

哪里ArrayList真的是类型。

但这正是 Java所没有的!!!,而是使用边界来不断地抽象化抽象类型,即产生相同的具体类型,而与为进行专门化而传入的参数无关:

ArrayList
{
    Object[] elems;
}

此处使用隐式绑定Object(ArrayList== ArrayList)进行了修正。

尽管如此,它使泛型数组不可用,并且对原始类型造成一些奇怪的错误:

List l= List.of("h","s");
List lRaw=l
l.add(new Object())
String s=l.get(2) //Cast Exception

它引起很多歧义,因为

void function(ArrayList list){}
void function(ArrayList list){}
void function(ArrayList list){}

指相同的功能:

void function(ArrayList list)

因此,常规方法重载不能在Java中使用。

推荐阅读
手机用户2402852387
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有