Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeReference<Map<String, String>>() { }

Since few days ago I started to work on a webservice project. This project is using Jackson to marshalling and unmarshalling JSON objects. So my question is:

Why always I have to put the {} when I am creating an instance of TypeReference? I know the constructor is protected, but why is protected? I think that it's like a hack to make visible the constructor creating an implementation of the constructor since TypeReference is abstract and you can do it. But what is the point of this?

String jsonString = "{\" firstName\":\"John\",\"lastName\":\"Chen\"}";
ObjectMapper objectMapper = new ObjectMapper();

// properties will store name and value pairs read from jsonString
Map<String, String> properties = objectMapper.readvalue(
    jsonString, new TypeReference<Map<String, String>>()
        { //
        });
like image 372
Raikish Avatar asked Dec 06 '22 11:12

Raikish


1 Answers

TL;DR

Via subclassing it is possible for TypeReference to extract the actual generic type parameter. E.g:

TypeReference<String> ref = new TypeReference<String>(){};
System.out.println(ref.getType());

Prints:

class java.lang.String

This can be useful when you can't use normal classes. E.g when this doesn't work:

// doesn't work
Type type = ArrayList<String>.class; 

You still can get that class by using a TypeReference:

// will yield Class<ArrayList<String>>>
Type type = new TypeReference<ArrayList<String>>(){}.getType();

Detailed

When looking at the source code of TypeReference (using Jackson 2.8.5) you can see that the constructor body contains the following lines:

Type superClass = getClass().getGenericSuperclass();
if (superClass instanceof Class<?>) { // sanity check, should never happen
    throw new IllegalArgumentException("Internal error: TypeReference constructed without actual type information");
}
_type = ((ParameterizedType) superClass).getActualTypeArguments()[0];

The interesting lines are the first and last. Let's take a closer look at the first line:

Type superClass = getClass().getGenericSuperclass();

For example when you're creating a subclass, by using an anonymous class:

TypeReference<SomeStype> ref = new TypeReference<SomeType>(){};

Then getClass returns the current Class object (an anonymous class), and getGenericSuperclass() will return the Class object from the class the current implementation extends from, in our case, superClass will equal Class<TypeReference<?>>.

Now when looking at the last line from the constructor body:

_type = ((ParameterizedType) superClass).getActualTypeArguments()[0];

As we know that the superClass is the Class object for TypeReference<?> we know that it has a generic parameter. Hence the cast to ParameterizedType. This specified Type has the method getActualyTypeArguments() which returns an array of all generic parameters specified by that class. In our case it's just 1. So [0] will yield the first element. In the example we will get the actually specified type parameter SomeType.

like image 184
Lino Avatar answered Dec 24 '22 21:12

Lino