说到Java泛型,大家应该都不陌生。常用于限制Java各种容器的数据类型。它是Java JDK 1.5后增加的特性,所以已经是一个比较老的特性了。这里写这篇文章主要目的是为了给大家复习泛型的知识点。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
Java泛型的主要作用有:
至于泛型是否会带来性能问题,这个说法不一。但泛型是一个编译时的功能,增强代码可读性、复用性的收益远大于泛型进行类型转换的影响。
package com.hust.zhang.generic;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
public class GenericClass {
@Getter
private T item;
}
定义一个Java泛型类,泛型类中的成员变量的类型可以通过类对象实例化时进行约束,测试如下,
public class GenericTest {
public static void main(String[] args) {
GenericClass generic1 = new GenericClass<>("item");
GenericClass generic2 = new GenericClass<>(123L);
System.out.println(generic1.getItem().getClass().getName());//java.lang.String
System.out.println(generic2.getItem().getClass().getName());//java.lang.Long
}
}
Java泛型接口和泛型类基本类似,用过Mybatis或者Mybaits-plus的同学应该都知道使用代码自动生成工具生成的Mapper文件里BaseMapper就是一个泛型接口,把它作为示例。
package com.baomidou.mybatisplus.core.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Param;
public interface BaseMapper extends Mapper {
int insert(T entity);
int deleteById(Serializable id);
int deleteByMap(@Param("cm") Map columnMap);
int delete(@Param("ew") Wrapper wrapper);
int deleteBatchIds(@Param("coll") Collection extends Serializable> idList);
int updateById(@Param("et") T entity);
int update(@Param("et") T entity, @Param("ew") Wrapper updateWrapper);
T selectById(Serializable id);
List selectBatchIds(@Param("coll") Collection extends Serializable> idList);
List selectByMap(@Param("cm") Map columnMap);
T selectOne(@Param("ew") Wrapper queryWrapper);
Integer selectCount(@Param("ew") Wrapper queryWrapper);
List selectList(@Param("ew") Wrapper queryWrapper);
List
然后数据库表的接口继承BaseMapper,各个数据库表接口的实现继承ServiceImpl类就具备了增删改查的基本功能了。
/**
* IService 实现类( 泛型:M 是 mapper 对象,T 是实体 , PK 是主键泛型 )
*/
public class ServiceImpl, T> implements IService {
protected Log log = LogFactory.getLog(getClass());
@Autowired
protected M baseMapper;
@Override
public M getBaseMapper() {
return baseMapper;
}
//... 省略 ...
}
上面的栗子里其实包含了泛型类、泛型接口和泛型方法,下面再举一个单纯的泛型方法,直接打印泛型的数据类型。
private static void printGeneric(T t){
System.out.println(t.getClass().getName());
}
下面看一个实际应用,比如以太专线和以太专网的公共属性类为以太连接。
以太连接父类:
@Data
public class EthConnection {
private String attribute;
}
以太专线子类:
@Data
public class ELineConnection extends EthConnection{
private String uuid;
}
以太专网子类:
@Data
public class ELanConnection extends EthConnection{
private String uuid;
}
比如我想对以太专线和以太专网子类传入它们共有的属性,那么我得像下面这么做,
public class GenericDemo {
public static void main(String[] args) {
String attr = "commonAttribute";
List eLanConnectionList = buildELanConnection(attr);
List eLineConnectionList = buildELineConnection(attr);
eLanConnectionList.stream().forEach(System.out::println);
eLineConnectionList.stream().forEach(System.out::println);
}
private static List buildELanConnection(String attr) {
ELanConnection connection = new ELanConnection();
connection.setAttribute(attr);
return Lists.newArrayList(connection);
}
private static List buildELineConnection(String attr) {
ELineConnection connection = new ELineConnection();
connection.setAttribute(attr);
return Lists.newArrayList(connection);
}
}
这里共有属性不多,感觉没啥,但如果它们需要传入的共有属性很多且都相同,那么重复的代码就太多了。设置共同属性的代码段可以抽取出来,如果属性不多,我下面这种做法也许显得很多余。这样设计是否合适大家可以一起品一品。
public class GenericDemo {
private static final int ELAN_TAG = 1;
private static final int ELINE_TAG = 2;
public static void main(String[] args) {
String attr = "commonAttribute";
List eLanConnectionList = buildELanConnection(attr);
List eLineConnectionList = buildELineConnection(attr);
eLanConnectionList.stream().forEach(System.out::println);
eLineConnectionList.stream().forEach(System.out::println);
}
private static List buildELanConnection(String attr) {
return castELanConnection(buildConnection(attr, ELAN_TAG));
}
private static List buildELineConnection(String attr) {
return castELineConnection(buildConnection(attr, ELINE_TAG));
}
private static List castELanConnection(List extends EthConnection> connections) {
if (CollectionUtils.isEmpty(connections)){
return Lists.newArrayList();
}
return connections.stream().map(ELanConnection.class::cast).collect(Collectors.toList());
}
private static List castELineConnection(List extends EthConnection> connections) {
if (CollectionUtils.isEmpty(connections)){
return Lists.newArrayList();
}
return connections.stream().map(ELineConnection.class::cast).collect(Collectors.toList());
}
private static List extends EthConnection> buildConnection(String attr, int tag) {
if (tag == ELINE_TAG) {
ELineConnection connection = new ELineConnection();
addAttribute(attr, connection);
return Lists.newArrayList(connection);
} else if (tag == ELAN_TAG) {
ELanConnection connection = new ELanConnection();
addAttribute(attr, connection);
return Lists.newArrayList(connection);
}
return Lists.newArrayList();
}
private static void addAttribute(String attr, EthConnection connection) {
connection.setAttribute(attr);
}
}
《Effective Java中文版第2版》第五章提到的泛型使用原则:
1、请不要再新代码中使用原生态类型(raw type)——原生态类型即不带任何实际类型参数的泛型名称,例如与List
相对应的原生态类型是List。 2、消除非受检警告——在可以证明引起警告的代码是类型安全的时候可以使用@SuppressWarnings("unchecked")注解来禁止这条警告。
3、列表优先于数组——数组与泛型相比,数组是协变的(covariant),而泛型是不可变的(invariant)。
4、优先考虑泛型——考虑泛型的同时需要保证代码安全且解除编译警告。
5、优先考虑泛型方法——静态工具方法尤其适合于泛型化。
6、利用有限制通配符(bounded wildcard type)来提升API的灵活性
7、优先考虑类型安全的异构容器
上面的代码就是利用第6点原则使用有限制的通配符来提升API的灵活性,如果不知道该使用哪种通配符,则记住助记符PECS(producer-extends,consumer-super),即如果参数化类型表示一个T生产者,就使用 extends T>;如果它表示一个T消费者,就使用 super T>。
其实上面的例子实现的并不好,其实看官方的API中如何使用有限制通配符的更好,如java.util.Vector实现的addAll方法入参使用了 extends E>,forEach、removeIf、sort、tryAdvance、forEachRemaining方法使用了 super E>
c++模板和Java泛型类似,它叫模板。以一或多个模版形参参数化,形参有以下三种:
模板定义的实体有:类模板、函数模板、别名模板(C++11起)、变量模板(C++14起)
详细内容参看文末参考链接。
参考链接:
1、Java 泛型 | 菜鸟教程
2、模板 - cppreference.com