1.什么是泛型
泛型(Generics)是 Java 提供的一种特性,使得类、接口、方法可以操作各种类型的对象,而不必在编写时指定具体的类型。泛型允许我们在定义时使用类型参数,并在实际使用时为这些参数提供具体的类型。泛型的主要目的是提高代码的通用性、类型安全性和可重用性。
泛型的基本概念:
- 类型参数:泛型使用的占位符,比如
T
、E
、K
、V
等,表示数据类型,具体类型在使用时指定。 - 编译期检查:泛型提供了编译期类型检查,避免了类型转换异常。
通过泛型,你可以在编译期就发现类型错误,而不必等到运行时。例如,一个 List<String>
会确保该列表只包含字符串。
List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(123); // 编译时会报错,因为类型不匹配
2.如何使用泛型
泛型的使用分为几类:泛型类、泛型方法、泛型接口等。
泛型类
使用泛型类可以使类在定义时不指定具体的类型,而在使用时传递实际的类型。
class Box<T> { private T value; public void set(T value) { this.value = value;
} public T get() { return value; }
}
示例:
Box<Integer> intBox = new Box<>();
intBox.set(123);
System.out.println("Integer: " + intBox.get()); // 输出:Integer: 123Box<String> strBox = new Box<>();
strBox.set("Hello Generics");
System.out.println("String: " + strBox.get()); // 输出:String: Hello Generics
泛型方法
泛型方法是在方法定义中使用类型参数,使得方法可以操作多种类型。
public static <T> void printArray(T[] array) {for (T element : array) {System.out.print(element + " ");}System.out.println();
}Integer[] intArray = {1, 2, 3, 4, 5};
String[] strArray = {"A", "B", "C"};printArray(intArray); // 输出:1 2 3 4 5
printArray(strArray); // 输出:A B C
3.编写泛型
编写泛型类、泛型方法或泛型接口时,首先要确定所操作的类型是未知的,在编写代码时你可以定义类型参数并在使用时指定实际类型。
泛型类
class Pair<K, V> {private K key;private V value;public Pair(K key, V value) {this.key = key;this.value = value;}public K getKey() {return key;}public V getValue() {return value;}
}
使用:
Pair<String, Integer> pair = new Pair<>("Age", 30);
System.out.println("Key: " + pair.getKey() + ", Value: " + pair.getValue());
泛型方法
编写泛型方法时,类型参数需在返回类型前声明。
public static <T> T getFirst(T[] array) {if (array == null || array.length == 0) {return null;}return array[0];
}String[] names = {"Alice", "Bob", "Charlie"};
System.out.println(getFirst(names)); // 输出:Alice
泛型接口
泛型接口定义在接口中使用类型参数,并在实现类中指定具体类型。
interface Container<T> {void set(T value);T get();
}class ContainerImpl<T> implements Container<T> {private T value;@Overridepublic void set(T value) {this.value = value;}@Overridepublic T get() {return value;}
}
第 4 章:擦拭法
Java 的泛型是通过类型擦除(Type Erasure)实现的。这意味着在编译时,所有泛型信息都会被移除,类型参数会被替换为其原始类型(通常是 Object
或具体的类型边界)。这使得泛型可以与非泛型代码互操作,并保持向后兼容性。
擦拭法的影响
-
类型信息在运行时丢失:由于类型擦除,运行时无法获取泛型的类型参数。
List<String> list = new ArrayList<>(); if (list instanceof ArrayList<String>) { // 编译错误,无法检测泛型类型 }
-
无法创建泛型数组:因为类型擦除会移除具体的类型信息,不能创建泛型数组。
List<Integer>[] listArray = new List<Integer>[10]; // 编译错误
5.通配符
通配符 ?
是泛型中的一种特殊类型,用于表示未知的类型。通配符常用于增强代码的灵活性,尤其是在泛型集合中使用。
通配符的种类
-
无界通配符
?
:表示任意类型。List<?> list = new ArrayList<>();
-
有界通配符(上界)
? extends T
:表示T
及其子类。List<? extends Number> list = new ArrayList<Integer>(); // Integer 是 Number 的子类
-
有界通配符(下界)
? super T
:表示T
及其父类。List<? super Integer> list = new ArrayList<Number>(); // Number 是 Integer 的父类
示例:
public static void printNumbers(List<? extends Number> list) {for (Number num : list) {System.out.println(num);}
}List<Integer> intList = Arrays.asList(1, 2, 3);
List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);printNumbers(intList); // 输出整数
printNumbers(doubleList); // 输出浮点数
6.泛型与反射
由于泛型在编译时被擦除,运行时类型信息被丢失,反射与泛型的结合有一些特殊的考虑。虽然运行时无法获取具体的类型参数,但你仍然可以通过反射获取泛型类型的一些信息,比如通过 getGenericType()
。
示例:使用反射获取泛型类型
import java.lang.reflect.*;class GenericExample<T> {private T value;
}public class Main {public static void main(String[] args) throws Exception {Field field = GenericExample.class.getDeclaredField("value");Type type = field.getGenericType();System.out.println("Generic Type: " + type);}
}
泛型与反射的局限性
- 由于类型擦除,无法在运行时获得泛型的实际类型参数。
- 无法创建泛型的实例,因为运行时不具有类型参数的信息。
7.总结
通过泛型,Java 实现了类型安全的通用编程机制。无论是在类、方法还是接口中,泛型都大大提高了代码的复用性和灵活性。通过类型擦除和通配符机制,Java 泛型可以兼容旧版本代码,但在使用反射时需要小心泛型信息的丢失。