泛型(Generic)

序言

说到Java泛型,大家应该都不陌生。常用于限制Java各种容器的数据类型。它是Java JDK 1.5后增加的特性,所以已经是一个比较老的特性了。这里写这篇文章主要目的是为了给大家复习泛型的知识点。

Java泛型(Generic)

介绍

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在接口方法的创建中,分别称为泛型类、泛型接口、泛型方法。

作用

Java泛型的主要作用有:

  1. 类型安全:编译时强类型检查,编译器可以直接验证类型假设。
  2. 消除强制类型转换:消除代码中的许多强制类型转换,使得代码可读性更高,减少出错机会。
  3. 提高代码复用性:泛型可以避免一些有相同特性且重复的代码。

至于泛型是否会带来性能问题,这个说法不一。但泛型是一个编译时的功能,增强代码可读性、复用性的收益远大于泛型进行类型转换的影响。

应用场景

泛型类

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 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 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> selectMaps(@Param("ew") Wrapper queryWrapper);

    List selectObjs(@Param("ew") Wrapper queryWrapper);

    IPage selectPage(IPage page, @Param("ew") Wrapper queryWrapper);

    IPage> selectMapsPage(IPage page, @Param("ew") Wrapper queryWrapper);
} 
  

然后数据库表的接口继承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 connections) {
        if (CollectionUtils.isEmpty(connections)){
            return Lists.newArrayList();
        }
        return connections.stream().map(ELanConnection.class::cast).collect(Collectors.toList());
    }

    private static List castELineConnection(List connections) {
        if (CollectionUtils.isEmpty(connections)){
            return Lists.newArrayList();
        }
        return connections.stream().map(ELineConnection.class::cast).collect(Collectors.toList());
    }
    
    private static List 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生产者,就使用;如果它表示一个T消费者,就使用

其实上面的例子实现的并不好,其实看官方的API中如何使用有限制通配符的更好,如java.util.Vector实现的addAll方法入参使用了,forEach、removeIf、sort、tryAdvance、forEachRemaining方法使用了

C++模板(Template)

c++模板和Java泛型类似,它叫模板。以一或多个模版形参参数化,形参有以下三种:

  1. 类型模板形参。
  2. 非类型模板形参。
  3. 模板模板形参。

模板定义的实体有:类模板、函数模板、别名模板(C++11起)、变量模板(C++14起)

详细内容参看文末参考链接。

参考链接:

1、Java 泛型 | 菜鸟教程

2、模板 - cppreference.com

你可能感兴趣的:(笔记,java)