泛型的基本概念简单介绍下Java的泛型机制和使用泛型的好处?Java的泛型是JDK5之后引入的新特性,其主要作用是提供编译时类型安全监控机制。泛型的本质是参数化类型,也就是说可以将被操作的数据类型指定为参数。泛型的好处:①:增加代码的可读性②:提高安全性,在编译时避免运行时可能抛出的异常。③:消除强制转换,避免ClassCastException。什么是通用擦除?Java中的泛型是伪泛型,Java中所有的泛型信息在编译时都会被擦除,也就是通常所说的泛型擦除。为什么会有泛型擦除?泛型是JDK5之后出现的新特性,JDK5之前的类库没有泛型的概念,所以为了兼容之前的类库,采用了泛型擦除的方式。通用擦除的价格?泛型不能用于显式引用运行时类型的操作,例如强制转换、instanceof操作和New表达式。因为所有参数的类型信息都丢失了。通用纠删码示例:--------------------------示例1--------------------------------------ArrayListstrList=newArrayList<>();ArrayListIntegerList=newArrayList<>();//正常情况下,这应该返回false;但返回是trueSystem.out.println(strList.getClass()==IntegerList.getClass());------------------------------例2-----------------------------------------------publicstaticvoidmain(String[]args)throwsNoSuchMethodException,InvocationTargetException,IllegalAccessException{Listlist=newArrayList<>();列表。添加(12);//这里直接添加会报错//list.add("a");类clazz=list.getClass();方法add=clazz.getDeclaredMethod("add",Object.class);//但可以通过反射添加add.invoke(list,"str");//[12,str]打印出的List中还有str,说明Java本质上是一个没有泛型的System.out.println(list);}泛型中常用的通配符:①:?表示一个不确定的java类型②:T(type)表示一个具体的java类型③:KV(keyvalue)分别表示java的keyvaluesKeyValue④:E(element)代表Element泛型方法和静态泛型方法声明T时,没办法使用T的泛型类型publicTgetT(Tt){returnt;}}packagecom.aha.test;importlombok.extern.slf4j.Slf4j;@Slf4jpublicclassTestMain{publicstaticvoidmain(String[]args){GenericOnestringGenericOne=newGenericOne<>();log.info(stringGenericOne.getT("aha"));}}静态泛型方法静态泛型方法是没办法使用类上面声明的泛型,为什么?在java中,泛型只是一个占位符。您必须先传递类型,然后才能使用类在实例化时实际传递类型参数。因为静态方法是在类实例化之前加载的,也就是说类中的泛型静态方法的加载已经完成,没有传递真正的类型参数,所以静态泛型方法没有办法使用在类上声明的泛型类型。解决方案:静态方法声明自己的泛型类型。packagecom.aha.test;publicclassGenericOne{publicTgetT(Tt){returnt;}//static如果没有生命E,会报错publicstaticEgetE(Ee){returne;}}----packagecom.aha.test;importlombok.extern.slf4j.Slf4j;@Slf4jpublicclassTestMain{publicstaticvoidmain(String[]args){GenericOnestringGenericOne=newGenericOne<>();log.info(stringGenericOne.getT("aha"));log.info("getE:{}",GenericOne.getE(123));}}实际业务场景中的通用高层用法解释先看一段代码:/***处理商品类目业务板块,是否接入网关*是否接入网关:只有ServicePreHandleOrderVo涉及**@paramoldDate数据库直接返回的旧数据,以上三个字段存储在数据库中都是键值*@return根据键值*/publicstaticListpreServiceOrderHandleData(ListoldDate,BusinessDataPropertiesproperties){MapproductTypeMap=newHashMap<>();地图<字符串,字符串>busiScopeMap=newHashMap<>();ListproductType=properties.getProductType();ListbusiScope=properties.getBusiScope();//根据数据库中的key从KeyValueMap中获取value并返回处理后的数据productType.forEach(e->productTypeMap.put(e.getKey(),e.getValue()));busiScope.forEach(e->busiScopeMap.put(e.getKey(),e.getValue()));returnoldDate.stream().peek(e->{//产品类型e.setProductTypeValue(productTypeMap.get(e.getProductType()));//业务范围e.setBusiScopeValue(busiScopeMap.get(e.getBusiScope())));//接入网关1:是0:否if("1".equals(e.getAccessDirect())){e.setAccessDirectValue("yes");}elseif("0".equals(e.getAccessDirect())){e.setAccessDirectValue("No");}}).collect(Collectors.toList());}要求是:传入参数List中的一些字段存储键,这些键需要映射到对应的值,然后返回然后,每次处理的数据都差不多,只是入参类型不同,也就是List中的ServicePreHandleOrderVo不同,共同点是每种类型都处理这三个字段,这里直接用T而不是ServicePreHandleOrderVo否,因为代码中使用了T的具体方法,例如:e.setProductTypeValue(productTypeMap.get(e.getProductType()));编译器在编译器中没有办法确定T的具体类型,所以不能直接使用T的方法,这里想到了,从这些需要处理的类中提取需要处理的字段,并使用的方法?extendsclass让编译器知道需要处理的类型的父类是谁。我们可以使用父类的方法,将需要处理的公共属性提取到父类中,完美解决了我们的问题。提取类:/***产品类型和业务区间统一处理的抽象实体类*@authorWT*@date2021-07-14*/@DatapublicclassHandleDataExcel{/***产品类型*/@ExcelIgnoreprivateString产品类别;@ExcelProperty("产品类型")@TableField(exist=false)@ColumnWidth(value=20)privateStringproductTypeValue;/***业务范围*/@ExcelIgnoreprivateStringbusiScope;@ExcelProperty("BusinessInterval")@TableField(exist=false)@ColumnWidth(value=20)privateStringbusiScopeValue;}然后修改方法:/***Processproducttypebusinessinterval**@paramoldDate直接旧数据由数据库返回,上面三个字段都作为键值存入数据库*@return根据键值映射商品类别业务范围和是否接入网关到值返回的新数据*/publicstaticListhandleData(ListoldDate,BusinessDataProperties属性){MapproductTypeMap=newHashMap<>();MapbusiScopeMap=newHashMap<>();ListproductType=properties.getPr产品类型();ListbusiScope=properties.getBusiScope();productType.forEach(e->productTypeMap.put(e.getKey(),e.getValue()));busiScope.forEach(e->busiScopeMap.put(e.getKey(),e.getValue()));returnoldDate.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()));在通用类型中,?超级T和?extendsT在上面的例子中使用了关键字extends。当时因为类比类的继承关系想到了上面的解决方案。在编写Lambda表达式时,我经常使用Supplier供应商供应类型,消费者action消费类型,这让我对?的泛型类型产生了兴趣。超T。于是了解了一下,整理到这里。表示包括T在内的任意T的父类,表示T的任何子类,包括T。说到这里,不得不说一下PECS:(ProducerextendsConsumersuper)原理,这个原理也很好理解,就是我们要定义生产者接口的时候,应该使用这种泛型,消费者也一样。为什么是这样?对于数据类型List,我们所能做的就是从这个集合中获取数据。我们取出来的数据可以是T或者T类型的任意子类类型,但是我们没有办法去这个集合里面直接塞数据,因为你不知道这个集合里面放的是什么类型的数据.通常,会创建一个新集合。集合中有数据后,List指向新创建的集合。所以生产者使用这种类型。可以参考如下代码:packagecom.train.pojo;importlombok.Data;importjava.util.List;@DatapublicclassNumberExample{privateListproducerList;}//=================packagecom.train.pojo;importlombok.extern.slf4j.Slf4j;importjava.util.ArrayList;importjava.util.List;@Slf4jpublicclassTestGeneric{publicstaticvoidmain(String[]args){//列表producerList2=newArrayList<>();//没有办法执行这样的语句//producerList2.add(1);ArrayListintegerList=newArrayList<>();integerList.add(1);integerList.add(3);integerList.add(5);NumberExamplenumberNumberExample=newNumberExample<>();//但是这样的分配是允许的numberNumberExample.setProducerList(integerList);//或者像这样直接赋值ListproducerList=integerList;log.info(producerList.toString());升og.info(numberNumberExample.getProducerList().toString());integerList.add(9);}}列表consumer是对面的包com.train.pojo;importlombok.Data;importjava.util.List;@DatapublicclassConsumerExample{privateListconsumerList;}//===========ConsumerExampleconsumer=newConsumerExample<>();ListconsumerList=consumer.getConsumerList();//consumer是producer的对立面,所以可以直接往里面塞东西consumerList.add(1);consumerList.add(0.3);//取如果不允许,只能是对象类型的对象object=consumerList.get(1);