Android、Java泛型扫盲
首先我们定义A、B、C、D四个类,他们的关系如下
1 | class A {} |
不指明泛型类型
1 | //以下代码均编译通过 |
这个好理解,因为所有的类都继承与Object,故能往list里面添加任意实例对象
无边界通配符 ?
首先我们要明白一个概念,通配符?
意义就是它是一个未知的符号,可以是代表任意的类。
1 | //我们发现,这样写编译不通过,原因很简单,泛型不匹配,虽然B继承A |
知识点
- 无边界通配符
?
能取不能存。这个好理解,因为编译器不知道?
具体是啥类型,故不能存;但是任意类型都继承于Object,故能取,但取出默认为Object对象。
上边界符 ?extends
继续上代码
1 | List<? extends C> listC; |
知识点
- 上边界符
? extends
只是限定了赋值给它的实例类型(这里为赋值给listC的实例类型),且边界包括自身。 - 上边界符
? extends
跟?
一样能取不能存,道理是一样的,虽然限定了上边界,但编译器依然不知道?
是啥类型,故不能存;但是限定了上边界,故取出来的对象类型默认为上边界的类型
下边界符 ?super
1 | List<? super B> listB; |
知识点
- 下边界符
?super
,跟上边界符一样,只是限定了赋值给它的实例类型,也包括边界自身 - 下边界符
?super
能存能取,因为设定了下边界,故我们能存下边界以下的类型,当然也包括边界自身;然而取得时候编译器依然不知道?
具体是什么类型,故取出默认为Object类型。
类型擦除
首先我们要明白一点:Java 的泛型在编译期有效,在运行期会被删除 我们来看一段代码
1 | //这两个方法写在同一个类里 |
上面的代码会有问题吗?显然是有的,编译器报错,提示如下信息: list(List<A>) clashed with list(List<B>) ; both methods have same erasure
翻译过来就是,在类型擦除后,两个方法具有相同的签名,我们来看看类型擦除后是什么样子
1 | public void list(List listA) {} |
可以看出,两个方法签名完全一致,故编译不通过。 明白了类型擦除,我们还需要明白一个概念
- 泛型类并没有自己独有的Class类对象
比如并不存在List.class或是List.class,而只有List.class 接下来这个案例就好理解了
1 | List<A> listA = new ArrayList<A>(); |
泛型传递
现实开发中,我们经常会用到泛型传递,例如我们经常需要对Http请求返回的结果做反序列化操作
1 | public static <T> T fromJson(String result, Class<T> type) { |
此时我们传进去是什么类型,就会返回自动该类型的对象
1 | String result="xxx"; |
那如果我们想返回一个集合呢,如List<A>
,下面这样明显是不对的。
1 | //编译报错,前面类型擦除时,我们讲过,不存List<A>.class这种类型 |
那我们该怎么做呢?首先,我们对fromJson
改造一下,如下:
1 | //type为一个数组类型 |
这个时候我们就可以这么做了
1 | String result="xxx"; |
ok,我在再来,相信大多数Http接口返回的数据格式是这样的:
1 | public class Response<T> { |
那这种我们又该如何传递呢?显然用前面的两个fromJson
方法都行不通,我们再来改造一下,如下:
1 | //这里我们直接传递一个Type类型 |
这个Type是什么鬼?点进去看看
1 | public interface Type { |
哦,原来就是一个接口,并且只有一个方法,我们再来看看它的实现类
发现有5个实现类,其中4个是接口,另外一个是Class类,我们再来看看Class类的声明
1 | public final class Class<T> implements java.io.Serializable, |
现在有没有明白点,现在我们重点来关注下Type
接口的其中一个实现接口ParameterizedType
,我们来看下它的内部代码,里面就只有3个方法
1 | public interface ParameterizedType extends Type { |
顾名思义,ParameterizedType
代表一个参数化类型。
这个时候我们来自定义一个类,并实现ParameterizedType接口,如下:
1 | public class ParameterizedTypeImpl implements ParameterizedType { |
我们再次贴出fromJson
方法
1 | //这里我们直接传递一个Type类型 |
此时我们想得到Response<T>
对象,就可以这样写
1 | Response<A> responseA = fromJson(result, new ParameterizedTypeImpl(Response.class, A.class)); |
想得到List<T>
对象,也可以通过ParameterizedTypeImpl
得到,如下:
1 | List<A> listA = fromJson(result, new ParameterizedTypeImpl(List.class, A.class)); |
然而,如果我们想得到Response<List<T>>
对象,又该如何得到呢? ParameterizedTypeImpl
一样能够实现,如下:
1 | //第一步,创建List<T>对象对应的Type类型 |
然后,能不能再简单一点呢?可以,我们对ParameterizedTypeImpl
改造一下
1 | /** |
此时,我们就可以这样写
1 | //第一步,直接创建Response<List<T>>对象对应的Type类型 |
现实开发中,我们还可能遇到这样的数据结构
1 | { |
此时,Response<T>
里面的泛型传List肯定是不能正常解析的,我们需要再定一个类
1 | public class PageList<T>{ |
此时就可以这样解析数据
1 | //第一步,直接创建Response<PageList<T>>对象对应的Type类型 |
注:ParameterizedTypeImpl get(Type... types)
仅仅适用于单个泛型参数的时候,如Map等,有两个泛型参数以上的不要用此方法获取Type类型。如果需要获取Map等两个泛型参数以上的Type类型。可调用getParameterized(@NonNull Type rawType, @NonNull Type... actualTypeArguments)
构造方法获取,如:
1 | //获取 Map<String,String> 对应的Type类型 |
到这,泛型相关知识点讲解完毕,如有疑问,请留言。