前言
Java 泛型是 JDK 5 引入的一项重要特性,它为 Java 编程带来了更强大的类型安全和代码复用能力,下面将从概念、作用、实现原理、使用场景和限制等方面详细阐述对 Java 泛型的理解。
本文配套《Java面试宝典》已整理,免费领取地址:https://pan.quark.cn/s/38ac3e833887
概念
泛型即 “参数化类型”,就是将类型由原来的具体类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用 / 调用时传入具体的类型(类型实参)。例如ArrayList,其中E就是类型形参,在创建ArrayList对象时,如ArrayList,String就是类型实参。
作用
1. 类型安全
- 泛型的主要目标之一是增强程序的类型安全。在没有泛型之前,像ArrayList这样的集合可以存储任意类型的对象,这可能会导致在运行时出现ClassCastException。例如:
import java.util.ArrayList;
import java.util.List;
public class WithoutGeneric {
public static void main(String[] args) {
List list = new ArrayList();
list.add("Hello");
list.add(123);
String str = (String) list.get(1); // 运行时会抛出 ClassCastException
}
}
- 使用泛型后,可以明确指定集合中存储的元素类型,编译器会在编译时进行类型检查,避免了运行时的类型转换错误。
import java.util.ArrayList;
import java.util.List;
public class WithGeneric {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(123); // 编译时会报错
String str = list.get(0);
}
}
2. 代码复用
泛型允许编写通用的代码,这些代码可以处理多种不同类型的数据,而不需要为每种类型都编写重复的代码。例如,我们可以编写一个泛型方法来交换数组中两个元素的位置:
public class GenericMethod {
public static <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
public static void main(String[] args) {
Integer[] intArray = {
1, 2, 3};
swap(intArray, 0, 1);
String[] strArray = {
"Hello", "World"};
swap(strArray, 0, 1);
}
}
- 实现原理:类型擦除
Java 泛型是通过类型擦除来实现的。在编译过程中,编译器会将泛型类型信息擦除,只保留原始类型。例如,List和List在编译后都会被擦除为List。类型擦除的主要步骤如下: - 替换类型参数:将泛型类型中的类型参数替换为其边界类型(如果没有指定边界,则替换为Object)。
- 插入类型转换:在需要的地方插入类型转换代码,以保证类型安全。
- 生成桥接方法:在子类重写泛型方法时,为了保证多态性,编译器会生成桥接方法。
使用场景
1. 集合类
Java 中的集合框架(如ArrayList、HashMap等)广泛使用了泛型,使得集合可以存储特定类型的元素,提高了代码的安全性和可读性。
2. 泛型类和泛型方法
可以创建泛型类和泛型方法来处理不同类型的数据,实现代码的复用。例如,自定义一个泛型的栈类:
public class GenericStack<T> {
private T[] stack;
private int top;
public GenericStack(int capacity) {
stack = (T[]) new Object[capacity];
top = -1;
}
public void push(T item) {
stack[++top] = item;
}
public T pop() {
return stack[top--];
}
}
限制
1. 不能使用基本数据类型
- 泛型的类型参数只能是引用类型,不能是基本数据类型。例如,不能使用List,但可以使用List。
2. 运行时类型信息丢失
- 由于类型擦除,在运行时无法获取泛型的具体类型信息。例如,无法通过instanceof判断一个List是List还是List。
3. 不能创建泛型数组
- 虽然可以声明泛型数组,但不能直接创建泛型数组。例如,不能直接写T[] array = new T[10];,只能通过(T[]) new Object[10];的方式进行强制类型转换。