泛型的定义
泛型,即参数化类型
。就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
例如:GenericClass**<T>**{}
一些常用的泛型类型变量:
E:元素(Element),多用于java集合框架
K:关键字(Key)
N:数字(Number)
T:类型(Type)
V:值(Value)
先来看个简单的例子,
1 | List<String> list = new ArrayList(); |
运行后会发现报了一个错误
这里可以看出来在代码编写阶段就已经报错了,不能往string类型的集合中添加int类型的数据。
那可不可以往List集合中添加多个类型的数据呢,答案是可以的,其实我们可以把list集合当成普通的类也是没问题的,那么就有下面的代码:
1 | List list = new ArrayList(); |
从这里可以看出来,不定义泛型也是可以往集合中添加数据的,所以说泛型只是一种类型的规范,在代码编写阶段起一种限制。
下面我们通过例子来介绍泛型背后数据是什么类型。
1 | import java.lang.reflect.Field; |
输出结果:
1 | > Task :Demo01.main() |
从日志上看到通过反射获取到的属性是Object
类型的,在方法中返回的是String
类型,因此可以思考在getValue
方法里面实际是做了个强转的动作,将Object
类型的value强转成String
类型。
因为泛型只是为了约束我们规范代码,而对于编译完之后的class交给虚拟机后,对于虚拟机它是没有泛型的说法的,所有的泛型在它看来都是Object
类型,因此泛型擦除是对于虚拟机而言的。
下面我们再来看一种泛型结构:
1 | class BaseBean2<T extends String> { |
将泛型加了个关键字extends
,对于泛型写得多的伙伴们来说,extends
是约束了泛型是向下继承的,最后我们通过反射获取value的类型是String类型的,因此这里也不难看出,加extends
关键字其实最终目的是约束泛型是属于哪一类的。所以我们在编写代码的时候如果没有向下兼容类型,会警告错误的:
既然说了泛型其实对于jvm来说都是Object类型的,那咱们直接将类型定义成Object
不就是的了,这种做法是可以,但是在拿到Object
类型值之后,自己还得强转,因此泛型减少了代码的强转工作,而将这些工作交给了虚拟机。
比如下面我们没有定义泛型的例子:
1 | class BaseBean3 { |
势必在getValue的时候代码有个强转的过程,因此在能用泛型的时候,尽量用泛型来写,而且我认为一个好的架构师,业务的抽取是离不开泛型的定义。
常见的泛型主要有作用在普通类上面,作用在抽象类、接口、静态或非静态方法上。
类上面的泛型
1 | public class BaseBean<T> { |
抽象类或接口上的泛型
1 | public abstract class BaseAdapter<T> { |
多元泛型
1 | public interface Base<K, V> { |
多种泛型约束
1 | public static <T extends Comparable<T> & Supplier<R>, R extends Number> { |
泛型二级抽象类或接口
1 | public interface BaseCommon<K extends Common1, V> extends Base<K, V> { |
抽象里面包含抽象
1 | public interface Base<K, V> { |
<?>通配符
<?>通配符
和 <T>
区别是<?>
在你不知道泛型类型的时候,可以用<?>
通配符来定义,下面通过一个例子来看看<?>
的用处:
1 | class BaseBean<T> { |
在定义的时候将Common
的泛型指向Common1
的泛型,可以看到直接提示有问题,这里可以想,虽然Common1
是继承自Common
的,但是并不代表BaseBean
之间是等量的,在开篇也讲过,如果泛型传入的是什么类型,那么在BaseBean
中的getValue
返回的类型就是什么,因此可以想两个不同的泛型类肯定是不等价的,但是如果我这里写呢:
1 | public static void main(String[] args) { |
在上面如果定义的泛型是通配符是可以等价的,因为此时的setValue的参数是Object类型,所以能直接将上面定义的泛型赋给通配符的BaseBean。
通配符不能定义在类上面、接口或方法上,只能作用在方法的参数上
其他的几种情况自己去尝试,正确的使用通配符:
1 | public void setClass(Class<?> class) {} |
上限以及下限泛型
<T extends >
、<T super >
、<? extends >
、<? super >
<T extends **>
表示上限泛型、<T super **>
表示下限泛型
为了演示这两个通配符的作用,增加了一个类:
1 | public class Demo01 { |
会看到出现以下错误
Type parameter 'BaseCommon' is not within its bound; should extend 'Common'
第二个定义的泛型是不合法的,因为BaseCommon
是Common
的父类,超出了Common
的类型范围。
<T super >
不能作用在类、接口、方法上,只能通过方法传参来定义泛型
在BaseBean里面定义了个方法:
1 | public class Demo01 { |
可以看到当传进去的是Common1.class
的时候是不合法的,因为在add
方法中需要传入Common
父类的字节码对象,而Common1
是继承自Common
,所以直接不合法。
在实际开发中其实知道什么时候定义什么类型的泛型就ok,在mvp实际案例中泛型用得比较广泛,大家可以根据实际项目来找找泛型的感觉,只是面试的时候需要理解类型擦除是针对谁而言的。
类型擦除
其实在开篇的时候已经通过例子说明了,通过反射绕开泛型的定义,也说明了类中定义的泛型最终是以Object被jvm执行。
所有的泛型在jvm中执行的时候,都是以Object
对象存在的,加泛型只是为了一种代码的规范,避免了开发过程中再次强转。
泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。
摘录自: