实例:gson解析泛型对象(代码片段)

Chin_style Chin_style     2022-12-03     430

关键词:

一,前期基础知识储备

1)Java泛型

Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

2)泛型使用

泛型使用方式,分别为:泛型类、泛型接口、泛型方法。

泛型类,泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。

泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。

格式为:public class 类名<泛型类型1,…>

public class Box<T> 
   
  private T t;
 
  public void add(T t) 
    this.t = t;
  
 
  public T get() 
    return t;
  
 
  public static void main(String[] args) 
    Box<Integer> integerBox = new Box<Integer>();
    Box<String> stringBox = new Box<String>();
 
    integerBox.add(new Integer(10));
    stringBox.add(new String("Java泛型"));
 
    System.out.printf("整型值为 :%d\\n\\n", integerBox.get());
    System.out.printf("字符串为 :%s\\n", stringBox.get());
  

泛型方法,所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的<E>)。

每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。

格式为:public <泛型类型> 返回类型 方法名(泛型类型 .)

public class ObjectPrint   
    public <T> void show(T t) 
        System.out.println(t); 
    

public class ObjectToolDemo  
    public static void main(String[] args)  
        ObjectPrint op = new ObjectPrint();
        op.show("java");
        op.show(1100);
        op.show(true);
    

泛型通配符,类型通配符一般是使用?代替具体的类型参数。例如 List<?> 在逻辑上是List<String>,List<Integer> 等所有List<具体类型实参>的父类。

public class GenericTest 
     
    public static void main(String[] args) 
        List<String> names = new ArrayList<String>();
        List<Integer> ages = new ArrayList<Integer>();
        List<Number> numbers = new ArrayList<Number>();
        
        names.add("icon");
        ages.add(18);
        numbers.add(314);
 
        getData(names);
        getData(ages);
        getData(numbers);
       
   
 
   public static void getData(List<?> data) 
      System.out.println("data :" + data.get(0));
   

因为getData()方法的参数是List类型的,所以names,ages,numbers都可以作为这个方法的实参,这就是通配符的作用。

泛型上下边界,可能有时候,你会想限制那些被允许传递到一个类型参数的类型种类范围。例如,一个操作数字的方法可能只希望接受Number或者Number子类的实例。这就是有界类型参数的目的。

要声明一个有界的类型参数,首先列出类型参数的名称,后跟extends关键字,最后紧跟它的上界限

public class GenericTest 
     
    public static void main(String[] args) 
        List<String> name = new ArrayList<String>();
        List<Integer> age = new ArrayList<Integer>();
        List<Number> number = new ArrayList<Number>();
        
        name.add("icon");
        age.add(18);
        number.add(314);
 
        //getUperNumber(name);//1
        getUperNumber(age);//2
        getUperNumber(number);//3
       
   
 
   public static void getData(List<?> data) 
      System.out.println("data :" + data.get(0));
   
   
   public static void getUperNumber(List<? extends Number> data) 
          System.out.println("data :" + data.get(0));
       

上图中的extends不是类继承里的那个extends,两个根本没有任何关联。图中的extends后的BoundingType可以是类,也可以是接口,意思是说,T是在BoundingType基础上创建的,具有BoundingType的功能,可能是JAVA开发人员不想再引入一个关键字,所以用已有的extends来代替而已。

类型通配符下界限通过形如 List<? super Number>来定义,表示类型只能接受Number及其三层父类类型,如 Object 类型的实例。

? extends Number
? super Number
这两者有什么区别呢?
1."? extends T" 表示类型的上界,表示参数化类型可能是T或者是T的子类,只能取,不能写
2."? super T" 表示类型的下界,Java core中叫超类型限定,表示参数化类型是此类型的超类型(父类型),甚至是Object。只能写,不能取

3)泛型的优势

①运行时不确定类型;
②类型安全;
③消除强制转换;
④提高虚拟机性能。

在没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。一个错误的示范如下:

public static void main(String[] args) 
        List list = new ArrayList();
        list.add(1);
        list.add("String");
        int isInt = (int) list.get(1); //ClassCastException
    

本例中对于强制类型转换错误的情况,编译器在编译时并不提示错误,在运行的时候才出现ClassCastException异常,这样便存在着安全隐患。

利用泛型类可以选择具体的类型对类进行复用相对比较容易理解,具体的说明如下:

public class Box<T> 
    private T t;
    public void set(T t)  this.t = t; 
    public T get()  return t; 

这样我们的Box类便可以得到复用,我们可以将T替换成任何我们想要的类型:

Box<Integer> integerBox = new Box<Integer>();
Box<Double> doubleBox = new Box<Double>();
Box<String> stringBox = new Box<String>();

二,上代码,具体实现

在android开发中经常需要从接口服务器获取数据,然后展示在手机界面上。其中手机端和接口服务器之间通常使用json数据来进行通信。

常用的解析场景如下:

public class TypetokenActivity extends AppCompatActivity 

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        String json = "\\"Success\\":true,\\"ErrorMsg\\":\\"\\",\\"ErrorNo\\":\\"\\"";
        Gson gson = new Gson();
        FamilyMember member = gson.fromJson(json, FamilyMember.class);
        Log.e("TypetokenActivity", "bean name: " + member .name);
        Log.e("TypetokenActivity", "bean jsonStr: " + gson.toJson(member));
    

    class FamilyMember 
        public boolean Success;
        public String ErrorMsg;
        public String ErrorNo;
    

但是上述代码在遇到泛型时就会遇到问题。示例如下:

public class TypetokenActivity extends AppCompatActivity 

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        String json = "\\"Success\\":true,\\"ErrorMsg\\":\\"\\",\\"ErrorNo\\":\\"\\",\\"Result\\":" +
                "\\"FmMobileNumber\\":\\"15555215554\\",\\"FmId\\":3,\\"FlId\\":5,\\"FmUser\\":\\"15555215554\\"";
        Gson gson = new Gson();
        ResponseEntity<FamilyMember> entity = gson.fromJson(json, new ResponseEntity<FamilyMember>().getClass());
        Log.d(TAG, "onCreate entity: " + entity); /*com.example.javatast.typetoken.ResponseEntity@22dae67*/
        FamilyMember result = entity.getResult();  /*com.google.gson.internal.LinkedTreeMap cannot be cast to com.example.javatast.typetoken.FamilyMember*/

    

    class FamilyMember 
        public long FmId;
        ...
    
    class ResponseEntity<T> 
        public T Result;
        public boolean Success;
        public String ErrorMsg;
        public String ErrorNo;
        ......

通过Log信息,entity得到的是一个ResponseEntity对象,但是在接着调用getResult()方法时,出现了错误:

“com.google.gson.internal.LinkedTreeMap cannot be cast to com.example.javatast.typetoken.FamilyMember”。

导致上述问题的原因是Java在运行时,泛型参数的类型会在运行时被擦除,导致在运行期间所有的泛型类型都是Object类型。

解释一下类型擦除

不同的语言在实现泛型时采用的方式不同,C++的模板会在编译时根据参数类型的不同生成不同的代码,而Java的泛型是一种伪泛型,编译为字节码时参数类型会在代码中被擦除,单独记录在Class文件的attributes域,而在使用泛型处做类型检查与类型转换。
TIPS: 区别Java语言的编译时运行时是非常重要的,泛型只在编译时强化他们的类型信息,并在运行时丢弃他们的元素类型信息。泛型的运行时擦除可以通过Java提供的反射机制进行证明,比如通过反射调用List<String>容器的add()方法,绕过泛型检查,成功插入Integer类型的变量。

//代码
ArrayList<String> stringList = Lists.newArrayList();
ArrayList<Integer> intList = Lists.newArrayList();
System.out.println("intList type is " + intList.getClass());
System.out.println("stringList type is " + stringList.getClass());
System.out.println(stringList.getClass().isAssignableFrom(intList.getClass()));

//运行结果
intList type is class java.util.ArrayList
stringList type is class java.util.ArrayList
true

上述代码中两个泛型类型的ArrayList,一个参数是String,另一个是Integer,当输出getClass方法的类型时均为java.util.ArrayList。而泛型类型擦除导致第三行输出为true,即运行时认为stringList和intList类型一致。

(编译是将你写的代码弄成Java虚拟机可以执行的字节码。 运行是Java虚拟机运行你写的代码(编译后的字节码文件),然后显示运行结果。)
假设参数类型的占位符为T,擦除规则如下:

<T>擦除后变为Obecjt

<? extends A>擦除后变为A

*<? super A>擦除后变为Object

上述擦除规则叫做保留上界。泛型擦除之后保留原始类型。原始类型raw type就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型。无论何时定义一个泛型类型,相应的原始类型都会被自动地提供。类型变量被擦除crased,并使用其限定类型(无限定的变量用Object)替换。

正确的解决方法——TypeToken

对于上面的类ResponseEntity<T>,由于在运行期间无法得知T的具体类型,对这个类的对象进行序列化和反序列化都不能正常进行。Gson通过借助TypeToken类来解决这个问题。

        String json = "\\"Success\\":true,\\"ErrorMsg\\":\\"\\",\\"ErrorNo\\":\\"\\",\\"Result\\":" +
                "\\"FmMobileNumber\\":\\"15555215554\\",\\"FmId\\":3,\\"FlId\\":5,\\"FmUser\\":\\"15555215554\\"";
       
        try 
            Gson gson_1 = new Gson();
            java.lang.reflect.Type type = new TypeToken<ResponseEntity<FamilyMember>>() 
            .getType();
            ResponseEntity<FamilyMember> entity_1 = gson_1.fromJson(json, type);
            entity_1.getResult();
            Log.d(TAG, "onCreate: " + entity_1 + ",,,," + entity_1.getResult());
         catch (Exception e) 
            Log.e(TAG, "Error parsing data " + e.toString());
        

TypeToken的使用非常简单,如上面的代码,只要将需要获取类型的泛型类作为TypeToken的泛型参数构造一个匿名的子类,就可以通过getType()方法获取到我们使用的泛型类的泛型参数类型。

 

参考文章:

学习RxJava之Java的泛型

RxJava-泛型详解及手写实现3

漫谈Java的泛型机制

Gson解析泛型对象时TypeToken的使用方法

json-gson解析泛型及解析null

参考://publicstaticclassparaJson{publicclassparaJson{staticGsongson=newGsonBuilder().registerTypeAdapterFactory(newNullStringToEmptyAdapterFactory()).create();//staticGsongson=newGson();//解析Json对象public 查看详情

使用gson结合泛型解析数据

参考技术A使用Gson结合泛型解析数据时,用到了这么一个方法parameterizedType.getActualTypeArguments()[0]那么它是什么意思呢?getClass().getGenericSuperclass()返回表示此Class所表示的实体(类、接口、基本类型或void)的直接超类的Type,然后将... 查看详情

gson基础用法总结(代码片段)

目录    1、基本数据类型解析    2、基本数据类型生成    3、实体类的解析及生成    4、泛型在Gson中的使用 Gson,大家在都知道是json解析工具,也是google的亲儿子,自然也有着诸多的优点,今天就来总... 查看详情

gson解析json字符串(代码片段)

Gson怎样使用gson把一个json字符串解析成一个jsonObject对象因此我要把上面的fastjson转换成是gson,如下图:JsonObjectobject=newJsonParser().parse(result).getAsJsonObject();怎样从gson中取出键的值使用gson把json字符串转换成一个list集合Lis... 查看详情

gson解析json字符串(代码片段)

Gson怎样使用gson把一个json字符串解析成一个jsonObject对象因此我要把上面的fastjson转换成是gson,如下图:JsonObjectobject=newJsonParser().parse(result).getAsJsonObject();怎样从gson中取出键的值使用gson把json字符串转换成一个list集合Lis... 查看详情

gson速学必会(代码片段)

...子包reflect, annotation,和 stream。reflect包包含处理Java泛型类型信息的类和接口。anno 查看详情

一起talkandroid吧(第三百五十二回:gson库解析json对象)(代码片段)

...位看官们,大家好,上一回中咱们说的是Android中解析JSON数据总结的例子,这一回中咱们介绍的例子是Gson库解析JSON对象。闲话休提,言归正转。让我们一起TalkAndroid吧!看官们,我们在前面章回中介绍了使用Java... 查看详情

一起talkandroid吧(第三百五十八回:gson库解析java对象)(代码片段)

...官们,大家好,上一回中咱们说的是Android中Gson库解析JSON数据的例子,这一回中咱们介绍的例子是Gson库解析Java对象。闲话休提,言归正转。让我们一起TalkAndroid吧!操作步骤看官们,我们在本章回中将介绍如何... 查看详情

在gson中解析数组-每个对象都有一个标识符-2月14日[重复](代码片段)

...答案 我有以下JSON字符串,我试图使用谷歌gson解析它。我尝试了多个选项,但无法将其映射到javapojo。JSON字符串:[DRIVER:"name":"Tom","age":23,DRIVER:"name":"Dick","age" 查看详情

gson反序列化泛型实例

1packagecom.ppmoney.g2.mapper;importcom.google.common.reflect.TypeToken;importcom.google.gson.Gson;importcom.google.gson.JsonSyntaxException;importjava.lang.reflect.Type;publicclassJsonHelper{publicst 查看详情

泛型对象池(代码片段)

为什么需要用到对象池呢?(C#)因为实例化一个类需要在内存堆中划出一块内存来让这个对象使用(泛指C#这种有自己的内存管理机制的语言,像C++这种自己管理内存的就不是了),但是这些实例化的对象并不是在整个程序生命周... 查看详情

一起talkandroid吧(第三百五十三回:gson库解析json数组一)(代码片段)

...官们,大家好,上一回中咱们说的是Android中Gson库解析JSON对象的例子,这一回中咱们介绍的例子是Gson库解析JSON数组。闲话休提,言归正转。让我们一起TalkAndroid吧!使用步骤看官们,在本章回中我们将介绍如何... 查看详情

泛型通配符实例(代码片段)

泛型的通配符:?作用:作为方法的参数集合的泛型使用,可以接收任意的数据类型不能创建对象使用初级使用:我把一些注释,都写在了详细的文档中。1packagecom.cyl.demo;23importjava.util.ArrayList;4importjava.util.Iterator;56publicclassDemo27publicstat... 查看详情

java泛型全解析接口类封装类型(代码片段)

目录泛型为何需要泛型?泛型的好处什么时候使用泛型?泛型的擦除泛型的补偿泛型的应用7.2【泛型方法】【泛型接口】泛型の通配符:?泛型的限定泛型泛型是JavaSE1.5的新特性,泛型的本质是参数化类型ÿ... 查看详情

java泛型全解析接口类封装类型(代码片段)

目录泛型为何需要泛型?泛型的好处什么时候使用泛型?泛型的擦除泛型的补偿泛型的应用7.2【泛型方法】【泛型接口】泛型の通配符:?泛型的限定泛型泛型是JavaSE1.5的新特性,泛型的本质是参数化类型ÿ... 查看详情

gson全解析(上)(代码片段)

...le/gson本篇文章是基于Gson官方使用指导(GsonUserGuide)以及Gson解析的优秀外文(来自http://www.javacreed.com/)做出的一个翻译和归纳。博客原链接:Anthony的简书博客前言最近在研究Retrofit中使用的Gson的时候,发现对Gson的... 查看详情

gson全解析(下)-gson性能分析(代码片段)

...。本系列文章是基于Gson官方使用指导(GsonUserGuide)以及Gson解析的优秀外文(来自http://www.javacreed.com/)做出的一个翻译和归纳。博客原链接:Gson全解析࿰ 查看详情

gson源码解析(代码片段)

...分析3.整体回顾ObjectToJson后记基础了解最近在解决一个Json解析时,把Gson原理过了一遍。Gson是Google开发的Json解析库,当然这种轮子市面上比较多,也不一一列举了。今天主要来聊聊Gson源码ÿ 查看详情