为什么Java必须采用类型擦除来保持向后兼容性?

时间:2016-04-05 16:02:23

标签: java generics compilation jvm

让我在前面说明我理解Java可以做什么和不能做什么而不是在问这个问题。我想知道从JVM和编译器的角度来看,实际的技术挑战是什么,需要编译器按照它的方式运行。

每当我看到关于java Type Erasure的弱点或最讨厌的方面的讨论似乎总是在Java开发人员的列表顶部附近(它适合我!)。如果我的历史是正确的,Java 1.0从不实现任何类型检查,除了传递对象并重铸它们。当需要更好的类型系统时,Sun必须在完全打字支持之间做出决定,这可能会破坏可比性,或者与他们选择的泛型解决方案保持一致,而这些解决方案并没有破坏旧代码。

与此同时,C#遇到了同样的问题,并且采取了相反的方式,即在同一时间内实现更复杂的打字系统(我相信)可以打破后向可比性。

我的主要问题是为什么这是两种语言的问题或问题?什么是编译器进程,这意味着没有办法支持C#样式处理类型而不破坏旧代码中的可比性?我理解问题的一部分是确切的类型并不总是在编译时知道,但首先(天真)一瞥似乎有时它可以在编译时知道,或者它可以在编译时保持未知,并在运行时使用一种反射方法处理。

问题是实施起来不可行,还是认为实施运行时解决方案的速度太慢?

为了更进一步,我们可以使用一个简单的通用工厂代码示例作为类型擦除感觉相当繁琐的地方的示例。

public class GenericFactory<FinalType, BuilderType<FinalType> extends GenericBuilder<FinalType>>{

   private Class builderClass;

   public GenericFactory(Class<BuilderType> builderClass){
        this.builderClass=builderClass;
   }

   public FinalType create(){

       GenericBuilder builder=builderClass.newInstance();

       builder.setFoo(getSystemProperty("foo");
       builder.setBar(getSystemProperty("bar");
       builder.setBaz(getSystemProperty("baz");

       return builder.build();
   }
}

这个例子,假设我没有搞砸某处的语法,显示出类型擦除的两个特殊烦恼,乍一看似乎它们应该更容易处理。

首先,相关性较低,我必须在引用BuilderType扩展GenericBuilder之前添加一个FinalType参数,即使看起来似乎可以从BuilderType推断出FinalType。我说不太相关,因为这可能更多关于泛型语法/实现,然后编译器限制强制类型擦除。

第二个问题是我必须将我的BuilderClass对象传递给构造函数才能使用反射来构建构建器,尽管它已经由泛型定义。似乎编译器存储这里使用的泛型类(只要它不使用?语法)来允许反射查找泛型然后构造它就相对容易。

由于没有这样做,我认为有一个很好的理由不是。我试图了解这些原因是什么,是什么迫使JVM坚持使用类型擦除以保持向后兼容性?

1 个答案:

答案 0 :(得分:0)

我不确定你所描述的是什么(两个&#34;烦恼&#34;)是类型擦除的结果。

  

我必须在引用BuilderType扩展GenericBuilder之前添加一个FinalType参数,即使看起来似乎可以从BuilderType中推断出FinalType

BuilderType<FinalType>不会是有效的泛型类型名称,除非我错过了对Java 8中的更改。因此它应该是BuilderType extends GenericBuilder<FinalType>,这很好。这里无法推断FinalType,编译器应该如何知道要提供哪种类型?

  

第二个问题是我必须将我的BuilderClass对象传递给构造函数才能使用反射来构建构建器,尽管它已经由泛型定义。

那不是真的。通用参数不定义FinalType实际上是什么。我可以创建GenericFactory<String, StringBuilderType>StringBuilderType extends GenericBuilder<String>)和GenericFactory<Integer, IntegerBuilderType>IntegerBuilderType extends GenericBuilder<Integer>)。

这里,如果您为变量定义或方法调用提供类型参数,则会发生类型擦除。至于为什么要参考安迪的评论。

但是,如果您有字段或子类,例如private GenericFactory<String, StringBuilderType> stringFactory,没有类型擦除。可以从反射数据中提取泛型类型(遗憾的是,这里没有简单的内置方法,但请看一下:http://www.artima.com/weblogs/viewpost.jsp?thread=208860)。