一,前期基础知识储备

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);//1getUperNumber(age);//2getUperNumber(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 {@Overrideprotected 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 {@Overrideprotected 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的使用方法》

实例:Gson解析泛型对象相关推荐

  1. Kotlin Gson解析泛型对象

    API:API说明文档:www.juhe.cn/docs/api/id- 接口地址:v.juhe.cn/wepiao/quer- API返回的json: {"reason":&qu ...

  2. Gson解析json对象,json数组

    String jsonStr 为 {    "error": 0,    "msg": "成功",    "result" ...

  3. android 生成泛型对象,java android解析多层含有泛型对象的json数据获取不到泛型类型解析失败解决办法...

    ####问题描述 * java 解析多层含有泛型对象的json数据获取不到泛型类型 * 如果将泛型改成实际的类型就能正常解析 * 如果不改成实际的类型泛型数据被解析成com.google.gson.i ...

  4. JavaScript中hoisting(悬置/置顶解析/预解析) 实例解释,全局对象,隐含的全局概念...

    JavaScript中hoisting(悬置/置顶解析/预解析) 实例解释,全局对象,隐含的全局概念 <html><body><script type="tex ...

  5. 使用Gson解析Json为Map对象探索(上)

    使用Gson解析Json为Map对象探索(上) 有些时候由于后台业务系统的不一样导致一个Json的list对象装的并非是同一种类型的对象,导致无法建立比较好的Java模型,而刚好可以利用map对象的通 ...

  6. 使用Gson解析Json为Map对象探索(下)

    使用Gson解析Json为Map对象探索(下) 下面来说说其中出现的问题 1.如何获取一个list列表 1.1首先是数据格式 1.2处理成列表的代码 com.google.gson.Gson gson ...

  7. gson解析json maven_Gson解析Json

    Json(JavaScript Object Notation)是一种轻量级的数据交换格式,类似XML,但比XML更小更快更容易解析.当前各种流行的web应用框架都对Json提供良好的支持,各种流行开 ...

  8. 深入Java泛型(五):Json解析泛型

    前言 dio是一个强大的Dart Http请求库,支持Restful API.FormData.拦截器.请求取消.Cookie管理.文件上传/下载.超时.自定义适配器等- 为啥封装 统一处理请求域名: ...

  9. Java中如何利用gson解析数据

    最近在学习Java,需要用到json,本人对java不是特别精通,于是开始搜索一些java平台的json类库. 发现了google的gson,带着一些好奇心,我开始使用了gson. 经过比较,gson ...

最新文章

  1. python自动化测试难不难_Python测试自动化好学还是Pythonweb开发好学?
  2. JS Range 对象的使用
  3. python实现项目的复制_Python之copy模块
  4. 关于J2EE中死锁问题的研究(2)
  5. CVTE前端笔试编程题
  6. 3-3 修改haproxy配置文件
  7. 组合范畴语法 CCG
  8. 不要奢望.NET能够跨平台
  9. R语言:data.table语句批量生成变量
  10. c语言伪代码写for循环,伪代码撰写规范
  11. 数据库基础知识(面试)
  12. iOS逆向:破解 APP防止dyld注入 的方法
  13. 实验二.常用网络命令
  14. sofelf转jic
  15. CentOS 某服务器搭建问题收集
  16. Windows下修改本机域名localhost
  17. 计算机无法还原,win7旗舰版系统无法创建系统还原点 无法还原怎么办
  18. win7计算机搜索功能没有了,win7搜索功能不能用了怎么办|win7搜索功能不见了怎么解决? - 学无忧...
  19. ChatGPT封杀潮,禁入学校,AI顶会特意改规则,LeCun:要不咱把小模型也禁了?...
  20. 让用户输入一个三位数(若不是三位数则提示错误),判断该数是否是水仙花数。(水仙花数:每一位上的数字的立方和,等于该数本身)

热门文章

  1. C#的Directory类和DirectoryInfo类
  2. delauney 三角化
  3. Flash小技巧之allowScriptAccess
  4. WrapPanel:自动折行面板(环绕面板)基础简述
  5. [python]利用随机api二次元图片
  6. Android 基础知识3:四大组件之 Broadcast(广播)
  7. 用ESP8266播放播放bad apple(一):点亮LED——IO口的简单输出应用
  8. r96950hs和r76850hs哪个好
  9. 【面经】嘉实基金-科技类-数据方向面经
  10. Console.WriteLine()方法