本质
java泛型(generics)是JDK5中引入的一个新特性,提供了编译时类型安全检测机制。该机制允许程序员在编译时检测到非法的类型。
泛型的本质是参数化类型,即给类型制定一个参数,然后再使用时再指定此参数具体的值,那样这个类型就可以在使用时决定了,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
1 | ArrayList<String> stringValues = new ArrayList<String>(); |
为什么使用泛型
保证了类型的安全性
泛型的作用就是在编译的时候就会检查类型安全,并且所有的强制转换都是自动和隐式的。
1 | ArrayList<Cat> stringValues = new ArrayList<Cat>(); |
相当于告诉了编译器每个集合接受的对象类型是什么,编译器在编译期就会做类型检查,告知是否插入了错误类型的对象,使得程序更加安全,增强了程序的健壮性。
消除强制转换
消除源代码中的许多强制类型转换,这使得代码更加可读,并且减少了出错机会。
1 | List<String> list = new ArrayList<String>(); |
避免了不必要的装箱,拆箱操作
将简单类型作为Object传递时会引起Boxing和Unboxing操作,这两个过程都是具有很大开销的。引入泛型后,就不必进行Boxing和Unboxing的操作了,所以运行效率相对较高,特别在对集合操作非常频繁的系统中。
1 | Object a = 1;//由于是object类型,会自动进行装箱操作 |
如何使用泛型
泛型类
1 | public class 类名<泛型类型1, ...>{} |
定义泛型类,在类后添加一对尖括号,并在尖括号中填写类型参数,参数可以有多个,过个参数使用逗号分隔
1 | T:任意类型 type |
1 | public class GenericClass<T> { |
泛型接口
public <泛型类型> 返回类型 方法名(泛型类型 变量名) {}
方法声明中定义的形参只能在方法里使用,而接口、类声明中定义的类型形参则可以在整个接口、类中使用。当调用fun()方法时,根据传入的实际对象,编译器就会判断出类型形参T所代表的实际类型。
1 | public interface GenericInterface<T> { |
泛型方法
1 | 修饰符<代表泛型的变量>返回值类型 方法名(参数){} |
1 | public <T> T genercMethod(T t){ |
这里可以看出,泛型方法随着我们的传入参数类型不同,他得到的类型也不同。泛型方法能使方法独立于类而产生变化。
泛型通配符
Java泛型的通配符是用于解决泛型之间引用传递问题的特殊语法,主要有以下三类
<?> :无边界的通配符
<? extends E>:固定上边界的通配符
<? super E>:固定下边界的通配符
1 | //表示类型参数可以是任何类型 |
泛型擦除
Java的泛型只在编译时有效,到了运行时这个泛型类型就会被擦除掉,即List和List在运行时其实都是List类型。
为什么选择这种实现机制?在Java诞生10年后,才实现类似C++模板的概念,即泛型。Java的类库是Java生态中非常宝贵的财富,所以需要保证向后兼容,和迁移兼容,基于上面的背景和考虑,Java设计者采取了类型擦除这种折中的实现方式。正是这样,令我们无法在运行期间随心所欲的获取到泛型参数的具体类型。
TypeToken使用
Gson在反序列化时需要定义一个TypeToken类型
1 | private Type type = new TypeToken<List<Map<String, Foo>>>(){}.getType(); |
为什么要用TypeToken来定义反序列化的类型?正如上面所说,如果直接把List<Map<String, Foo>>的类型传过去,但是因为运行泛型被擦除了,所以得到的其实是List,那么后面的Gson就不知道要转成Map<String, Foo>类型了,这是Gson会默认转成LinkedTreeMap类型。
后面的大括号,在Java语法中,在这个语境,{}是用来定义匿名类,这个匿名类是继承了TypeToken类,它是TypeToken的子类。
为什么要通过子类来获取泛型的类型?这是TypeToken能够获取到泛型类型的关键,这是一个巧妙的方法,这个想法是这样子的,既然像List这样中的泛型会被擦除掉,那么我用一个子类SubList extends List这样的话,在JVM内部中会不会把父类泛型的类型给保存下来呢?这个子类需要继承的父类的泛型都是已经确定了的,JVM确实是有保存这部分信息的,它是保存在子类Class信息中,我们可以通过下述方法获取,https://stackoverflow.com/questions/937933/where-are-generic-types-stored-in-java-class-files
1 | Type mySuperClass = foo.getClass().getGenericSuperClass(); |
Class类的getGenericSuperClass()方法的注释是
概括来说就是对于带有泛型的class,返回一个ParameterizedType对象,对于Object、接口和原始类型返回null,对于数 组class则是返回Object.class。ParameterizedType是表示带有泛型参数的类型的Java类型,JDK1.5引入了泛型之 后,Java中所有的Class都实现了Type接口,ParameterizedType则是继承了Type接口,所有包含泛型的Class类都会实现 这个接口。
DEMO
1 | /** |
引用
https://blog.csdn.net/qq_41701956/article/details/123473592
https://blog.csdn.net/weixin_49527334/article/details/115334014