类型擦除如何工作

时间:2017-09-27 16:37:39

标签: java generics jvm kotlin

我正在调查创建代理对象的库是如何工作的,特别是我想了解它们如何从声明的方法中获取类型。例如Android的流行库 - Retrofit:

interface MyService {

    @GET("path")
    Call<MyData> getData();
}

我很困惑 - 如何才能从这个界面获取正确的MyData类而不是原始对象?我的理解类型擦除的原因将删除放置在通用括号内的任何信息。

我写了一些测试代码,令我惊讶的是从这样的代码中获取类型非常容易:

@org.junit.Test
public void run() {
    Method[] methods = Test.class.getDeclaredMethods();
    Method testMethod = methods[0];
    System.out.println(testMethod.getReturnType());
    ParameterizedType genericType = ((ParameterizedType) testMethod.getGenericReturnType());
    Class<Integer> clazz = (Class<Integer>) genericType.getActualTypeArguments()[0];
    System.out.println(clazz);
}

interface Test {
    List<Integer> test();
}

它看起来有点脏,但它可以工作并打印Integer。这意味着我们在运行时有类型。我也读过有关匿名课程的另一个肮脏技巧:

System.out.println(new ArrayList<Integer>().getClass().getGenericSuperclass());

在此代码

时打印原始AbstractList<E>
System.out.println(new ArrayList<Integer>() { }.getClass().getGenericSuperclass());

打印ArrayList<Integer>

这不是困扰我的最后一件事。在Kotlin中有一些具体化的泛型,在编译时看起来像是一些黑客,但是我们很容易从泛型中获得类:

inline fun <reified T> test() {
    print(T::class)
}

现在我对类型擦除机制感到困惑。

  1. 有人可以解释一下,为什么有时候它会保存信息,有时候却没有?
  2. 为什么泛型在Java中没有以正常方式实现?是的,我读到它可能会破坏先前版本的兼容性,但我想了解如何。除了new ArrayList<Integer>之外,为什么泛型返回类型不会破坏任何东西?
  3. 为什么匿名类包含泛型类型而不是类型擦除?
  4. 更新 4.如何在Kotlin中使用具体化的泛型,以及为什么这些很酷的东西不能用Java实现?

    Here非常明确地阐明了仿制药的工作原理。的 @Mibac

      

    您只能将reified与内联函数结合使用。这样   函数使编译器将函数的字节码复制到每个字节码   正在使用该功能的地方(该功能正在使用   &#34;内联&#34)。当您使用reified类型调用内联函数时,   编译器知道用作类型参数的实际类型并进行修改   生成的字节码直接使用相应的类。   因此像myVar这样的调用是T成为myVar的String,如果是类型的话   参数是String,在字节码和运行时。

1 个答案:

答案 0 :(得分:3)

  

有人可以解释一下,为什么有时它会保存信息而有时却没有?

在jvm级别上有一个Signature attribute

  

为类,接口,构造函数,方法或字段记录签名(第4.7.9.1节),其声明在Java编程语言中使用类型变量或参数化类型。

您可能希望拥有它的原因之一是,例如,编译器需要知道来自预编译some_method(来自第三方)的some.class的实际参数类型some.jar)。在此方案中,假设方法花费Object可能违反编译some.class的假设,并且您无法以类型安全的方式调用some_method

  

为什么匿名类包含泛型类型而不是类型擦除?

致电时:

System.out.println(new ArrayList<Integer>().getClass().getGenericSuperclass());

......根据 jvm classes ,没有任何定义,而这是:

System.out.println(new ArrayList<Integer>() { }.getClass().getGenericSuperclass());

...实际上在 jvm级别上定义了类,在 java laguage级别上匿名为albiet。

这就是为什么反射会给这些案例带来不同结果的原因。

  

为什么在Java中没有以正常方式实现泛型?是的,我读到它可能会破坏先前版本的兼容性,但我想了解如何。除了新的ArrayListdoes之外,为什么泛型返回类型不会破坏任何东西?

  

如何在Kotlin中使用具体化的泛型以及为什么这些很酷的东西不能用Java实现?

我认为你不能给出那些不以意见为基础的客观答案。此外,我建议拆分或缩小问题的范围。