关于Gson的TypeToken

news/2024/11/24 2:21:32/

文章目录

    • 引言
    • Type是什么
    • 获取类型的困惑
    • 自定义TypeToken
    • 解决问题
    • 总结

引言

Gson在Json解析中使用广泛, 常用的数据类型都可以解析, 特殊的可以自定义Adapter解析. 在解析大量具有某些相同结构的数据上,我们总想复用已有的类型, 为了复用通常可以使用继承和泛型. 比如服务端返回的json都有类似结构:

{"code":200,"message":"success","data":"{...}"
}

其中data对应的结构不定, 一种考虑是使用泛型:

public class Response<T>{public T data;//简化数据, 省略了其他字段
}

于是在做json解析是很可能会这样使用:

String json = "{\"data\":\"data from server\"}";
Type type = new TypeToken<Response<String>>(){}.getType();
Response<String> result = new Gson().fromJson(json, type);

这个TypeToken是如何和类型Response产生关系,又是怎样存储泛型信息的? 首先需要明确Type是什么.

Type是什么

这里的Type指java.lang.reflect.Type, 是Java中所有类型的公共高级接口, 代表了Java中的所有类型. Type体系中类型的包括:数组类型(GenericArrayType)、参数化类型(ParameterizedType)、类型变量(TypeVariable)、通配符类型(WildcardType)、原始类型(Class)、基本类型(Class), 以上这些类型都实现Type接口.

参数化类型,就是我们平常所用到的泛型List、Map;

数组类型,并不是我们工作中所使用的数组String[] 、byte[],而是带有泛型的数组,即T[] ;

通配符类型, 指的是<?>, <? extends T>等等

原始类型, 不仅仅包含我们平常所指的类,还包括枚举、数组、注解等;

基本类型, 也就是我们所说的java的基本类型,即int,float,double等

本文的重点在于参数化类型(ParameterizedType).

public interface ParameterizedType extends Type {// 返回确切的泛型参数, 如Map<String, Integer>返回[String, Integer]Type[] getActualTypeArguments();//返回当前class或interface声明的类型, 如List<?>返回ListType getRawType();//返回所属类型. 如,当前类型为O<T>.I<S>, 则返回O<T>. 顶级类型将返回null Type getOwnerType();
}

获取类型的困惑

对于普通的类想要获取类型简单调用.class或者getClass()方法即可,

Class<String> stringClass = String.class;
Class<?> stringClass2 = "hello".getClass();

但对于泛型你不能这样做,

Response<String>.class //不能通过编译
Response.class //只能这样获取类型, 但无法知道元素的类型

那么在做json解析时我们如果确实是需要让Gson解析成Response, 可以像上文的方式处理.

自定义TypeToken

先看一段代码:

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;public abstract class MyTypeToken<T> {private final Type type;public MyTypeToken() {Type genericSuperclass = getClass().getGenericSuperclass();if(genericSuperclass instanceof Class){throw new RuntimeException("Missing type parameter.");}ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;Type[] typeArguments = parameterizedType.getActualTypeArguments();type = typeArguments[0];}public Type getType() {return type;}
}
  1. MyTypeToken声明为抽象类, 使用时需要对其进行实例化, 实例化过程可以分解如下:

    MyTypeToken<String> sToken = new MyTypeToken<String>(){};
    

    相当于

    class MyTypeToken$0 extends MyTypeToken<String>{}
    MyTypeToken<String> sToken = new MyTypeToken$0();

    这样分解的目的在于明确sToken的类型是MyTypeToken$0(匿名的),父类型是MyTypeToken而不是MyTypeToken.

  2. getClass().getSuperclass()获取的是当前对象所属的类型的父类型. 注意到抽象类实例化时需要给具体的泛型类, 如果没有提供则使用Object(但此时使用的已不是泛型类了, 而是原始类型, 也就是擦除泛型后的类型)代替泛型参数. 因此如果像上面那样实例化, 那么getClass().getGenericSuperclass()得到的将是类型参数实例化后的父类型MyTypeToken, 泛型信息保留下来了. 如果不用泛型得到的是MyTypeToken, 是原始类型.

  3. 得到泛型参数实例化后的类型, getActualTypeArguments()返回的是确切的类型参数数组, 此处MyTypeToken只有一个类型参数, 返回的是数组[String.class].

至此, 通过new MyTypeToken>() {}.getType()得到的正是表示Response的类型, 将该类型应用在Gson在解析Response上将获得和TypeToken一致的效果(当然就这么点代码功能肯定是比不上了).

解决问题

探究TypeToken的目的其实为了解决以下问题而总结的.

//封装getType()操作
public class TokenUtil{public static <E> Type getType(){return new MyTypeToken<E>() {}.getType();}
}
// 本意在于获取Response<String>的类型, 但是new MyTypeToken<E>() {}时已经实现了
// 抽象类, 相当于创建一个子类 class MyTypeToken$0<E> extends MyTypeToken<E>{},
// 实例化时虽然传入的是Response<String>, 但.getGenericSuperclass()=MyTypeToken$0<E>
// 于是getType()返回的只是泛型参数类型E, 正真解析是按照Gson流程选择Map或者List
Type type = TokenUtil.<Response<String>>getType();
// fromJson返回Map或者List, ClassCastException!
Response<String> o = new Gson().fromJson(json, type);

总结

Gson解析时TypeToken的泛型参数只能使用时传入确切的类型才能获取正确的Type, 这也是TypeToken设计成抽象类的巧妙之处和原因(改为只有protected构造方法的普通类原理一样). 一旦将TypeToken改成普通类, 根据上面的分析, 一切类型信息都被擦除, Gson解析将得不到预期的类型.


http://www.ppmy.cn/news/740632.html

相关文章

realme GT2大师探索版和realme GT2 Pro 区别 哪个值得入手

ealme真我GT2大师探索版提供了三个存储组合版本&#xff0c;分别为8GB内存与12GB内存。 8GB128GB存储组合版本售价3499元。 8GB256GB存储组合版本售价3799元。 12GB256GB存储组合版本售价3999元。 前面两个版本的内存完全一样&#xff0c;闪存相差128GB&#xff0c;价格差距…

多线程:观测线程状态

观测线程状态 新生就绪运行&#xff1a;阻塞 死亡Thread.state package com.dxch.Demo02; //观察测试线程的状态 public class TestState {public static void main(String[] args) {Thread thread new Thread(()->{for (int i 0; i <5; i) {try {Thread.sleep(1000)…

leetcode第352场周赛补题

6909. 最长奇偶子数组 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;模拟 class Solution { public:int longestAlternatingSubarray(vector<int>& nums, int threshold) {int res 0;int n nums.size();for(int i 0; i < n; i ){if(nums[i] % 2 …

多媒体支持

无论多媒体功能在您的应用程序中是处于中心地位&#xff0c;还是偶尔被使用&#xff0c;iPhone用户都期望有很高的品质。视频应该充分利用设备携带的高分辨率屏幕和高帧率&#xff0c;而引人注目的音频也会对应用程序的总体用户体验有不可估量的增强作用。 您可以利用iPhone O…

【流程】影视和游戏的IT基础设施详解

文章发于微信公众号&#xff08;DanggooTD&#xff09;&#xff0c;感兴趣的话请关注查看原图文~ 最近和朋友合作翻译一本国外的流程书籍&#xff0c;叫《Production Pipeline Fundamentals for Film and Games》&#xff0c;中文译为《电影和游戏生产流程基础》&#xff0c;这…

Elastix 2.5 PBX服务器安装配置使用手册

2019独角兽企业重金招聘Python工程师标准>>> 一、Elastix 简介 Elastix 系统集成了最优秀的工具,它使 Asterisk PBX拥有一个简单易操作的界面,还增加了自己的设备,允许外界创新,使其成为开源通讯最好的软件包。Elastix 的目标就是要发展成为一个稳定、可调节和易…

Android组件之Service 与常用系统服务用法详解

一、服务 1.1、后台运行、不可见、没有界面&#xff0c;优先级高于activity&#xff0c;主要用于组件之间交互和执行后台任务。同样在主线程中运行&#xff0c;不能做耗时操作&#xff0c;超过20S会出现ANR。 1.2、 本地服务Local Service&#xff1a;应用程序内部--startSer…

web安全攻防渗透测试实战指南

1. Nmap的基本 Nmap ip 6 ip Nmap -A 开启操作系统识别和版本识别功能 – T&#xff08;0-6档&#xff09; 设置扫描的速度 一般设置T4 过快容易被发现 -v 显示信息的级别&#xff0c;-vv显示更详细的信息 192.168.1.1/24 扫描C段 192.168.11 -254 上 nmap -A -T4 -v -i…