泛型原理
什么是泛型&为什么引入泛型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public static void main(String[] args) { ArrayList list = new ArrayList(); list.add(521); list.add("wan"); list.add(true); list.add('a'); Object item1 = list.get(0); list.forEach(item -> { if (item instanceof Integer) { } else if (item instanceof String) { } else if (item instanceof Boolean) { } }); }
|
JDK5 之前没有泛型时,声明的 List 集合默认是可以存储任意类型元素。开发的时候由于不知道集合中元素的确切类型,遍历的时候我们拿到的 item 其实是 Object 类型,如果要使用就必须强转,强转就必须得判断当前元素的具体类型,否则直接使用强转很可能会发生类型转换异常。这样就会让开发很不方便,每次都要额外做判断工作。
有了泛型的指定,我们声明的 list 就只能存储规定类型 String ,当存储其他类型的元素时编辑器就会直接给我们报错(可以在 IDEA 开发环境中看 add 方法提示参数类型就是 String),这样类型不匹配的问题就在编译时候就能检查出来,而不会在运行时才抛出异常。而且当我们进行遍历、获取元素等操作时,get 方法返回值就是 String 类型的。
总结泛型的好处
- 编译期类型安全
- 避免了强制类型转换运行时异常
- 同一个类可以操作多种类型数据,代码复用
泛型类
ArrayList 是最典型的泛型类例子。 ArrayList 在定义的时候指定了一个泛型,并且下面的添加元素、获取元素等方法也都是对这个 E 进行操作。也可以在一个类中定义多个泛型参数,比如 HashMap。
1
| public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>
|
泛型方法
有时候开发中我们会有这样一种需求,根据方法传入的参数类型不同,返回不同的返回值类型。
1 2 3 4 5 6 7 8 9 10 11
| public static <E, T> List<T> convertListToList(List<E> input, Class<T> clzz) { List<T> output = Lists.newArrayList(); if (CollectionUtils.isNotEmpty(input)) { input.forEach(source -> { T target = BeanUtils.instantiate(clzz); BeanUtils.copyProperties(source, target); output.add(target); }); } return output; }
|
无界泛型通配符
“?” 代表不确定的类型。泛型的上界下界:
1 2
| IPage<? extends AppOrderResponse> IPage<? super AppOrderResponse>
|
上面都是把无界通配符用在返回值,无界通配符也是可以用在方法参数上的。
使用上界参数
1 2 3 4 5 6
| public void test1(List<? extends AppOrderResponse> list){ list.add(new AppOrderDispatchedResponse()); list.add(new AppOrderResponse()); list.add(null); AppOrderResponse appOrderResponse = list.get(0); }
|
泛型上界参数在方法内只能读取,不能写入。
使用下界参数
1 2 3 4 5
| public void test2(List<? super AppOrderResponse> list){ list.add(new AppOrderResponse()); list.add(new AppOrderDispatchedResponse()); Object object = list.get(0); }
|
泛型下界参数在方法内只能写入,不能读取。
泛型通配符 “?” 和 T、E、R、K、V 的区别
- T、E、R、K、V 对于程序运行时没有区别,只是名字不同
- ? 表示不确定的泛型类型
- T (type) 表示具体的一个泛型类型
- K V (key value) 分别代表 Map 中的键值 Key Value
- E (element) 代表元素,例如 ArrayList 中的元素
- T 用于定义泛型类和泛型方法
- “?” 用于方法形参
泛型擦除
所谓的泛型擦除其实很简单,简单来说就是泛型只在编译时起作用,运行时泛型还是被当成 Object 来处理,示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("wan"); String s = list.get(0); ArrayList<Integer> list2 = new ArrayList<>(); System.out.println("list 和 list2 类型相同吗:" + (list.getClass() == list2.getClass())); Method[] methods = list.getClass().getMethods(); for (Method method : methods) { method.setAccessible(true); if (method.getName().equals("add")) { Class<?>[] parameterTypes = method.getParameterTypes(); if (parameterTypes.length == 1) { for (Class<?> parameterType : parameterTypes) { System.out.println("add(E e) 形参 E 的类型为:" + parameterType.getName()); } } } else if (method.getName().equals("get")) { Class<?> returnType = method.getReturnType(); System.out.println("E get(int index) 的返回值 E 的类型为:" + returnType.getName()); } } }
|
可以看到我们实例化 ArrayList 时虽然传入不同的泛型,但其实它们仍然还是同一个类型。对于 add 方法的形参和 get 方法的返回值,按道理说我们指定的泛型是 String 那么打印出来应该是 String 才对,但是这里运行时得到的却都是 Object,所以这就足以证明了,泛型在编译期起作用,运行时一律被擦除当做 Object 看待,这就是泛型擦除。
参考资料
- 一文带你搞懂 Java 泛型 - 知乎 (zhihu.com)
- Java 泛型擦除 - hongdada - 博客园 (cnblogs.com)