泛型
Java5之后加入了泛型,它让数据类型变得参数化,这可能使开发者编写出更加通用的代码模块进行复用。Java中的Collection体系就使用泛型进行了重构。
示例
- 没有泛型之前,Java这么写会在运行时报错
List list = new LinkedList();
list.add("words");
list.add(1);
list.forEach(l-> System.out.println((String)l));
- 为此,Java提供了泛型,你可以在创建对象的时候使用
<>
来指定类型参数。拥有泛型之后,我们可以指定List的数据类型,一来可以提升了代码的可读性,让人一看就知道这个数组列表中包含的是什么对象,二来可以让代码在编译器就检查出参数不合法。
泛型类
@Data
public class RestResult<T> {
private Integer code;
private String msg;
private T data;
}
泛型方法
/**
* 给定一个数组,返回数组中的最小值
*
* @param array
* @param <T> 传入的T对象必须实现Comparable接口
* @return
*/
private static <T extends Comparable> T min(T[] array) {
if (array == null && array.length == 0) {
return null;
}
T smallest = array[0];
for (T element : array) {
if (smallest.compareTo(element) > 0) {
smallest = element;
}
}
return smallest;
}
编写一个实例化的泛型方法
- Java8
/**
* 泛型方法,给定一个Supplier函数式接口,返回具体的类型
* @param constr
* @param <T>
* @return
*/
private static <T> T getInstance(Supplier<T> constr) {
return constr.get();
}
- 反射
/**
* 传统方法解决泛型实例化,反射
* @param clazz 注意,Class类本身是泛型,例如String.class是Class<String>的实例
* @param <T>
* @return
*/
private static <T> T getInstanceByClass(Class<T> clazz) {
try {
return clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
// 演示使用,非正确用法
throw new RuntimeException("An exception occurred when using newInstance");
}
}
泛型擦除
历史原因
虚拟机没有泛型类型对象-所有对象都是属于普通的类。所以定义泛型类型,最终都会被解析成原始类型。举个例子: List<String> -> List
泛型只在编译期检验参数是否合法,在程序运行时,原本的泛型信息就会被"擦除"了。只会在对象进入和离开泛型的临界点添加类型检查。也就是说,当你调用泛型方法时,JVM会帮你自动强转。
RestResult<String> stringRestResult = new RestResult<>();
RestResult<Integer> integerRestResult = new RestResult<>();
System.out.println("stringRestResult实例在运行时的信息:"+stringRestResult.getClass());
System.out.println("integerRestResult实例在运行时的信息:"+integerRestResult.getClass());
程序输出:
stringRestResult实例在运行时的信息:class RestResult
integerRestResult实例在运行时的信息:class RestResult
可见,String和Integer都在运行时消失了。
我们通过编译来验证这个问题
public class Generics {
public Generics() {
}
public static void main(String[] var0) {
RestResult var1 = new RestResult();
RestResult var2 = new RestResult();
System.out.println("stringRestResult in Running:" + var1.getClass());
System.out.println("integerRestResult in Running:" + var2.getClass());
}
}
泛型类型的继承规则
我们以一个例子来展示这个规则:
/**
* 展示类型继承规则
* @param restResult
*/
private static void printResultData(RestResult<Integer> restResult) {
Integer data = restResult.getData();
System.out.println("The RestResult is :" + data);
}
public static void main(String[] args) {
RestResult<Number> integerRestResult = new RestResult<>();
integerRestResult.setData(Integer.valueOf("10086"));
// 无法通过编译
printResultData(integerRestResult);
}
泛型通配符
- 无限定通配符: ?
- 错误的应用场景
/**
* 使用无限定通配符来传参,但是会有类型转换的风险
* @param restResult
*/
private static void printResultDataByWildcard(RestResult<?> restResult) {
Integer data = ((Integer) restResult.getData());
System.out.println("The RestResult is :" + data);
}
public static void main(String[] args) {
RestResult<String> stringRestResult = new RestResult<>();
stringRestResult.setData("10086");
// 报错,因为传递了一个String,而方法内部进行了强转
printResultDataByWildcard(stringRestResult);
}
- 正确的应用场景
@Data
public class RestResult<T> {
private Integer code;
private String msg;
private T data;
public static boolean resultDataIsNull(RestResult<?> restResult) {
return Objects.isNull(restResult.getCode()) || Objects.isNull(restResult.getMsg()) || Objects.isNull(restResult.getData());
}
}
public static void main(String[] args) {
RestResult<String> testNullRestResult = new RestResult<>();
testNullRestResult.setData(null);
// 简单的判断是否包含空引用
boolean b = testNullRestResult.resultDataIsNull(testNullRestResult);
System.out.println(b);
}
- 上边界通配符: extends
- 下边界通配符: super
/**
* 限制下边界为Integer
*
* @param restResult
*/
private static void printResult(RestResult<? super Integer> restResult) {
Integer data = ((Integer) restResult.getData());
System.out.println("The RestResult is :" + data);
}
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
总结
- 定义泛型时,对应的数据类型是不确定的
- 泛型方法被调用时,会指定具体类型
- 编译时编译器会对泛型进行安全检查
- 不能使用基础类型实例化类型参数,例如:List<int>
- 运行时泛型会被"擦除"
- 不要创建参数化类型的数组: List<String>[] table = new ArrayList<>[10]
- 可以使用
@SafeVarargs
标注方法来抑制警告 - 使用
@SuppressWarnings("unchecked")
可以消除对受检查异常的检查 - 泛型无法使用new T() , new T[] 或者 T.class 这样的表达式,因为最终会擦除为Object.class,Java的本意是不希望调用new Object();
- try catch中无法捕获泛型类的实例。例如: catch(T e)