Tony is now implementing some persistence/serialization library using Java. Because the implementation of generic of Java is the erasure, the generic has many limitations.
The first interface Tony needed to implement is creating a new instance by using type parameter.
public class SomeLib<T> {
public T fromString(String string) {
}
}
Extra Class
Because the type parameter is erased at runtime, Tony can’t use new T()
to create a new instance or use T.class
to get real class etc. The common way to do the similar things is to put an extra Class
parameter in interface:
public class SomeLib {
public <T> T fromString(String string, Class<T> clazz) {
T t = (T) clazz.newInstance();
}
}
Superclass Workaround
This way works, but also very inconvenient. If Tony want to deserialize a type with nested generic types, the interfaces may become:
public <T, S> T fromString(String string, Class<T> clazz, Class<S> type) {
T t = (T) clazz.newInstance();
t.set(type.newInstance());
}
“In some rare cases, if we need to deserialize multiple nested generic types, the method will become much bloated with different numbers of type parameter and class types, which is not acceptable.” Considering this solution is somewhat cumbersome, Tony decided to find whether there exists some workarounds. After some searching, he found a solution from hibernate source code:
public interface GenericDAO<T, ID extends Serializable> {
T findById(ID id, boolean lock);
// ...
}
public abstract class GenericHibernateDAO<T, ID extends Serializable>
implements GenericDAO<T, ID> {
private Class<T> persistentClass;
public GenericHibernateDAO() {
this.persistentClass = (Class<T>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
}
public Class<T> getPersistentClass() {
return persistentClass;
}
@SuppressWarnings("unchecked")
public T findById(ID id, boolean lock) {
// use persistentClass
}
From the Java doc of getGenericSuperclass
, Tony found that this technique works only when the type parameter is defined on the immediate superclass or interface (when a CLASS
implements / extends something that has generic declarations). It has some limitations:
- It fails if the type parameter is defined elsewhere in the type hierarchy;
- It does not return the actual type parameters used when an
INSTANCE
is instantiated
Returns the Type representing the direct superclass of the entity
If the superclass is a parameterized type, the Type object returned must accurately reflect the actual type parameters used in the source code.
In other words, it CAN tell that in class A implements Comparable<String>
, the actual type parameter is String, but it CANNOT tell that in Set<String> a = new TreeSet<String>()
, the actual type parameter is String
.
That means our serialize class have to implement/extends some generic class for this workaround to work:
public class SomeLib<T> {
public <T> T fromString(String string) {
// NOT work!!! no generic super class
this.clazz = (Class<T>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
T t = (T) clazz.newInstance();
}
}
Gson
Way
Failing to come up with other better way, Tony decided to see whether other serialization library have better solution. He found some samples of how Gson
handle Java generic:
class Foo<T> {
T value;
}
Gson gson = new Gson();
Foo<Bar> foo = new Foo<Bar>();
gson.toJson(foo); // May not serialize foo.value correctly
gson.fromJson(json, foo.getClass()); // Fails to deserialize foo.value as Bar
The above code fails to interpret value as type Bar
because we invokes foo.getClass()
to get its class information, but this method returns a raw class, Foo.class
. This means that Gson
has no way of knowing that this is an object of type Foo<Bar>
, and not just plain Foo
, which is impossible for it to set correct data.
Gson
provides another way to solve this problem by specifying the correct parameterized type for generic type, using TypeToken class:
Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
gson.toJson(foo, fooType);
gson.fromJson(json, fooType);
“The core code snippet is that it extends TypeToken
with generic type parameters – new TypeToken<Foo<Bar>>() {}
. So, from above trial, we already know that we can get exact type parameter through genericSuperclass
, which is TypeToken
in this case. In this solution, we only need to add one more Type
even we need deserialize many levels of nested generic types.”
Type fooType = new TypeToken<Foo<Bar<Foo<Bar>>>>() {}.getType();
gson.toJson(foo, fooType);
“And in order to force the extension, Gson
make the constructor of TypeToken
as protected
so we can’t access the constructor directly except for extend it. Brilliant! But what about using abstract
class? Em, it seems also a choice to enforce the extension.”
Ref
Written with StackEdit.
评论
发表评论