Java Monitoring

About Stackify

Stackify was founded in 2012 with the goal to create an easy to use set of tools for developers. Now over 950 customers in 40 countries rely on Stackify’s tools to provide critical application performance and code insights so they can deploy better applications faster.

Java Monitoring

About Stackify

Stackify was founded in 2012 with the goal to create an easy to use set of tools for developers. Now over 950 customers in 40 countries rely on Stackify’s tools to provide critical application performance and code insights so they can deploy better applications faster.

There have been many changes in the syntax and API throughout Java history. One of the most important guidelines regarding those changes is backward compatibility.

Type erasure

Regarding generics, this means that parameterized types are not stored in the bytecode. This is called type erasure, because parameterized types are “erased”. Generics are enforced at compile-time by the compiler itself.

Problems of type erasure

Type erasure hinders development in at least two different ways.

Method names

Since generics are not written in the bytecode, they do not affect the signature of a method. Hence, methods that have the same name and the same arguments – stripped of generics, have the same signature.

For example, the following class cannot be compiled because the signature of its methods is the same, though they have different generic types.

This way, the compiler enforces that the collection and the class both have the same parameterized type. The class type is written in the bytecode, and thus it can be obtained using reflection.

A naive approach would be to get the first element of the list, check its type and infer that the type of all elements in the list are of this type. Unfortunately, if T has any child class, there’s no way to know for sure whether any element of the list is of type T or a subclass of T.

The extra Class arguments does set the lower bound for T.

Using reflection

The final way is quite tricky. When I mentioned type erasure and that parameterized types cannot be accessed through the Reflection API, I deliberately omitted three cases:

Note that relevant data can only be obtained if the parameterized type is “real”. For example, tf the method signature is changed to public <T> void withoutClass(List<T> list), the previous code now outputs:

T

Obviously, this cannot be considered helpful.

Kotlin’s approach

By design, Kotlin aims to generate Java-compatible bytecode: it also suffers from type erasure. Hence, it has the same issue as Java regarding method signature clash and reflection.

However, Kotlin offers a great way to do without the extra parameter. It means T can be used as it is. This is achieved by using the reified keyword, while declaring T. There’s a caveat though: reified generics can only be used when functions are inlined.

A note on inline function

inline functions are special in that they are not called. Instead, the compiler sort of copy-pastes the code of the inline function where it should have been called.

For example, here’s a snippet using inline:

fun foo() {
bar()
}
inline fun bar() {
doSomething()
}

The compiler will replace it with the following:

fun foo() {
doSomething()
}

Of course, this specific example is not very useful, but it’s meant to show how it works.

Conclusion

This post showed the limitations of using parameterized types on the JVM, most importantly type erasure, and some ways to overcome them. It also showed how Kotlin improves the situation in some specific scenarios compared to Java.

About Nicolas Frankel

Nicolas Fränkel is a Developer and Software Architect with 15 years of experience. He also double as a teacher in universities and higher education schools, a trainer and triples as a book author.
He shares his thoughts weekly in his blog, A Java Geek