一、泛型的基本概念
泛型的本质是参数化类型,就是说所操作的数据类型被指定为一个参数。泛型提供了编译时类型的安全检查机制,该机制允许程序在编译时检测非法的类型。在不使用泛型的情况下,可以引用Object类型来实现参数的任意化,但是在具体使用时需要进行强制类型转换,如果不知道参数具体的引用类型会引起类型转换错误,而这种错误只有在运行时才出现。使用泛型后就可以在编译期检测类型错误,提高了程序的安全性。下面看一个例子解释泛型的好处。
使用泛型前:
public static void main(String[] args) {
List a = new ArrayList();
a.add("name");
a.add(1);
a.add(BigDecimal.ONE);
BigDecimal bigDecimal = (BigDecimal) a.get(2);
bigDecimal.add(BigDecimal.ONE);
}
要想使用BigDecimal的方法,就必须对列表中的元素进行显示的强制类型转换。
使用泛型后:
public static void main(String[] args) {
List<BigDecimal> a = new ArrayList();
a.add("name");//产生编译错误
a.add(1);//产生编译错误
a.add(BigDecimal.ONE);
a.get(2).add(BigDecimal.ONE);
}
使用泛型后,规定在List中只能存放BigDecimal的元素,存放其他类型的元素会在编译时会产生错误,不需要显示的进行强制类型转换。
二、泛型标记和泛型限定
- E-Element 在集合中使用,表示在集合中存放的元素
- T-Type 表示Java类
- K-Key 表示键
- V-Value 表示值
- N-Number 表示数值类型
- ? 表示不确定的类型
对泛型的上限限定:<? extends T>,表示该通配符所代表的类型是T的子类或是T的实现类。
对泛型的下限限定:<? super T>,表示该通配符所代表的类型是T的父类或父接口。
三、类型擦除
在编码阶段使用泛型时加上的参数类型,会被编译器在编译时去掉,这个过程叫做类型擦除。
public static void main(String[] args) {
ArrayList<String> arrayString=new ArrayList<String>();
ArrayList<Integer> arrayInteger=new ArrayList<Integer>();
System.out.println(arrayString.getClass()==arrayInteger.getClass());
}
输出:
true
ArrayList和ArrayList被编译之后统一为List,编译器擦除了String和Integer的信息。
Java类型擦除的过程:
首先找到用来替换类型参数的具体类,然后把代码中的类型参数都替换为具体的类。
既然类型都被擦除了,那么泛型又是如何做到像ArrayList泛型类型,不允许向其中插入String对象的呢?
Pair<Integer> pair=new Pair<Integer> ();
pair.setValue(3);
Integer integer=pair.getValue();
System.out.println(integer);
擦除类型后getValue()将返回Object类型,编译器会对Object类型进行强制类型转换,转换成Integer类型。也就是说编译器会把getValue()方法翻译成两条字节码指令:
- 对原始方法Pair.getValue的调用,将返回Object类型。
- 将返回的Object类型强制转换为Integer。
四、泛型的使用
4.1 泛型方法
将方法的参数定义为泛型。
public static void main(String[] args) {
test(1, "a");
}
public static <T> void test(T... array) {
for (T e : array) {
if (e instanceof Integer) {
System.out.println("Integer");
} else if (e instanceof String) {
System.out.println("String");
}
}
}
4.2 泛型类
可以传入不同的参数类型实例化不同的对象。
public class General<T> {
private T t;
public T get() {
return t;
}
public void set(T t) {
this.t = t;
}
public static void main(String[] args) {
General<String> general = new General<>();
general.set("a");
General<Integer> general1 = new General<>();
general1.set(1);
}
}
4.3 泛型接口
public interface IGeneral<T> {
T get();
}
public class IGeneralImpl implements IGeneral<Integer> {
@Override
public Integer get() {
return 1;
}
public static void main(String[] args) {
new IGeneralImpl().get();
}
}
五、泛型常见的面试题
5.1 Java中的泛型是什么?有什么好处?
泛型是一种参数化类型的机制。它使得代码适用于各种类型,从而编写出更加通用的代码。泛型是一种编译时类型检查机制。
5.2 可以把List传递给一个接受List参数的方法吗?
不可以,这样会有编译错误。因为List可以存储任何类型的对象,而List只能存储String类型。
5.3 Java中List和原始类型List之间的区别?
最本质的区别就是,在编译时编译器不会对原始类型的做类型检查。可以把任何带参数的泛型类型传递给接受原始类型List的方法,但却不能把List传递给接受List的方法。
5.4 List<?>和List的区别?
List<?>是未知类型的List,List是任意类型的List。可以把List赋值给List<?>,却不能把List赋值给List。