Java 泛型是自 Java 5 引入的一项关键特性,旨在提升代码的类型安全性和可复用性。泛型通过引入参数化类型,允许类、接口和方法操作具体类型的数据,而无需在定义时指定具体类型。本文将深入探讨 Java 泛型的基本概念、类型参数与界定、通配符的使用、泛型的限制与继承、类型擦除、桥方法、泛型数组的创建、反射中的泛型处理,以及泛型的高级用法。通过全面了解和掌握这些知识点,开发者可以编写出更加通用、灵活和安全的 Java 代码。
泛型类允许在类定义中使用参数化类型。这意味着类可以操作某种特定类型的数据,而在实例化类时才指定这种类型。
public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
使用泛型类时,具体的类型参数在实例化时确定:
Box<Integer> integerBox = new Box<>();
integerBox.set(10);
Integer value = integerBox.get();
类似于泛型类,泛型接口允许在接口定义中使用参数化类型。
public interface Container<T> {
void add(T item);
T get(int index);
}
实现泛型接口:
public class Box<T> implements Container<T> {
private List<T> items = new ArrayList<>();
@Override
public void add(T item) {
items.add(item);
}
@Override
public T get(int index) {
return items.get(index);
}
}
泛型方法使得方法可以独立于类而操作某种特定类型的数据。泛型方法的类型参数放在方法的返回类型之前。
public class Util {
public static <T> void printArray(T[] inputArray) {
for (T element : inputArray) {
System.out.printf("%s ", element);
}
System.out.println();
}
}
调用泛型方法:
Integer[] intArray = {1, 2, 3, 4, 5};
Util.printArray(intArray);
类型参数是泛型中的占位符,通常用单个大写字母表示:
类型参数可以被限定,使得它只能是某个类或接口的子类或实现类。
public class Box<T extends Number> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
这样,Box 类的实例只能接受 Number 类型及其子类作为类型参数。
多重界定
类型参数可以有多个边界,使用 & 符号进行分隔。
public class Box<T extends Number & Comparable<T>> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
通配符用于泛型中的未知类型。
无界通配符 > 表示未知类型,通常用于只读访问的场景。
public void printList(List<?> list) {
for (Object element : list) {
System.out.println(element);
}
}
调用方法:
List<String> stringList = Arrays.asList("one", "two", "three");
printList(stringList);
上界通配符 extends T> 表示类型是 T 或 T 的子类,通常用于读取的场景。
public void process(List<? extends Number> list) {
for (Number number : list) {
System.out.println(number.doubleValue());
}
}
调用方法:
List<Integer> integerList = Arrays.asList(1, 2, 3);
process(integerList);
下界通配符 super T> 表示类型是 T 或 T 的超类,通常用于写入的场景。
public void addNumbers(List<? super Integer> list) {
list.add(1);
list.add(2);
}
调用方法:
List<Number> numberList = new ArrayList<>();
addNumbers(numberList);
T[] array = (T[]) new Object[10];
泛型支持继承,但需要注意类型兼容性。
class Parent<T> { }
class Child<T> extends Parent<T> { }
Parent<String> parent = new Parent<>();
Child<String> child = new Child<>();
但是,不能将 Parent 赋值给 Parent,因为泛型是不协变的。
泛型的协变和逆变
虽然泛型类型参数是不协变的,但是可以使用通配符来实现协变和逆变:
List<? extends Number> covariantList = new ArrayList<Integer>(); // 协变
List<? super Integer> contravariantList = new ArrayList<Number>(); // 逆变
Java 的泛型在编译时会进行类型擦除,以保证与旧版本的 Java 兼容。类型擦除后,所有泛型信息都会被移除,替换为其限定的类型或 Object。
public class Box<T> {
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; }
}
类型擦除后相当于:
public class Box {
private Object t;
public void set(Object t) { this.t = t; }
public Object get() { return t; }
}
在类型擦除过程中,为了保证类型安全,编译器会生成桥方法。例如:
class Parent<T> {
public T method(T t) { return t; }
}
class Child extends Parent<String> {
public String method(String t) { return t; }
}
生成桥方法后:
class Child extends Parent<String> {
public String method(String t) { return t; }
public Object method(Object t) { return method((String) t); } // 桥方法
}
由于类型擦除的限制,Java 不支持直接创建泛型数组。可以通过创建 Object 数组并进行类型转换来间接实现:
T[] array = (T[]) new Object[10];
泛型被广泛应用于 Java 的集合框架、定义自定义泛型类和方法、类型参数界定等场景。
在使用反射时,泛型类型会被擦除为原始类型,无法直接获取泛型类型的信息。但可以通过反射获取泛型的实际类型参数。
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public class GenericTypeDemo<T> {
public static void main(String[] args) {
GenericTypeDemo<String> demo = new GenericTypeDemo<>();
Type type = demo.getClass().getGenericSuperclass();
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
}
类型擦除会导致一些潜在的问题,例如类型转换异常和类型安全问题。
public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
public static void main(String[] args) {
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
Box rawBox = stringBox; // 允许的,但是会有类型安全问题
rawBox.set(10); // 运行时异常:ClassCastException
}
}
泛型类型可以嵌套使用,例如 Map
Map<String, List<Integer>> map = new HashMap<>();
Java 7 引入了菱形操作符 <>,使得编译器可以推断泛型的类型参数,从而减少冗长的代码。
List<String> list = new ArrayList<>();
Java 8 引入了更强大的类型推导机制,允许在方法调用中推导出类型参数。
public static <T> List<T> emptyList() {
return new ArrayList<T>();
}
List<String> list = emptyList();
Java 泛型极大地提高了代码的类型安全性和可复用性,使得代码更加通用和灵活。理解并掌握泛型的使用对于编写高质量的 Java 代码至关重要。通过深入了解泛型的各种特性和限制,可以更好地利用泛型来编写类型安全、灵活和高效的代码。