#Week 44 — What does "type erasure" refer to?

6 messages · Page 1 of 1 (latest)

gilded wingBOT
#
Question of the Week #44

What does "type erasure" refer to?

fringe doveBOT
#

In Java, generics were implemented as a compile-time construct.
It is possible to use a generic type like the following:

List<String> someList = new ArrayList<String>();

The compiler is then able to check the type of the list and make sure only valid (with respect to the type) elements are inserted in the list as well as it allows obtaining items without (explicit) casting.

someList.add("Hi");
//someList.add(123);//compile-time error
//someList.add(LocalDateTime.now());//compile-time error

String stringFromList = someList.get(0);//no casting necessary, returns Hi
//LocalDateTime invalid = someList.get(0);//prevented by the compiler because get() on a List<String> only returns String elements

However, this information is not present at runtime. At runtime, an ArrayList<String> object is the same as an ArrayList<Object> or an ArrayList<LocalDateTime> or similar.
After compilation, the above code would be equivalent with the following:

//Don't actually write code using "raw types" like this, this is unsafe and emits warnings for a reason
List someList = new ArrayList();//the runtime doesn't know about the type
someList.add("Hi");//any type would be allowed
//someList.add(123);//adding an instance of a different type would be possible as well since the type is not checked
String stringFromList = (String)someList.get(0);//since get(0) could return any Object, a cast is added here
//LocalDateTime invalid = (LocalDateTime)someList.get(0);//it would also be possible to extract wrong types that way
#

Since no generic type information about objects is present at runtime, it is possible to insert call generic methods with invalid objects.

//DON'T DO THIS, it's unsafe
List<String> someStringList = new ArrayList<>();
List someUntypedList = someStringList;//This line of code is unsafe as it creates a reference of the original list which is not checked by the compiler
someUntypedList.add(LocalDateTime.now());//this inserts an object of the wrong type into the list
String s = someStringList.get(0);//this throws an exception at runtime because the List<String> actually contains a LocalDateTime here

Raw types exist solely for compatibility and should not be used anywhere.

Instead, it is possible to use so-called wildcard types for generic types where the actual type parameter is unknown:

List<String> someStringList = new ArrayList<>();
List<?> someUntypedList = someStringList;//Here, you are telling the compiler that the type parameter is unknown
//someUntypedList.add(LocalDateTime.now());//the compiler doesn't allow inserting any object into the list since the correct type is not known
#

Aside from that, it is also not possible to extract the actual generic type parameters from a generic object since that information is not present at runtime.
For example, it is not generally possible to check whether an is a List<String>, List<Object> or a different List. It is only possible to check whether or not it is a List.

Object someObject = new ArrayList<String>();

boolean isList = someObject instanceof List<?>;//it is possible to check whether it is a list
//boolean isStringList = someObject instanceof List<String>;//compile-time error
#

Unlike to objects, it is possible to get the generic type parameters of fields and methods using reflection:

public class SomeClass{
  public List<String> someList = new ArrayList<>();
  public List<Integer> someMethod(List<LocalDate> someParameter){
    return Collections.emptyList();
  }
}
System.out.println(SomeClass.class.getField("someList").getGenericType());
System.out.println(SomeClass.class.getMethod("someMethod", List.class).getGenericReturnType());
System.out.println(Arrays.toString(SomeClass.class.getMethod("someMethod", List.class).getGenericParameterTypes()));
📖 Sample answer from dan1st
fringe doveBOT
#

Generics in java are actually totally fake, the runtime does not care about them. Type erasure refers to the java compiler resolving generic types to a common supertype, and making sure the runtime still works with those new types.
An example:
A generic type of <T> implicitly extends Object. Thus, using <T> like this: ```java
class Holder<T> {
T value;
T get() { return value; }
}
Holder<String> s = new Holder<>();
s.value = "hello";
assert s.get().equals("hello");

compiles to: ```java
class Holder {
  Object value;
  Object get() { return value; }
}
Holder s = new Holder();
s.value = "hello";
assert ((String) s.get()).equals("Hello");

(the cast in this case is unnecessary, but this would be about the use case)

Every generic type can be erased to a common supertype, with all the usages of the generic type enjoying type safety at compile time. At runtime, the uses are still entirely safe, because the java compiler has proven that the generic types being used are fully compatible. Using typed classes in a raw manner will defeat the java compiler's ability to do this tho, and should be used sparingly, if at all. That is a story for another time tho.

⭐ Submission from 0x150