抽象利器 - 泛型

泛型的基本概念 简单介绍一下 Java 的泛型机制,以及使用泛型的好处?
Java 的泛型是 JDK 5 之后引入的一个新特性,主要的作用就是:提供编译时类型安全监测的机制。
泛型的本质是参数化类型,就是说被操作的数据类型可以被当做参数一样被指定。
泛型的好处:
①:增加代码的可读性
②:提高了安全性,将运行时可能抛出的异常,在编译期规避掉。
③:消除了强制转换,避免 ClassCastException
什么是泛型擦除?
Java 中的泛型是伪泛型, Java 在编译阶段所有的泛型信息都会被擦除掉,这就是通常所说的泛型擦除。
为什么会有泛型擦除? 泛型是在 JDK 5 之后出现的新特性,在 JDK 5 之前的类库都没有泛型的概念,所以为了兼容以前的类库,就采用了泛型擦除的方法。
泛型擦除的代价? 泛型不能被用于显示地引用运行时类型的操作之中,例如:转型, instanceof操作和 New 表达式。因为所有参数的类型信息都丢失了。
泛型擦除代码示例:

----------------------------例子1---------------------------------------ArrayList strList = new ArrayList<>(); ArrayList IntegerList = new ArrayList<>(); // 正常来说这边应该是返回 false 的;但是返回的是 true System.out.println(strList.getClass() == IntegerList.getClass()); ------------------------------例子2-----------------------------------------------public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {List list = new ArrayList<>(); list.add(12); // 这里直接添加会报错 // list.add("a"); Class clazz = list.getClass(); Method add = clazz.getDeclaredMethod("add", Object.class); // 但是通过反射添加,是可以的 add.invoke(list, "str"); // [12, str] 打印出来的 List 中还有 str 这说明 Java 本质上是没有泛型的 System.out.println(list); }

泛型中常用的通配符:
①:? 表示不确定的 java 类型
②:T (type) 表示具体的一个 java 类型
③:K V (key value) 分别代表 java 键值中的 Key Value
④:E (element) 代表 Element
泛型方法与静态方泛型 泛型方法
泛型方法:使用类上面声明的泛型
package com.aha.test; public class GenericOne {// 当类上面没有声明 T 的时候 这边就没有办法使用 T 这个泛型 public T getT (T t) { return t; }}package com.aha.test; import lombok.extern.slf4j.Slf4j; @Slf4j public class TestMain {public static void main(String[] args) { GenericOne stringGenericOne = new GenericOne<>(); log.info(stringGenericOne.getT("aha")); }}

静态泛型方法
静态泛型方法是没有办法使用类上面声明的泛型的,为什么?
java 中泛型只是一个占位符,必须在传递类型后才能使用。
类在实例化时才能真正的传递类型参数,由于静态方法的加载先于类的实例化,也就是说类中的泛型还没有传递真正的类型参数,静态的方法的加载就已经完成了,所以静态泛型方法是没有办法使用类上声明的泛型的。
解决办法:
静态方法自己声明自己的泛型类型。
package com.aha.test; public class GenericOne {public T getT (T t) { return t; }// static 这边不生命 E的话就会报错 public static E getE (E e) { return e; }}----package com.aha.test; import lombok.extern.slf4j.Slf4j; @Slf4j public class TestMain {public static void main(String[] args) { GenericOne stringGenericOne = new GenericOne<>(); log.info(stringGenericOne.getT("aha")); log.info("getE:{}", GenericOne.getE(123)); }}

泛型高阶用法实战 业务场景说明
先来看一段代码:
/** * 处理产品类别 商业区间 和 是否接入网关 * 是否接入网关: 只有 ServicePreHandleOrderVo 涉及 * * @param oldDate 数据库直接返回的老数据,上面三个字段在数据库中存储的都是 key 值 * @return 将 产品类别 商业区间 和 是否接入网关 根据 key 值映射成 value 返回的新数据 */ public static List preServiceOrderHandleData (List oldDate,BusinessDataProperties properties) {Map productTypeMap = new HashMap<>(); Map busiScopeMap = new HashMap<>(); List productType = properties.getProductType(); List busiScope =properties.getBusiScope(); // 根据数据库中的 key 从 KeyValueMap 中取到 value 并返回处理好的数据 productType.forEach(e -> productTypeMap.put(e.getKey(),e.getValue())); busiScope.forEach(e -> busiScopeMap.put(e.getKey(),e.getValue())); return oldDate.stream().peek(e -> { // 产品类型 e.setProductTypeValue(productTypeMap.get(e.getProductType())); // 商业区间 e.setBusiScopeValue(busiScopeMap.get(e.getBusiScope())); // 是否接入网关1:是0:否 if ("1".equals(e.getAccessDirect())) { e.setAccessDirectValue("是"); } else if ("0".equals(e.getAccessDirect())) { e.setAccessDirectValue("否"); }}).collect(Collectors.toList()); }

需求便是:将传入的参数 List 其中的某些字段存储的是 key 要将这些 key 映射成对应的 value,然后返回出去。然后呢,每次要处理的数据类似,但是传入参数类型不同,即 List 中的 ServicePreHandleOrderVo 不同,共同点就是每个类型都是处理这三个字段,这边直接使用 T 来代替 ServicePreHandleOrderVo 是不行的,因为在代码中使用到了 T 的具体方法,例如:e.setProductTypeValue(productTypeMap.get(e.getProductType())); 编译器在编译器没有办法确定 T 的具体类型,所以不能直接使用 T 的方法,这边就想到了,将这些需要处理的类的需要处理的字段抽取出来,使用 ? extends class 的方式让编译器可以知道需要处理类型的父类是谁,我们在代码中就可以使用父类的方法,将需要处理的公共属性,抽取到父类中就完美解决咱们的问题了。
提取出来的类:
/** *为了统一处理 产品类型 和 商业区间 抽象出来的实体类 *@author WT *@date 2021-07-14 */ @Data public class HandleDataExcel {/** * 产品类型 */ @ExcelIgnore private String productType; @ExcelProperty("产品类型") @TableField(exist = false) @ColumnWidth(value = https://www.it610.com/article/20) private String productTypeValue; /** * 商业区间 */ @ExcelIgnore private String busiScope; @ExcelProperty("商业区间") @TableField(exist = false) @ColumnWidth(value = https://www.it610.com/article/20) private String busiScopeValue; }

然后将方法进行修改:
/** * 处理产品类型 商业区间 * * @param oldDate 数据库直接返回的老数据,上面三个字段在数据库中存储的都是 key 值 * @return 将 产品类别 商业区间 和 是否接入网关 根据 key 值映射成 value 返回的新数据 */ public static List handleData (List oldDate,BusinessDataProperties properties) {Map productTypeMap = new HashMap<>(); Map busiScopeMap = new HashMap<>(); List productType = properties.getProductType(); List busiScope =properties.getBusiScope(); productType.forEach(e -> productTypeMap.put(e.getKey(),e.getValue())); busiScope.forEach(e -> busiScopeMap.put(e.getKey(),e.getValue())); return oldDate.stream().peek(e -> { // 产品类型 e.setProductTypeValue(productTypeMap.get(e.getProductType())); // 商业区间 e.setBusiScopeValue(busiScopeMap.get(e.getBusiScope())); }).collect(Collectors.toList()); }

注意: 这个是关键,让编译器可以知道 T 父类的类型, 这样就可以使用 T 父类中的方法。我们就可以在方法中使用 e.setProductTypeValue(productTypeMap.get(e.getProductType()));
泛型中 ? super T? extends T
在上面的示例中用到了 extends 这个关键字,当时呢我是类比类的继承关系想到了上面那种解决办法,在写 Lambda 表达式的时候呢经常使用到
Supplier supplier 供给型 Consumer action 消费型 ,让我对 ? super T 这种泛型有了兴趣。所以就了解了一下,这边整理一下。
表示包括 T 在内的任何 T 的父类, 表示包括 T 在内的任何 T 的子类。
讲到这里,就不得不说一下 PECS:(Producer extends Consumer super) 原则了,这个也很好理解,就是当我们要定一个生产者的接口,在定义存放生产者的容器的时候就应该使用 这种泛型,消费者同理。
这是为什么呢?
List 这种数据类型,我们能做的就只有从这个集合中取数据,我们取出的数据可以是 T 的任意一个子类类型或者是 T 类型,但是呢我们没有办法往这个集合中直接塞数据的,因为你不知道这个集合中到底放的是什么类型的数据。一般都是新建立一个集合,在集合中有数据之后,然后将 List 指向新建立的那个集合。所以说生产者用这个类型。可以参考下面这段代码:
package com.train.pojo; import lombok.Data; import java.util.List; @Data public class NumberExample {private List producerList; }// =================package com.train.pojo; import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; import java.util.List; @Slf4j public class TestGeneric {public static void main(String[] args) {// List producerList2 = new ArrayList<>(); // 像这一句就是没有办法被执行的 // producerList2.add(1); ArrayList integerList = new ArrayList<>(); integerList.add(1); integerList.add(3); integerList.add(5); NumberExample numberNumberExample = new NumberExample<>(); // 但是像这样赋值是被允许的 numberNumberExample.setProducerList(integerList); // 或者直接这样赋值 List producerList = integerList; log.info(producerList.toString()); log.info(numberNumberExample.getProducerList().toString()); integerList.add(9); }}

【抽象利器 - 泛型】List consumer 则是与上面的相对立的。
package com.train.pojo; import lombok.Data; import java.util.List; @Data public class ConsumerExample {private List consumerList; }// ===========ConsumerExample consumer = new ConsumerExample<>(); List consumerList = consumer.getConsumerList(); // consumer 则是与 producer 对立的这个就可以直接往里面塞东西 consumerList.add(1); consumerList.add(0.3); // 取的话就是不允许的只能是 object 类型的 Object object = consumerList.get(1);

    推荐阅读