1. 泛型概述
泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。
泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是安全简单。
在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。
泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。
可以在集合框架(Collection framework)中看到泛型的动机。例如,Map 类允许您向一个 Map添加任意类的对象,即使最常见的情况是在给定映射(map)中保存某个特定类型(比如 String)的对象。
因为 Map.get() 被定义为返回 Object,所以一般必须将 Map.get() 的结果强制类型转换为期望的类型,如下面的代码所示:
|
|
要让程序通过编译,必须将 get() 的结果强制类型转换为 String,并且希望结果真的是一个 String。但是有可能某人已经在该映射中保存了不是 String 的东西,这样的话,上面的代码将会抛出 ClassCastException。
理想情况下,您可能会得出这样一个观点,即 m 是一个 Map,它将 String 键映射到 String 值。这可以让您消除代码中的强制类型转换,同时获得一个附加的类型检查层,该检查层可以防止有人将错误类型的键或值保存在集合中。这就是泛型所做的工作。
|
|
2. 泛型的好处
Java 语言中引入泛型是一个较大的功能增强。不仅语言、类型系统和编译器有了较大的变化,以支持泛型,而且类库也进行了大翻修,所以许多重要的类,比如集合框架,都已经成为泛型化的了。这带来了很多好处:
2.1 类型安全
泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。
Java 程序中的一种流行技术是定义这样的集合,即它的元素或键是公共类型的,比如“String 列表”或者“String 到 String 的映射”。通过在变量声明中捕获这一附加的类型信息,泛型允许编译器实施这些附加的类型约束。类型错误现在就可以在编译时被捕获了,而不是在运行时当作 ClassCastException 展示出来。将类型检查从运行时挪到编译时有助于您更容易找到错误,并可提高程序的可靠性。
2.2 消除强制类型转换
泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。
尽管减少强制类型转换可以降低使用泛型类的代码的罗嗦程度,但是声明泛型变量会带来相应的罗嗦。
2.3 优化了程序设计,解决了黄色警告线
3. 泛型的应用
3.1 泛型的内部原理
泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入。但是,编译器编译带类型说明的集合时会去除掉“类型”信息,目的就是使程序运行效率不受影响。因此,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。
|
|
由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法即可。
|
|
ArrayList
- 整个称为ArrayList<E>泛型类型
- ArrayList<E>中的E称为类型变量或类型参数
- 整个ArrayList<Integer>称为参数化的类型
- ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
- ArrayList<Integer>中的<>念着typeof
- ArrayList称为原始类型
参数化类型与原始类型的兼容性:参数化类型可以引用一个原始类型的对象,编译报告警告,例如
|
|
原始类型可以引用一个参数化类型的对象,编译报告警告,例如
参数化类型不考虑类型参数的继承关系:
|
|
注意:
假设Vector<String> v = new Vector<Object>();可以的话,那么以后从v中取出的对象当作String用,而v实际指向的对象中可以加入任意的类型对象;
假设Vector<Object> v = new Vector<String>();可以的话,那么以后可以向v中加入任意的类型对象,而v实际指向的集合中只能装String类型的对象。
编译器不允许创建泛型变量的数组。即在创建数组实例时,数组的元素不能使用参数化的类型。
例如,下面语句有错误:
|
|
思考题:
下面的代码会报错误吗?
|
|
答案:编译的时候是不会报错的,因为编译器是一行一行按照语法检查代码的,因此不会出错。
4. 泛型类
把泛型定义在类上,格式:public class 类名<泛型类型1,…>,注意:泛型类型必须是引用类型
|
|
5. 泛型方法
把泛型定义在方法上,格式:public <泛型类型> 返回类型 方法名(泛型类型 .)
|
|
6. 泛型接口
把泛型定义在接口上,格式:public interface 接口名<泛型类型1…>
|
|
7. 泛型高级(通配符)
为了解决类型被限制死了不能动态根据实例来确定的缺点,引入了“通配符泛型”,针对上面的例子,使用通配泛型格式为<? extends Collection>,“?”代表未知类型,这个类型是实现Collection接口。? extends E:向下限定,E及其子类,限定通配符的上边界。? super E:向上限定,E及其父类,限定通配符的下边界。
|
|
泛型
泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去除掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法即可。
泛型引用和创建两端,给出的泛型变量必须相同
泛型类
|
|
泛型类中使用泛型
- 成员类型
- 返回值和参数类型
- 局部变量的引用上
|
|
泛型方法
|
|
泛型方法与泛型类没有什么关系,泛型方法不一定非要在泛型类中!
泛型的继承和实现
|
|
继承泛型类
- 子类不是泛型类:需要给父类传递类型常量
当给父类传递的类型常量为String时,那么在父类中所有T都会被String替换!
|
|
- 子类是泛型类:可以给父类传递类型常量,也可以传递类型变量
|
|
通配符
- 无限通配符<?>
- 向下通配符<? extends T>
- 向上通配符<? super T>
类型推断
- 通过反射的方式获取泛型的实际类型
- 泛型只能是引用类型,不能是基本数据类型
泛型擦除
泛型会在编译时擦除,List
Gson泛型封装
在你真的会用Gson吗?Gson使用指南(一) 的第三节我介绍了在Gson中如何使用泛型来简化我们的类设计,但随之而来引入了一个新的问题:封装。不知道各位有没有想过这样一个问题:每次都要用 new TypeToken<XXX>(){};
好麻烦,有没有更好的办法?
有更好的办法么?当然有!相信也有不少人自己作了尝试,只是有人欢喜有人愁了,不过没关系,今天我们就来解决这个问题。
约定
1、本文涉及到的json格式
|
|
2、假定第一种的对应的Java类型为 Result<XXX>
,第二种为 Result<List<XXX>>
为何封装,如何封装
1. 为何封装:
- 写
new TypeToken<XXX>(){}
麻烦,IDE格式化后还不好看 - 不同的地方每进行一次
new TypeToken<XXX>(){}
操作都会生成一个新的类 - 对于任意类
XXX
都只有两种情况new TypeToken<Result<XXX>>(){}
和new TypeToken<Result<List<XXX>>>(){}
- 方便统一管理
2. 如何封装
从上面的我们可以知道,最简单的方法就是提供两个方法分别对应data
为Array和Object的情况并接收一个参数,即告知XXX的类型,自动将完成new TypeToken<XXX>(){}
与new TypeToken<Result<List<XXX>>>(){}
的过程。
方法原型:
|
|
为何失败?
对于那些尝试着封装过的人可能都这么写过:
|
|
当然上面的写法肯定是没有办法完成的,虽然代码不会报错,但运行结果肯定是不对的,因为这里的T
其实是一个 TypeVariable
,他在运行时并不会变成我们想要的XXX,所以通过TypeToken
得到的 泛型信息只是 "Result<List<T>>"
。
如何解决?
既然TypeToken的作用是用于获取泛型的类,返回的类型为Type
,真正的泛型信息就是放在这个Type
里面,既然用TypeToken生成会有问题,那我们自己生成Type就行了嘛。
Type是Java中所有类型的父接口,在1.8以前是一个空接口,自1.8起多了个getTypeName()
方法,下面有ParameterizedType
、 GenericArrayType
、 WildcardType
、 TypeVariable
几个接口,以及Class
类。这几个接口在本次封装过程中只会用到 ParameterizedType
,所以简单说一下:
ParameterizedType
简单说来就是形如“ 类型<> ”的类型,如:Map<String,User>
。下面就以 Map<String,User>
为例讲一下里面各个方法的作用。
|
|
所以,知道了这里需要的泛型是怎么回事,一切都好说了,下面我们来完成之前留下的空方法。
1. 实现一个简易的 ParameterizedType
|
|
2. 生成Gson需要的泛型
2.1 解析data是object的情况
|
|
2.2 解析data是array的情况
是Array的情况要比是Object的情况多那么一步。
|
|
本次代码较少,不提供源码
虽然这篇博客是以Gson为例,但从上面的内容可以看出实际上和Gson关系不大,主要的内容还是Java的泛型基础,所以这种封装的方法同样适用于其它的框架。
最后借这次机会给安利一个简易的泛型生成库 TypeBuilder ,其最初实现的目的就是让大家快速的生成泛型信息,同时也会作一些参数检查,保证正确性。
用上面的代码给大家举个例子
|
|
Type
Type 是 Java 编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型
ParameterizedType
ParameterizedType 表示参数化类型,如 Collection
方法 | 说明 |
---|---|
Type[ ] getActualTypeArguments() | 获取真实参数 |
|
|