I came across a following source code that I do not understand:
final Book book = objectMapper.readValue(string, new TypeReference<>() {
});
How will Jackson understand what class to deserialize from the provided string?
I found a similar construct in this article, where it is written it will throw an exception. But the code actually works and Book is deserialized. What is this black magic?
CodePudding user response:
It's a clever way to deal with Java Generics type erasure. From "Type Erasure
To implement generics, the Java compiler applies type erasure to:
- Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.
- Insert type casts if necessary to preserve type safety.
- Generate bridge methods to preserve polymorphism in extended generic types.
Neil Gafter refers to
public abstract class TypeReference<T> {}
as super type tokens.
When you now do a
TypeReference<Book> x = new TypeReference<Book>() {};
or in Neals example
TypeReference<List<String>> x = new TypeReference<List<String>>() {};
there is no type erasure happening for the anonymous class created on the right-hand-side of the assignment. It has a fixed generic type, which can be accessed by the super class like this:
protected TypeReference() {
Type superclass = getClass().getGenericSuperclass();
if (superclass instanceof Class) {
throw new RuntimeException("Missing type parameter.");
}
this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
}
Jacksons ObjectMapper
will use the this.type
later to figure out the type of things you want to deserialise.
Observation of the diamond operator is ofc correct.