在Java编程中,泛型(Generics)是一个强大的特性,它允许程序员在编写类、接口和方法时定义类型参数(type parameters)。这些类型参数在类、接口或方法被实例化时(例如,创建对象或使用方法时)被具体的类型所替换。通过泛型,我们可以创建可重用的组件,这些组件可以处理多种数据类型,同时保证了类型安全并减少了类型转换的错误。
一、核心知识点
1. 泛型类
泛型类就是具有一个或多个类型参数的类。类型参数被尖括号(< >)括起来,并放在类名的后面。例如:
public class Box<T> {
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; }
}
在上面的代码中,T 是一个类型参数,它可以在创建 Box 对象时指定为任何类型。
2. 泛型接口
与泛型类类似,泛型接口也包含类型参数。例如:
public interface List<E> {
void add(E e);
E get(int index);
// ... 其他方法 ...
}
在这个例子中,List 是一个泛型接口,它有一个类型参数 E。
3. 泛型方法
泛型方法是指可以应用于多种数据类型的方法。类型参数放在方法签名的返回类型之前(在修饰符和返回类型之间)。例如:
public static <T> void printArray(T[] array) {
for (T item : array) {
System.out.print(item + " ");
}
System.out.println();
}
在这个例子中,printArray 是一个泛型方法,它可以接受任何类型的数组作为参数。
4. 类型通配符
类型通配符(?)表示未知的类型。它主要有两种形式:上界通配符(? extends Type)和下界通配符(? super Type)。例如:
List<? extends Number> numbers; // 可以存放Number及其子类对象
List<? super Integer> integers; // 可以存放Integer及其父类对象
5. 泛型擦除
由于Java的泛型是编译时类型检查机制,所以在运行时所有的泛型信息都会被擦除(Type Erasure)。这意味着在运行时,Java虚拟机(JVM)并不知道泛型参数的具体类型。
应用实例
示例1:泛型类
public class Main {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<>();
integerBox.set(10);
System.out.println(integerBox.get()); // 输出: 10
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
System.out.println(stringBox.get()); // 输出: Hello
}
}
示例2:泛型接口
假设我们有一个简单的泛型接口实现:
public class ArrayList<E> implements List<E> {
// 实现List接口的方法...
}
// 使用ArrayList
List<String> stringList = new ArrayList<>();
stringList.add("Apple");
stringList.add("Banana");
System.out.println(stringList.get(0)); // 输出: Apple
示例3:泛型方法
public class Main {
public static void main(String[] args) {
Integer[] intArray = {1, 2, 3, 4, 5};
printArray(intArray); // 输出整数数组
String[] stringArray = {"Hello", "World"};
printArray(stringArray); // 输出字符串数组
}
}
6. 泛型与原始类型
在Java中,如果泛型类没有指定类型参数,那么它会被视为原始类型(Raw Type)。使用原始类型通常是不安全的,因为它会绕过泛型提供的类型检查。然而,在Java 5之前编写的代码中,可能仍然存在原始类型的使用。为了兼容旧代码,Java允许将泛型与原始类型混合使用,但应尽量避免。
7. 泛型与静态上下文中的类型推断
Java 7引入了菱形操作符(diamond operator,<>),允许在实例化泛型类时省略类型参数,编译器会自动推断出类型参数。这在静态上下文中特别有用,例如创建集合时:
List<String> list = new ArrayList<>(); // 使用菱形操作符进行类型推断
8. 泛型与数组
在Java中,创建泛型数组是不允许的,因为泛型类型在运行时会被擦除,这可能导致类型不安全问题。但是,你可以创建数组的泛型引用,但这通常是不推荐的,因为这会绕过泛型提供的类型安全保证。
9. 泛型与反射
由于泛型类型在运行时会被擦除,因此使用反射来操作泛型类型时需要特别小心。反射API通常与原始类型一起使用,而不是泛型类型。如果需要通过反射来操作泛型类型,可能需要借助一些额外的机制(如类型令牌,Type Token)来保留泛型类型信息。
10. 泛型与泛型边界
泛型边界允许我们限制泛型参数的类型。例如,我们可以创建一个泛型类,其类型参数必须是某个特定类的子类或实现了某个特定接口。这在设计API时特别有用,因为它允许我们限制可以传递给泛型参数的类型。