Type Erasure

The term “type erasure” simply means that generic type parameters are replaced by actual types at compile-time, right?

This term bothers me. Types are not being erased. It took me a while to understand that because of the term.

Why not call it “type parameter erasure”, “type parameter replacement” or something similar that actually says what it really is?

Perhaps it should be called as you want. Unfortunately the term is “type erasure” and we’re kind of stuck with it.

Not quite.

Without type erasure, List<String> would remain List<String> at runtime, so you could write code like this:

if( a is List<String> ) {
   // do something for lists of strings, but not for other lists
}

However, with type erasure, the type parameters are literally erased. List<String> and List<Int> are the same type at runtime.

That means if you have an Any parameter, you can check if it’s a list, but not what kind:

fun myFunction( x: Any ) {
    if( x is List ) {
        // it's a list... but what kind?
    }
}

If you want to know what kind of list it is, you have to either store that information separately, or you have to check every element of the list (and what do you do if it’s empty?)

fun myFunction( x: Any ) {
    if( x is List ) {
        if( x.isEmpty() ) throw IllegalStateException() // We don't know what kind of list
        if( x.all { it is String} ) {
              // We have a list of strings
        }
    }
}

It’s called type erasure because the generic type parameter really is erased by the compiler, so the information is not available at runtime.

It means you can do stupid things like:

val myStrings : MutableList<String> = mutableListOf<String>( "This is a string!" )
val v : Any = myStrings
(v as! MutableList<Int>).add(123)
// At this point myStrings is a MutableList<String> but it contains an Int!

If we didn’t have type erasure, it would be possible for the add() method of list to check the type of the parameter and raise an exception if it didn’t match the list’s generic type. We get that safety from the compiler, but not at runtime. This is why you get warnings about unchecked casts: It happen when the compiler can’t be sure the cast will be safe.

6 Likes

Because it’s not just type parameters. @naxymatt is right that List<String> becomes List, but you also have generic types that aren’t parameters, that are being erased.

The following method contains two variables (parameters) that are of type T and that the compiler erases to Comparable.

fun <T : Comparable<T>> compareEquals(a : T, b: T) : Boolean {
    return a.compareTo(b) == 0;
}

becomes

fun compareEquals(a : Comparable, b: Comparable) : Boolean {
    return a.compareTo(b) == 0;
}

after compilation.

You could also have normal var and vals of type T in a method that would be type erased the same way.

1 Like

I’ll need some time to read that and try to understand it. :slight_smile:

By “type parameter”, I’m referring to the “T” in <T>, so I think that includes your examples.

That’s not a type parameter, as it’s not a parameter to a type. It’s just a generic type. In List<T> the T is a parameter to the type List. My method just has generic types, not type parameters (ignoring the T in Comparable<T>).

You can call type erasure “generic type erasure”, if you like that better.

You’re wrong. In this case T is a parameter to the function not to a type, but it’s still a parameter. So talking about type parameters is valid both when using referencing the generic type of a class or a function.

from the Java documentation about generic methods:

Generic methods are methods that introduce their own type parameters. This is similar to declaring a generic type, but the type parameter’s scope is limited to the method where it is declared. Static and non-static generic methods are allowed, as well as generic class constructors.

Your thinking is correct, it’s a misnomer. Kotlin inherited it from Java, and Java itself didn’t invent it either. What Java did was abuse an existing term that actually means just what it sounds like: the complete disappearance of type information from the runtime. You can see it in action in Java, but not on the example of generics: it is what happens to the primitive types. Take an int value as an example: it’s just four bytes of memory. If all a function has is those four bytes, there’s no way for it to find out at runtime whether they are to be interpreted as int, float, or anything else. So, all the programer can do is rely on the static type system to interpret the bytes.

From the perspective of static type theory, type erasure is a positive thing: it gives you the guarantee that the runtime will stay strictly within the type system so the compiler can reason about the program without any doubts. In other words, it is able to prove theorems about it.

But in Java (and therefore Kotlin), type erasure just makes the compiler’s situation worse: neither the runtime has the convenience of finding out the type parameters, nor can the compiler prove anything definite about how you’re going to use the object with the parametric type. So you might call it “type degradation”.

1 Like

@mtopolnik is right, the term is two fold.

Maybe you should call it GenericUpperBoundProjection as the type parameter doesn’t really disappear, instead it is mapped to its upper bound. For List, this will be object as no upper bound was given.

Afaicr, an older java lib not supporting generics can be used on a newer platform bundled with a generic runtime library (rt.jar). In order to resolve the old requests on non generic elements (e.g. List) in rt.ja from the old java lib, the generic elements (e.g. List<T>) in rt.jar have to be mapped to non generic elements (List<Object> or List) in order to guarantee conformance.