本文首发于个人网站:Java泛型的局限和使用经验

这篇文章主要总结泛型的一些局限和实际的使用经验

泛型的局限

  1. 任何基本类型不能作为类型参数
    经过类型擦除后,List中包含的实际上还是Object的域,而在Java类型系统中Object和基本类型是两套体系,需要通过“自动装包、拆包机制”来进行交互。

    1. public class ListOfInt {
    2. public static void main(String[] args) {
    3. //(1)通过自动装包和拆包,在泛型中和基本类型进行交互
    4. List<Integer> li = new ArrayList<>();
    5. for (int i = 0; i < 5; i++) {
    6. //发生自动装包
    7. li.add(i);
    8. }
    9. //发生自动拆包
    10. for (int i: li) {
    11. System.out.println(i);
    12. }
    13. //(2)错误示例
    14. List<int> list = new ArrayList<>();
    15. }
    16. }


  1. 任何在运行时需要知道确切类型信息的操作都无法工作。
    由于Java的泛型是编译期泛型(在进入运行时后没有泛型的概念),因此运行时的类型转换和类型判定等操作都没有效果。

    1. public class Erased<T> {
    2. public void f(Object[] args) {
    3. //(1)不能在类型参数上使用instanceof操作, instanceof右边必须是某个原生类型或数组
    4. if (args instanceof T) {}
    5. //(2)不能直接实例化类型参数
    6. T var = new T();
    7. //(3)不能这么定义泛型数组,原因同上
    8. T[] array = new T[100];
    9. //(4)先定义一个Object数组,再强转成T[]数组,绕过泛型检查,但是会收到一个告警
    10. T[] array2 = (T[])new Object[100];
    11. }
    12. }


  1. 冲突1:方法名一样,参数列表是同一个类型参数的两个泛型方法,重载将产生相同的函数签名; ```java package org.java.learn.generics;

import java.util.List;

public class UserList { void f(List v) {} void f(List v) {} }

  1. <br />从图中的提示可以看出,在泛型擦除后,这两个方法签名完全相同,产生冲突;
  2. <br />![](http://upload-images.jianshu.io/upload_images/44770-89cb1e9ec3792192.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=%E6%B3%9B%E5%9E%8B%E5%AF%BC%E8%87%B4%E7%9A%84%E9%87%8D%E8%BD%BD%E5%86%B2%E7%AA%81)
  3. 4.
  4. 冲突2:使用泛型接口时,需要避免重复实现同一个接口
  5. ```java
  6. interface Payable<T> {}
  7. class Employee implements Payable<Employee> {}
  8. class Hourly extends Employee implements Payable<Hourly> {}


IDEA编辑器给出如下所示——“Payable不能被不同的类型参数继承,即不能重复实现同一个接口”
Java泛型基础(三):泛型的局限和使用经验 - 图1

  1. 不能在静态域或方法中引用类型参数 ```java public class Erased {

    public static void f(Object[] args) {

    1. //不能在静态域中引用类型参数
    2. if (args instanceof T) {}
    T var = new T();


    T[] array = new T[100];


    T[] array2 = (T[])new Object[100];
}

}


<br />这个例子跟问题2基本相同,唯一是在方法的签名里多了一个_static_关键字,然后引发编译错误的原因就变成了:在静态域中无法引用类型变量(入下图所示)。
<br />![](http://upload-images.jianshu.io/upload_images/44770-5a80ed2c5d1bec14.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=2017-12-0920.31.09.png)


<a name="46f27344"></a>
# 泛型的常用经验

1. 
尽量消除异常,初学者容易写出使用原生类型的代码,或者使用泛型不当的代码,现在编辑器非常先进,尽量消除提示的异常;对于开发者自己确认不需要消除切可以工作的代码,可以使用`@SuppressWarnings("unchecked")`屏蔽掉异常;

2. 
能用泛型类(或接口)的时候尽量使用;能用泛型方法的时候尽量使用泛型方法;

3. 
定义API时,尽量使用泛型;
```java
public class PlainResult<T> extends BaseResult {
    private static final long serialVersionUID = -xxxxxx;

    private T data;

    public PlainResult() {
    }

    public T getData() {
        return this.data;
    }

    public void setData(T data) {
        this.data = data;
    }
}


  1. 编写基础工具类时,尽量使用泛型;

    • 例子1:通用的返回值对象 ```java //使用泛型类 @Data @Builder @AllArgsConstructor @NoArgsConstructor public class DataListPageInfo { int page; int pageSize; int totalCount; List items = new ArrayList<>();

}


   - 例子2:缓存操作工具类,这里使用了
```java
@Component
public class RedisTemplateService {

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisTemplateService.class);

    @Resource
    private RedisTemplate redisTemplate;

    private String getKey(String key, Object... params) {
        if (params == null || params.length <= 0) {
            return key;
        }

        return String.format(key, params);
    }

      //这里使用了泛型方法
      public <T> T getFromJsonCache(Class<T> type, String keyPrefix, Object... params) {
        try {
            String ret = getString(keyPrefix, params);
            return JSON.parseObject(ret, type);
        } catch (Exception e) {
            LOGGER.error("json parse error, type={},keyPrefix={},params={}", type, keyPrefix, params, e);
        }

        return null;
    }

     ……   
}

参考资料

  1. 《Java编程思想》
  2. 《Java核心技术》
  3. 《Effective Java》

本号专注于后端技术、JVM问题排查和优化、Java面试题、个人成长和自我管理等主题,为读者提供一线开发者的工作和成长经验,期待你能在这里有所收获。
Java泛型基础(三):泛型的局限和使用经验 - 图2