Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How type erasure works

I am investigating how libraries that creates proxy objects work especially I want to understand how they fetch types from declared methods. For example popular library for Android - Retrofit:

interface MyService {

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

I was confused - how it is possible to get from this interface exactly MyData class not raw Object? Cause from my understanding type erasure will remove any information that placed inside generic braces.

I wrote some test code and surprisingly to me it is really easy to get type from such code:

@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();
}

It looks a bit dirty but it works and prints Integer. It means that we have types in runtime. Also I've read about another dirty trick with anonymous classes:

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

prints raw AbstractList<E> while this code

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

prints ArrayList<Integer>.

And it is not last thing that confused me. In Kotlin there are reified generics from which looks like some hack in compile time but we can easily get class from generic:

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

And now I am totally confused with type erasure mechanism.

  1. Can someone explain, why sometimes it holds information and sometimes doesn't?
  2. Why generics isn't implemented in normal way in Java? Yes, I read that it could break compabillity with previous versions, but I want to understand how. Why does generic return type not break anything but new ArrayList<Integer>does?
  3. Why do anonymous classes hold generic type and are not type erasured?

Updated: 4. How do reified generics works in Kotlin and why such cool thing can't be implemented in Java?

Here explained pretty clear about how reified generics work. @Mibac

You can only use reified in combination with an inline function. Such a function makes the compiler copy the function's bytecode to every place where the function is being used (the function is being "inlined"). When you call an inline function with reified type, the compiler knows the actual type used as a type argument and modifies the generated bytecode to use the corresponding class directly. Therefore calls like myVar is T become myVar is String, if the type argument were String, in the bytecode and at runtime.

like image 210
hluhovskyi Avatar asked Oct 18 '22 05:10

hluhovskyi


1 Answers

Can someone explain, why sometimes it holds information and sometimes doesn't?

On jvm level there is a Signature attribute that:

records a signature (§4.7.9.1) for a class, interface, constructor, method, or field whose declaration in the Java programming language uses type variables or parameterized types.

One of the reasons why you might want to have it is that, for example, compiler needs to know actual type of argument of a some_method that comes from precompiled some.class (that comes from third party some.jar). In this scenario assuming that method takes Object may violate assumptions with which some.class was compiled and you can't call some_method in a typesafe manner.

Why do anonymous classes hold generic type and are not type erasured?

When you call:

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

... nothing gets defined in terms of jvm classes, while this:

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

... actually defines class on the jvm level, albiet anonymous on the java laguage level.

This is the reason why reflection gives different results for these cases.

Why generics isn't implemented in normal way in Java? Yes, I read that it could break compabillity with previous versions, but I want to understand how. Why does generic return type not break anything but new ArrayListdoes?

How do reified generics works in Kotlin and why such cool thing can't be implemented in Java?

I don't think you can give objective answer that is not opinion-based to these. Moreover, I'd suggest either splitting or narrowing down scope of your question.

like image 194
Eugene Loy Avatar answered Oct 27 '22 11:10

Eugene Loy