这篇文章主要总结泛型的一些局限和实际的使用经验
泛型的局限
任何基本类型不能作为类型参数
经过类型擦除后,List
中包含的实际上还是Object的域,而在Java类型系统中Object和基本类型是两套体系,需要通过“自动装包、拆包机制”来进行交互。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public class ListOfInt {
public static void main(String[] args) {
//(1)通过自动装包和拆包,在泛型中和基本类型进行交互
List<Integer> li = new ArrayList<>();
for (int i = 0; i < 5; i++) {
//发生自动装包
li.add(i);
}
//发生自动拆包
for (int i: li) {
System.out.println(i);
}
//(2)错误示例
List<int> list = new ArrayList<>();
}
}
任何在运行时需要知道确切类型信息的操作都无法工作。
由于Java的泛型是编译期泛型(在进入运行时后没有泛型的概念),因此运行时的类型转换和类型判定等操作都没有效果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class Erased<T> {
public void f(Object[] args) {
//(1)不能在类型参数上使用instanceof操作, instanceof右边必须是某个原生类型或数组
if (args instanceof T) {}
//(2)不能直接实例化类型参数
T var = new T();
//(3)不能这么定义泛型数组,原因同上
T[] array = new T[100];
//(4)先定义一个Object数组,再强转成T[]数组,绕过泛型检查,但是会收到一个告警
T[] array2 = (T[])new Object[100];
}
}
冲突1:方法名一样,参数列表是同一个类型参数的两个泛型方法,重载将产生相同的函数签名;
1
2
3
4
5
6
7
8package org.java.learn.generics;
import java.util.List;
public class UserList<W, T> {
void f(List<T> v) {}
void f(List<W> v) {}
}从图中的提示可以看出,在泛型擦除后,这两个方法签名完全相同,产生冲突;
冲突2:使用泛型接口时,需要避免重复实现同一个接口
1
2
3
4
5interface Payable<T> {}
class Employee implements Payable<Employee> {}
class Hourly extends Employee implements Payable<Hourly> {}IDEA编辑器给出如下所示——“Payable不能被不同的类型参数继承,即不能重复实现同一个接口”
不能在静态域或方法中引用类型参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class Erased<T> {
public static void f(Object[] args) {
//不能在静态域中引用类型参数
if (args instanceof T) {}
T var = new T();
T[] array = new T[100];
T[] array2 = (T[])new Object[100];
}
}这个例子跟问题2基本相同,唯一是在方法的签名里多了一个static关键字,然后引发编译错误的原因就变成了:在静态域中无法引用类型变量(入下图所示)。
泛型的常用经验
尽量消除异常,初学者容易写出使用原生类型的代码,或者使用泛型不当的代码,现在编辑器非常先进,尽量消除提示的异常;对于开发者自己确认不需要消除切可以工作的代码,可以使用
@SuppressWarnings("unchecked")
屏蔽掉异常;能用泛型类(或接口)的时候尽量使用;能用泛型方法的时候尽量使用泛型方法;
定义API时,尽量使用泛型;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public 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
2
3
4
5
6
7
8
9
10
11
12//使用泛型类
public class DataListPageInfo<T> {
int page;
int pageSize;
int totalCount;
List<T> items = new ArrayList<>();
}- 例子2:缓存操作工具类,这里使用了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class RedisTemplateService {
private static final Logger LOGGER = LoggerFactory.getLogger(RedisTemplateService.class);
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;
}
……
}
参考资料
- 《Java编程思想》
- 《Java核心技术》
- 《Effective Java》