抽象利器 - 泛型
泛型的基本概念
简单介绍一下 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 extends List> 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 extends S> supplier 供给型
、 Consumer super T> action 消费型
,让我对 ? super T
这种泛型有了兴趣。所以就了解了一下,这边整理一下。 super T>
表示包括 T
在内的任何 T
的父类, extends T>
表示包括 T
在内的任何 T
的子类。讲到这里,就不得不说一下
PECS:(Producer extends Consumer super)
原则了,这个也很好理解,就是当我们要定一个生产者的接口,在定义存放生产者的容器的时候就应该使用 extends T>
这种泛型,消费者同理。这是为什么呢?
List extends T>
这种数据类型,我们能做的就只有从这个集合中取数据,我们取出的数据可以是 T
的任意一个子类类型或者是 T
类型,但是呢我们没有办法往这个集合中直接塞数据的,因为你不知道这个集合中到底放的是什么类型的数据。一般都是新建立一个集合,在集合中有数据之后,然后将 List extends T>
指向新建立的那个集合。所以说生产者用这个类型。可以参考下面这段代码:package com.train.pojo;
import lombok.Data;
import java.util.List;
@Data
public class NumberExample {private List extends T> 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 extends Integer> 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 extends Integer> producerList = integerList;
log.info(producerList.toString());
log.info(numberNumberExample.getProducerList().toString());
integerList.add(9);
}}
【抽象利器 - 泛型】
List super T> consumer
则是与上面的相对立的。package com.train.pojo;
import lombok.Data;
import java.util.List;
@Data
public class ConsumerExample {private List super T> consumerList;
}// ===========ConsumerExample consumer = new ConsumerExample<>();
List super Number> consumerList = consumer.getConsumerList();
// consumer 则是与 producer 对立的这个就可以直接往里面塞东西
consumerList.add(1);
consumerList.add(0.3);
// 取的话就是不允许的只能是 object 类型的
Object object = consumerList.get(1);
推荐阅读
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- ts泛型使用举例
- Kotlin泛型的高级特性(六)
- Java数据抽象
- 《10大利器帮你改变工作方式》学习心得
- 教员法则
- C#|10、接口、抽象、密封、开放封闭原则
- 谈git的故事
- C++抽象数据类型介绍
- 第十章抽象与封装