天天看點

為什麼使用TypeReference

在使用fastJson的時候對于泛型的反序列化很多場景下都會使用到TypeReference,例如:

public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("1");
        list.add("2");

        JSONObject o = new JSONObject();
        o.put("k",list);

        List<String> types = o.getObject("k",List.class);
        System.out.println(JSON.toJSONString(types));

        List<String> types2 = o.getObject("k",new TypeReference<List<String>>(){});
        System.out.println(JSON.toJSONString(types2));
    }           

使用TypeReference可以明确的指定反序列化的類型,具體實作邏輯參考TypeReference的構造函數

protected TypeReference(){
        Type superClass = getClass().getGenericSuperclass();

        Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0];

        Type cachedType = classTypeCache.get(type);
        if (cachedType == null) {
            classTypeCache.putIfAbsent(type, type);
            cachedType = classTypeCache.get(type);
        }

        this.type = cachedType;
    }           

核心的方法是

getActualTypeArguments

,此方法可以擷取父類真實的泛型類型。具體參考注釋

java.lang.Class#getGenericSuperclass

new TypeReference<List<String>>(){}

建立了一個繼承TypeReference>的匿名子類,在其構造函數中拿到了泛型對應Type(

java.lang.reflect.ParameterizedType

)。

ParameterizedType是一個記錄類型泛型的接口, 繼承自Type, 一共三方法:

  • Type[] getActualTypeArguments(); //傳回泛型類型數組
  • Type getRawType(); //傳回原始類型Type
  • Type getOwnerType(); //傳回 Type 對象,表示此類型是其成員之一的類型。

例如

Map<String,String>

對應的ParameterizedType三個方法分别取值如下:

  • [class java.lang.String, class java.lang.String]
  • interface java.util.Map
  • null

TypeReference

的存在是因為java中子類可以擷取到父類泛型的真實類型,為了便于了解,看一段測試代碼

public class TypeReferenceKest {

    public static void main(String[] args) {
        IntMap intMap = new IntMap();

        System.out.println(intMap.getClass().getSuperclass());

        Type type = intMap.getClass().getGenericSuperclass();
        if(type instanceof ParameterizedType){
            ParameterizedType p = (ParameterizedType) type;
            for (Type t : p.getActualTypeArguments()){
                System.out.println(t);
            }
        }

        System.out.println("=====newclass=====");
        HashMap<String,Integer> newIntMap = new HashMap<>();

        System.out.println(newIntMap.getClass().getSuperclass());

        Type newClassType = newIntMap.getClass().getGenericSuperclass();
        if(newClassType instanceof ParameterizedType){
            ParameterizedType p = (ParameterizedType) newClassType;
            for (Type t : p.getActualTypeArguments()){
                System.out.println(t);
            }
        }

        System.out.println("=====subclass=====");
        HashMap<String,Integer> subIntMap = new HashMap<String,Integer>(){};

        System.out.println(subIntMap.getClass().getSuperclass());

        Type subClassType = subIntMap.getClass().getGenericSuperclass();
        if(subClassType instanceof ParameterizedType){
            ParameterizedType p = (ParameterizedType) subClassType;
            for (Type t : p.getActualTypeArguments()){
                System.out.println(t);
            }
        }
    }


    public static class IntMap extends HashMap<String,Integer> {
    }
}           

輸出為

class java.util.HashMap
class java.lang.String
class java.lang.Integer
=====newclass=====
class java.util.AbstractMap
K
V
=====subclass=====
class java.util.HashMap
class java.lang.String
class java.lang.Integer
           

擷取到了真實的類型,就可以實作對泛型的反序列化了。

參考資料

http://www.java2s.com/Tutorials/Java/java.lang/Class/Java_Class_getGenericSuperclass_.htm https://zhaoyanblog.com/archives/186.html

ps.

java雖然運作時會有類型擦除,但是會保留Field的泛型資訊,可以通過Field.getGenericType() 取字段的泛型。

exp

public class FieldGenericKest {

    public  Map<String,Integer> map = new HashMap<>();
    public List<Long> list = new ArrayList<>();

    public static void main(String[] args) throws Exception {
        FieldGenericKest kest = new FieldGenericKest();

        Field map = kest.getClass().getField("map");
        Field list = kest.getClass().getField("list");

        System.out.println("=====map=====");
        System.out.println("map.getType=" + map.getType());
        System.out.println("map.getGenericType=" + map.getGenericType());

        System.out.println("=====list=====");
        System.out.println("list.getType=" + list.getType());
        System.out.println("list.getGenericType=" + list.getGenericType());
    }
}           

輸出

=====map=====
map.getType=interface java.util.Map
map.getGenericType=java.util.Map<java.lang.String, java.lang.Integer>
=====list=====
list.getType=interface java.util.List
list.getGenericType=java.util.List<java.lang.Long>           

但是注意,這裡不能擷取到字段的真實類型

HashMap

ArrayList

真實的類型當然不能用Field來擷取,需要用對應的Value來擷取

Object mapVal = map.get(kest);
if(mapVal != null){
    Class<?> clz = mapVal.getClass();
    System.out.println(mapVal.getClass().getName());
}           

pps.

因為泛型的運作時擦除,對于局部變量來說, 泛型資訊是無法擷取的