Java-泛型总结
Java-泛型&通配符总结
- 泛型
- 什么是泛型?有什么作用?
- 泛型的使用方式有哪几种?
- 项目中哪里用到了泛型?
- 什么是泛型擦除机制?为什么要擦除?
- 什么是桥方法?
- 泛型有哪些限制?为什么?
- 以下代码是否能编译,为什么?
泛型
什么是泛型?有什么作用?
Java 泛型(Generics) 是 JDK 5 中引入的一个新特性。使用泛型参数,可以增强代码的可读性以及稳定性。
编译器可以对泛型参数进行检测,并且通过泛型参数可以指定传入的对象类型。比如 ArrayList<Persion> persons = new ArrayList<Persion>()
这行代码就指明了该 ArrayList
对象只能传入 Persion
对象,如果传入其他类型的对象就会报错。
ArrayList<E> extends AbstractList<E>
并且,原生 List
返回类型是 Object
,需要手动转换类型才能使用,使用泛型后编译器自动转换。
泛型的使用方式有哪几种?
泛型一般有三种使用方式:泛型类、泛型接口、泛型方法。
- 泛型类
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Test<T>{
private T key;
public Test(T key) {
this.key = key;
}
public T getKey(){
return key;
}
}
如何实例化泛型类:
Test<Integer> testInteger = new Test<Integer>(123456);
- 泛型接口
public interface Test<T> {
public T method();
}
实现泛型接口,不指定类型:
class TestImpl<T> implements Test<T>{
@Override
public T method() {
return null;
}
}
实现泛型接口,指定类型:
class TestImpl<T> implements Test<String>{
@Override
public String method() {
return "hello";
}
}
- 泛型方法
public static < E > void printArray( E[] inputArray )
{
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
System.out.println();
}
使用:
// 创建不同类型数组: Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3 };
String[] stringArray = { "Hello", "World" };
printArray( intArray );
printArray( stringArray );
项目中哪里用到了泛型?
- 自定义接口通用返回结果
CommonResult<T>
通过参数T
可根据具体的返回类型动态指定结果的数据类型 - 定义
Excel
处理类ExcelUtil<T>
用于动态指定Excel
导出的数据类型 - 构建集合工具类(参考
Collections
中的sort
,binarySearch
方法)。 - …
什么是泛型擦除机制?为什么要擦除?
Java 的泛型是伪泛型,这是因为 Java 在编译期间,所有的泛型信息都会被擦掉,这也就是通常所说类型擦除 。
编译器会在编译期间,会动态地将泛型 T
擦除为 Object
或将 T extends xxx
擦除为其限定类型 xxx
。
因此,泛型本质上其实还是编译器的行为,为了保证引入泛型机制但不创建新的类型,减少虚拟机的运行开销,编译器通过擦除将泛型类转化为一般类。
举个例子:
List<Integer> list = new ArrayList<>();
list.add(12);
//1.编译期间直接添加会报错
list.add("a");
Class<? extends List> clazz = list.getClass();
Method add = clazz.getDeclaredMethod("add", Object.class);
//2.运行期间通过反射添加,是可以的
add.invoke(list, "kl");
System.out.println(list)
再来举一个例子 : 由于泛型擦除的问题,下面的方法重载会报错。
public void print(List<String> list) { }
public void print(List<Integer> list) { }
原因也很简单,泛型擦除之后,List<String>
与 List<Integer>
在编译以后都变成了 List
。
既然编译器要把泛型擦除,那为什么还要用泛型呢?用 Object 代替不行吗?
这个问题其实在变相考察泛型的作用:
- 使用泛型可在编译期间进行类型检测。
- 使用
Object
类型需要手动添加强制类型转换,降低代码可读性,提高出错概率。 - 泛型可以使用自限定类型如
T extends Comparable
。
什么是桥方法?
桥方法(Bridge Method
) 用于继承泛型类时保证多态。
class Node<T> {
public T data;
public Node(T data) { this.data = data; }
public void setData(T data) {
System.out.println("Node.setData");
this.data = data;
}
}
class MyNode extends Node<Integer> {
public MyNode(Integer data) { super(data); }
// Node<T> 泛型擦除后为 setData(Object data),
//而子类 MyNode 中并没有重写该方法,所以编译器会加入该桥方法保证多态
public void setData(Object data) {
setData((Integer) data);
}
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
⚠️注意 :桥方法为编译器自动生成,非手写。
泛型有哪些限制?为什么?
泛型的限制一般是由泛型擦除机制导致的。擦除为 Object
后无法进行类型判断
- 只能声明不能实例化
T
类型变量。 - 泛型参数不能是基本类型。因为基本类型不是
Object
子类,应该用基本类型对应的引用类型代替。 - 不能实例化泛型参数的数组。擦除后为
Object
后无法进行类型判断。 - 不能实例化泛型数组。
- 泛型无法使用
Instance of
和getClass()
进行类型判断。 - 不能实现两个不同泛型参数的同一接口,擦除后多个父类的桥方法将冲突
- 不能使用
static
修饰泛型变量 - …
以下代码是否能编译,为什么?
public final class Algorithm {
public static <T> T max(T x, T y) {
return x > y ? x : y;
}
}
无法编译,因为 x 和 y 都会被擦除为 Object
类型, Object
无法使用 >
进行比较
public class Singleton<T> {
public static T getInstance() {
if (instance == null)
instance = new Singleton<T>();
return instance;
}
private static T instance = null;
}
无法编译,因为不能使用 static
修饰泛型 T
。