跳至主要内容

Get Java Generic Type?

Get Java Generic Type?

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.

评论

此博客中的热门博文

Spring Boot: Customize Environment

Spring Boot: Customize Environment Environment variable is a very commonly used feature in daily programming: used in init script used in startup configuration used by logging etc In Spring Boot, all environment variables are a part of properties in Spring context and managed by Environment abstraction. Because Spring Boot can handle the parse of configuration files, when we want to implement a project which uses yml file as a separate config file, we choose the Spring Boot. The following is the problems we met when we implementing the parse of yml file and it is recorded for future reader. Bind to Class Property values can be injected directly into your beans using the @Value annotation, accessed via Spring’s Environment abstraction or bound to structured objects via @ConfigurationProperties. As the document says, there exists three ways to access properties in *.properties or *.yml : @Value : access single value Environment : can access multi...

Elasticsearch: Join and SubQuery

Elasticsearch: Join and SubQuery Tony was bothered by the recent change of search engine requirement: they want the functionality of SQL-like join in Elasticsearch! “They are crazy! How can they think like that. Didn’t they understand that Elasticsearch is kind-of NoSQL 1 in which every index should be independent and self-contained? In this way, every index can work independently and scale as they like without considering other indexes, so the performance can boost. Following this design principle, Elasticsearch has little related supports.” Tony thought, after listening their requirements. Leader notice tony’s unwillingness and said, “Maybe it is hard to do, but the requirement is reasonable. We need to search person by his friends, didn’t we? What’s more, the harder to implement, the more you can learn from it, right?” Tony thought leader’s word does make sense so he set out to do the related implementations Application-Side Join “The first implementation ...

Learn Spring Expression Language

When reading the source code of some Spring based projects, we can see some code like following: @Value( "${env}" ) private int value ; and like following: @Autowired public void configure (MovieFinder movieFinder, @ Value ("#{ systemProperties[ 'user.region' ] } ") String defaultLocale) { this.movieFinder = movieFinder; this.defaultLocale = defaultLocale; } In this way, we can inject values from different sources very conveniently, and this is the features of Spring EL. What is Spring EL? How to use this handy feature to assist our developments? Today, we are going to learn some basics of Spring EL. Features The full name of Spring EL is Spring Expression Language, which exists in form of Java string and evaluated by Spring. It supports many syntax, from simple property access to complex safe navigation – method invocation when object is not null. And the following is the feature list from Spring EL document : ...