Java泛型知识
- 泛型类
- **泛型接口介绍**
- 泛型方法
- 1. 定义泛型方法
- 2. 泛型方法的特性和注意事项
- 类型参数声明
- 类型推断
- 泛型方法与泛型类的区别
- 静态方法中的泛型
- 3. 有界类型参数的泛型方法
- 4. 通配符和泛型方法
- 类型擦除(Type Erasure)
- 1. 类型擦除的工作原理
- 2. 类型擦除示例
- 3. 类型擦除的影响和限制
- 不能创建泛型类型的实例
- 不能使用泛型类型的数组
- 不能使用类型参数进行 instanceof 检查
- 只能捕获擦除后的异常类型
- 4. 桥接方法(Bridge Methods)
- 1. 无法创建泛型类型的实例
- 2. 不能使用泛型类型的数组
- 3. 不能使用类型参数进行 `instanceof` 检查
- 4. 泛型类型参数的类型擦除
- 5. 泛型方法中的桥接方法
- 6. 泛型类型参数的边界限制
- 总结
- Java 泛型中的通配符详解
- 1. 无界通配符(Unbounded Wildcard)
- 使用场景
- 2. 有界通配符
- 上界通配符(Upper Bounded Wildcard)
- 使用场景
- 下界通配符(Lower Bounded Wildcard)
- 使用场景
- 3. 通配符的PECS原则
- 4. 通配符和泛型方法的结合
- 3. 注意事项和使用细节
- 类型安全性
- 不能添加元素到使用上界通配符的集合中
- 可以从使用上界通配符的集合中读取元素
- 不能从使用下界通配符的集合中读取元素为具体类型
- 可以向使用下界通配符的集合中添加元素
- 结合泛型方法使用通配符
什么是泛型?
泛型(Generics)是编程语言中一种强大的抽象概念,它允许在定义类、接口和方法时使用类型参数。通过泛型,可以编写更加通用、灵活和类型安全的代码,而不必在每次使用时都指定具体的数据类型。
泛型类的引入
// 定义一个泛型类ItemBox,可以存放任意类型的物品
public class ItemBox<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
public static void main(String[] args) {
// 创建一个ItemBox对象,可以存放String类型的物品
ItemBox<String> stringBox = new ItemBox<>();
stringBox.setItem("Book");
System.out.println("物品名称:" + stringBox.getItem());
// 创建一个ItemBox对象,可以存放Integer类型的物品
ItemBox<Integer> integerBox = new ItemBox<>();
integerBox.setItem(100);
System.out.println("物品数量:" + integerBox.getItem());
// 创建一个ItemBox对象,可以存放任意类型的物品
ItemBox<Boolean> booleanBox = new ItemBox<>();
booleanBox.setItem(true);
System.out.println("物品状态:" + booleanBox.getItem());
}
}
泛型类
泛型类(Generic Class)是指在类的声明中使用了一个或多个类型参数(Type Parameters)。这些类型参数可以在类的字段、方法参数、方法返回类型以及局部变量中使用,从而使得类在实例化时可以支持多种不同的数据类型。
1. 定义泛型类
泛型类的定义使用尖括号 <>
指定一个或多个类型参数。例如,一个简单的泛型类可以是这样的:
public class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
在这个例子中,Box<T>
是一个泛型类,T
是类型参数。在实际使用时,可以通过指定具体的类型来创建不同的 Box
对象。
2. 使用泛型类
实例化泛型类
通过指定具体的类型来实例化泛型类,例如:
Box<String> stringBox = new Box<>();
Box<Integer> intBox = new Box<>();
这里,Box<String>
和 Box<Integer>
分别是 Box
类的两种具体化实例。这意味着可以创建一个可以存储 String
类型数据的盒子和一个可以存储 Integer
类型数据的盒子。
类中的方法和字段
在泛型类中,可以使用类型参数 T
来定义类的字段和方法,比如 content
字段和 setContent
、getContent
方法。这些方法可以操作存储在泛型类中的数据,而不需要知道具体的类型。
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
3. 泛型类的特性
类型安全
泛型类在编译时会进行类型检查,从而提高了类型安全性。例如,如果尝试将一个 String
类型的对象放入 Box<Integer>
类中,编译器会报错,避免了在运行时可能出现的类型转换异常。
代码重用
通过泛型类,可以编写更加通用和抽象的代码,避免了在不同类型上重复编写相似的类和方法。这提高了代码的可重用性和可维护性。
泛型方法
泛型类中还可以定义泛型方法,这些方法可以拥有自己的类型参数,不一定要与类的类型参数相同。例如:
public class Utils {
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
}
这里的 printArray
是一个泛型方法,可以打印任何类型的数组。
4. 限制和约束
有界类型参数
可以使用有界类型参数来限制泛型类的类型参数。例如,限制 T
必须是某个类的子类或实现某个接口:
public class Box<T extends Number> {
// ...
}
这样可以在编译时确保泛型类型的一些约束条件。
5. 单个类型参数的泛型类
最简单的泛型类只有一个类型参数。例如,一个简单的 Box<T>
类,用于存放任意类型的物品:
public class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
public static void main(String[] args) {
// 使用泛型类Box存放不同类型的数据
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello");
System.out.println("内容:" + stringBox.getContent());
Box<Integer> intBox = new Box<>();
intBox.setContent(123);
System.out.println("内容:" + intBox.getContent());
}
}
注意事项:
- 泛型类的类型参数可以用于定义类的字段、方法参数、方法返回类型以及局部变量,但不能用于静态上下文中。
- 泛型类型参数通常用单个大写字母
T
、E
、K
、V
等来表示,但实际上可以使用任何有效的标识符。
6. 多个类型参数的泛型类
泛型类可以拥有多个类型参数,通过逗号分隔。例如,Pair<K, V>
类用于表示一对键值对:
public 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;
}
public static void main(String[] args) {
// 使用泛型类Pair存放不同类型的键值对
Pair<String, Integer> pair1 = new Pair<>("Age", 30);
System.out.println(pair1.getKey() + ": " + pair1.getValue());
Pair<Boolean, String> pair2 = new Pair<>(true, "Active");
System.out.println(pair2.getKey() + ": " + pair2.getValue());
}
}
注意事项:
- 多个类型参数可以为不同的数据类型提供更多的灵活性和通用性。
- 类的构造函数、方法和字段都可以使用这些类型参数,以便实现具体的功能和逻辑。
7. 有界类型参数的泛型类
有界类型参数可以限制泛型类的类型参数必须是某个类的子类或实现某个接口。例如,BoundedBox<T extends Number>
类用于存放数字类型的数据:
public class BoundedBox<T extends Number> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
public static void main(String[] args) {
// 使用有界类型参数的泛型类BoundedBox
BoundedBox<Integer> intBox = new BoundedBox<>();
intBox.setContent(123);
System.out.println("内容:" + intBox.getContent());
// 编译错误:String不是Number的子类
// BoundedBox<String> stringBox = new BoundedBox<>();
}
}
注意事项:
- 有界类型参数通过
extends
关键字指定上界,可以是类或接口。例如,<T extends Number>
表示T
必须是Number
类或其子类。 - 有界类型参数可以增强类型安全性,限制泛型类型的范围,避免无效的类型使用。
泛型接口介绍
泛型接口(Generic Interface)是一种具有泛型类型参数的接口,它可以定义一组操作,这些操作可以在不同的数据类型上进行通用处理。泛型接口使得接口可以与多种数据类型进行交互,提高了代码的灵活性和重用性。
1. 定义泛型接口
泛型接口的定义方式与泛型类类似,使用尖括号 <>
指定一个或多个类型参数。例如,定义一个简单的泛型接口 Pair<T>
,用于表示一对数据:
public interface Pair<T> {
T getFirst();
T getSecond();
void setFirst(T first);
void setSecond(T second);
}
在这个例子中,Pair<T>
是一个泛型接口,T
是类型参数。接口中的方法可以使用类型参数 T
,但不能在接口的静态上下文中引用该类型参数。
2. 实现泛型接口
实现泛型接口时,需要指定具体的类型参数,或者使用通配符来表示不确定的类型。例如,实现 Pair<T>
接口的 OrderedPair
类:
public class OrderedPair<T> implements Pair<T> {
private T first;
private T second;
public OrderedPair(T first, T second) {
this.first = first;
this.second = second;
}
@Override
public T getFirst() {
return first;
}
@Override
public T getSecond() {
return second;
}
@Override
public void setFirst(T first) {
this.first = first;
}
@Override
public void setSecond(T second) {
this.second = second;
}
public static void main(String[] args) {
OrderedPair<String> pair = new OrderedPair<>("Hello", "World");
System.out.println("First: " + pair.getFirst());
System.out.println("Second: " + pair.getSecond());
}
}
3. 注意事项和相关知识细节
- 泛型接口的优势:
- 代码重用:通过泛型接口,可以实现对多种数据类型的通用操作,避免了为每种数据类型编写单独的接口和方法。
- 类型安全:编译器可以在编译时进行类型检查,确保使用泛型接口时不会出现类型转换错误。
- 泛型接口的限制:
- 静态上下文中的限制:与泛型类类似,泛型接口的静态成员(如静态字段或静态方法)不能引用接口中的类型参数。
- 通配符的使用:
- 可以使用通配符
?
表示不确定的类型,用于接口方法的参数或返回类型,以支持更广泛的类型操作。
- 可以使用通配符
public interface Processor<T> {
void process(T item);
}
public class StringProcessor implements Processor<String> {
@Override
public void process(String item) {
System.out.println("Processing: " + item);
}
public static void main(String[] args) {
Processor<String> processor = new StringProcessor();
processor.process("Hello World");
}
}
泛型方法
泛型方法(Generic Method)是一种在方法中使用泛型类型参数的技术,允许方法在调用时接受不同类型的参数,从而实现对多种数据类型的通用操作。泛型方法可以在普通类、泛型类、接口等地方定义和使用。
1. 定义泛型方法
泛型方法的定义格式为在方法返回类型前面使用尖括号 <>
声明类型参数,并在方法体中使用这些类型参数。例如,一个简单的泛型方法 printArray
用于打印任意类型数组的元素:
public class Utils {
public static <T> void printArray(T[] array) {
for (T elem : array) {
System.out.print(elem + " ");
}
System.out.println();
}
public static void main(String[] args) {
Integer[] intArray = { 1, 2, 3, 4, 5 };
Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4, 5.5 };
String[] stringArray = { "Hello", "World" };
System.out.print("Integer Array: ");
printArray(intArray);
System.out.print("Double Array: ");
printArray(doubleArray);
System.out.print("String Array: ");
printArray(stringArray);
}
}
在这个例子中,printArray
是一个泛型方法,使用了类型参数 <T>
。它可以接受任何类型的数组作为参数,并打印数组中的元素。在调用时,编译器会根据传入的参数类型进行类型推断。
2. 泛型方法的特性和注意事项
- 类型参数声明:泛型方法在方法名之前使用
<T>
这样的语法声明类型参数。可以使用多个类型参数,如<T, U>
。 - 类型推断:在调用泛型方法时,通常不需要显式指定类型参数,编译器可以根据方法参数的类型进行推断。
- 泛型方法与泛型类:泛型方法可以独立于泛型类存在,即使是非泛型类中的方法也可以是泛型的。
- 静态上下文中的限制:与泛型类类似,泛型方法中的静态方法不能引用类型参数。
类型参数声明
泛型方法的类型参数在方法返回类型之前声明,通常使用大写字母(如 T
、E
、K
、V
)表示类型参数:
public static <T> void methodName(T param) {
// 方法体
}
类型推断
调用泛型方法时,编译器会根据方法参数的类型自动推断类型参数,因此通常不需要显式指定类型参数:
Integer[] intArray = {1, 2, 3};
swap(intArray, 0, 2); // 编译器自动推断T为Integer
泛型方法与泛型类的区别
泛型方法可以独立于泛型类存在,即使在非泛型类中也可以定义泛型方法:
public class Utils {
public static <T> void genericMethod(T param) {
System.out.println(param);
}
}
静态方法中的泛型
静态方法可以是泛型方法,但静态方法不能引用类的泛型类型参数。如果静态方法需要使用泛型类型参数,必须在方法自身上声明:
public class Utils {
public static <T> void staticGenericMethod(T param) {
System.out.println(param);
}
}
3. 有界类型参数的泛型方法
有时候,可以对泛型方法的类型参数进行限制,使其必须是某个类的子类或者实现某个接口。这称为有界类型参数。例如,一个泛型方法 compare
比较两个对象的大小,其中的类型参数 <T extends Comparable<T>>
表示 T
必须实现 Comparable
接口:
public class Utils {
public static <T extends Comparable<T>> int compare(T obj1, T obj2) {
return obj1.compareTo(obj2);
}
public static void main(String[] args) {
System.out.println(compare(10, 20)); // Output: -1
System.out.println(compare("apple", "banana")); // Output: -1
}
}
在这个例子中,compare
方法可以比较 Integer
、String
等实现了 Comparable
接口的对象。
4. 通配符和泛型方法
通配符 ?
可以用在泛型方法中,用来表示不确定的类型。例如,一个泛型方法 printList
打印任意类型的 List
中的元素:
public class Utils {
public static void printList(List<?> list) {
for (Object elem : list) {
System.out.print(elem + " ");
}
System.out.println();
}
public static void main(String[] args) {
List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5);
List<String> stringList = Arrays.asList("Hello", "World");
System.out.print("Integer List: ");
printList(intList);
System.out.print("String List: ");
printList(stringList);
}
}
在这个例子中,printList
方法使用了通配符 List<?>
,可以接受任意类型的 List
对象作为参数,并打印列表中的元素。
类型擦除(Type Erasure)
类型擦除是Java泛型实现的核心机制,它在编译时将泛型类型转换为原始类型,以便与非泛型代码保持兼容。在Java中,泛型是一个编译时的概念,在运行时泛型类型被擦除,不会保留任何类型参数信息。这种机制确保了Java语言向后兼容,但也带来了若干限制和特性。
1. 类型擦除的工作原理
在编译时,Java编译器会移除(或擦除)所有泛型类型信息,将泛型类型替换为它们的非泛型上界(如果没有指定上界,则替换为 Object
)。以下是类型擦除的详细步骤:
- 将类型参数替换为其上界:
- 如果类型参数有上界,则替换为上界。
- 如果没有上界,则替换为
Object
。
- 插入类型转换:
- 在需要的地方插入类型转换,以确保类型安全。
- 移除所有泛型类型信息:
- 从方法签名、字段、局部变量中移除泛型类型信息。
2. 类型擦除示例
让我们来看一个具体的例子,以理解类型擦除的工作原理:
public class GenericClass<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public static void main(String[] args) {
GenericClass<String> stringInstance = new GenericClass<>();
stringInstance.setValue("Hello");
System.out.println(stringInstance.getValue());
GenericClass<Integer> integerInstance = new GenericClass<>();
integerInstance.setValue(123);
System.out.println(integerInstance.getValue());
}
}
在编译后,类型擦除将泛型类型 T
替换为 Object
,编译器生成的字节码相当于:
public class GenericClass {
private Object value;
public void setValue(Object value) {
this.value = value;
}
public Object getValue() {
return value;
}
public static void main(String[] args) {
GenericClass stringInstance = new GenericClass();
stringInstance.setValue("Hello");
System.out.println((String) stringInstance.getValue());
GenericClass integerInstance = new GenericClass();
integerInstance.setValue(123);
System.out.println((Integer) integerInstance.getValue());
}
}
3. 类型擦除的影响和限制
不能创建泛型类型的实例
由于类型参数在运行时被擦除,无法直接创建泛型类型的实例:
public class GenericClass<T> {
public GenericClass() {
// T value = new T(); // 编译错误
}
}
不能使用泛型类型的数组
由于类型擦除,不能直接创建泛型类型的数组:
public class GenericClass<T> {
// T[] array = new T[10]; // 编译错误
}
不能使用类型参数进行 instanceof 检查
类型擦除后,无法在运行时获取类型参数的信息,因此不能使用 instanceof
检查泛型类型:
public class GenericClass<T> {
public void checkType(Object obj) {
// if (obj instanceof T) { // 编译错误
// }
}
}
只能捕获擦除后的异常类型
泛型类不能直接或间接继承 Throwable
类:
public class GenericClass<T> {
// public class MyException<T> extends Exception { // 编译错误
// }
}
4. 桥接方法(Bridge Methods)
类型擦除可能会引入桥接方法(Bridge Methods),这是编译器生成的合成方法,用于保证类型安全和多态性。例如:
public class GenericClass<T> {
public T getValue() {
return null;
}
}
public class StringClass extends GenericClass<String> {
@Override
public String getValue() {
return "Hello";
}
}
编译后,StringClass
会包含一个桥接方法,用于返回 String
类型的值:
public class StringClass extends GenericClass {
@Override
public Object getValue() {
return getValue(); // 调用桥接方法
}
public String getValue() {
return "Hello";
}
}
1. 无法创建泛型类型的实例
由于类型参数在运行时被擦除,无法直接创建泛型类型的实例:
public class GenericClass<T> {
public GenericClass() {
// T value = new T(); // 编译错误
}
}
解决方案:可以通过传递 Class
对象来创建实例:
public class GenericClass<T> {
private Class<T> type;
public GenericClass(Class<T> type) {
this.type = type;
}
public T createInstance() throws IllegalAccessException, InstantiationException {
return type.newInstance();
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
GenericClass<String> stringGenericClass = new GenericClass<>(String.class);
String instance = stringGenericClass.createInstance();
System.out.println(instance);
}
}
2. 不能使用泛型类型的数组
由于类型擦除,无法直接创建泛型类型的数组:
public class GenericClass<T> {
// T[] array = new T[10]; // 编译错误
}
解决方案:可以创建泛型数组的原始类型数组,然后进行类型转换:
public class GenericClass<T> {
private T[] array;
@SuppressWarnings("unchecked")
public GenericClass(int size) {
array = (T[]) new Object[size]; // 创建泛型数组的原始类型数组
}
public T get(int index) {
return array[index];
}
public void set(int index, T value) {
array[index] = value;
}
public static void main(String[] args) {
GenericClass<String> genericArray = new GenericClass<>(10);
genericArray.set(0, "Hello");
System.out.println(genericArray.get(0));
}
}
3. 不能使用类型参数进行 instanceof
检查
类型擦除后,无法在运行时获取类型参数的信息,因此不能使用 instanceof
检查泛型类型:
public class GenericClass<T> {
public boolean checkType(Object obj) {
// if (obj instanceof T) { // 编译错误
// }
return false;
}
}
解决方案:可以使用传递的 Class
对象进行类型检查:
public class GenericClass<T> {
private Class<T> type;
public GenericClass(Class<T> type) {
this.type = type;
}
public boolean checkType(Object obj) {
return type.isInstance(obj);
}
public static void main(String[] args) {
GenericClass<String> stringChecker = new GenericClass<>(String.class);
System.out.println(stringChecker.checkType("Hello")); // true
System.out.println(stringChecker.checkType(123)); // false
}
}
4. 泛型类型参数的类型擦除
在类型擦除过程中,类型参数被替换为其上界(若未指定上界,则替换为 Object
)。这可能会导致潜在的类型转换异常:
public class GenericClass<T> {
public void print(T value) {
System.out.println(value);
}
}
public class Main {
public static void main(String[] args) {
GenericClass<Integer> intInstance = new GenericClass<>();
GenericClass rawInstance = intInstance; // 类型擦除导致的原始类型
rawInstance.print("Hello"); // 运行时类型转换错误
}
}
解决方案:避免将泛型类型转换为原始类型,尽量使用泛型类型参数:
public class GenericClass<T> {
public void print(T value) {
System.out.println(value);
}
public static void main(String[] args) {
GenericClass<Integer> intInstance = new GenericClass<>();
intInstance.print(123); // 正确使用泛型类型
}
}
5. 泛型方法中的桥接方法
类型擦除可能会引入桥接方法(Bridge Methods),这是编译器生成的合成方法,用于保证类型安全和多态性:
public class GenericClass<T> {
public T getValue() {
return null;
}
}
public class StringClass extends GenericClass<String> {
@Override
public String getValue() {
return "Hello";
}
}
编译后,StringClass
会包含一个桥接方法,用于返回 String
类型的值:
public class StringClass extends GenericClass {
@Override
public Object getValue() {
return getValue(); // 调用桥接方法
}
public String getValue() {
return "Hello";
}
}
解决方案:理解桥接方法的存在,有助于调试和理解编译器生成的字节码。
6. 泛型类型参数的边界限制
泛型类不能直接或间接继承 Throwable
类:
public class GenericClass<T> {
// public class MyException<T> extends Exception { // 编译错误
// }
}
解决方案:避免使用泛型类型参数来定义异常类。
总结
类型擦除是Java泛型实现的核心机制,它确保了与非泛型代码的兼容性,但也引入了一些限制和注意事项。理解类型擦除的工作原理和限制,有助于在使用泛型编程时避免常见错误,并编写更加安全和高效的代码。通过合理使用泛型和类型参数,可以在保持代码灵活性的同时,确保类型安全和兼容性。
Java 泛型中的通配符详解
通配符(Wildcard)是Java泛型中一个非常重要且强大的概念,用于表示不确定的类型。通配符在定义泛型类型时,提供了更多的灵活性,使得代码更加通用和复用性更高。通配符主要有三种形式:无界通配符、有界通配符(上界和下界)。
1. 无界通配符(Unbounded Wildcard)
无界通配符使用 ?
表示,它表示任何类型。
使用场景
无界通配符适用于当操作只涉及对象的通用功能时。例如,打印集合中的所有元素:
import java.util.*;
public class WildcardDemo {
public static void printList(List<?> list) {
for (Object elem : list) {
System.out.print(elem + " ");
}
System.out.println();
}
public static void main(String[] args) {
List<Integer> intList = Arrays.asList(1, 2, 3);
List<String> strList = Arrays.asList("one", "two", "three");
printList(intList); // Output: 1 2 3
printList(strList); // Output: one two three
}
}
在上述示例中,printList
方法使用无界通配符 List<?>
来表示可以接受任何类型的 List
。
2. 有界通配符
有界通配符通过指定一个类型边界来限制通配符所表示的类型。分为上界通配符和下界通配符。
上界通配符(Upper Bounded Wildcard)
上界通配符使用 ? extends T
表示,它表示类型参数必须是 T
或 T
的子类。
使用场景
上界通配符适用于读取操作,因为它确保了可以安全地读取 T
类型的值。
import java.util.*;
public class WildcardDemo {
public static void printNumbers(List<? extends Number> list) {
for (Number num : list) {
System.out.print(num + " ");
}
System.out.println();
}
public static void main(String[] args) {
List<Integer> intList = Arrays.asList(1, 2, 3);
List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);
printNumbers(intList); // Output: 1 2 3
printNumbers(doubleList); // Output: 1.1 2.2 3.3
}
}
在上述示例中,printNumbers
方法使用上界通配符 List<? extends Number>
来表示可以接受 Number
及其子类的 List
。
下界通配符(Lower Bounded Wildcard)
下界通配符使用 ? super T
表示,它表示类型参数必须是 T
或 T
的超类。
使用场景
下界通配符适用于写入操作,因为它确保了可以安全地写入 T
类型的值。
import java.util.*;
public class WildcardDemo {
public static void addNumbers(List<? super Integer> list) {
list.add(1);
list.add(2);
list.add(3);
}
public static void main(String[] args) {
List<Number> numList = new ArrayList<>();
addNumbers(numList);
System.out.println(numList); // Output: [1, 2, 3]
}
}
在上述示例中,addNumbers
方法使用下界通配符 List<? super Integer>
来表示可以接受 Integer
及其超类的 List
。
3. 通配符的PECS原则
PECS原则(Producer Extends Consumer Super)帮助记忆通配符的使用规则:
- Producer Extends:如果参数化类型表示一个生产者(即只从中读取),使用上界通配符
? extends T
。 - Consumer Super:如果参数化类型表示一个消费者(即只向其中写入),使用下界通配符
? super T
。
import java.util.*;
public class WildcardDemo {
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (T item : src) {
dest.add(item);
}
}
public static void main(String[] args) {
List<Number> numList = new ArrayList<>();
List<Integer> intList = Arrays.asList(1, 2, 3);
copy(numList, intList);
System.out.println(numList); // Output: [1, 2, 3]
}
}
在上述示例中,copy
方法使用下界通配符 List<? super T>
表示目标列表,可以接受 T
及其超类的元素;使用上界通配符 List<? extends T>
表示源列表,可以提供 T
及其子类的元素。
4. 通配符和泛型方法的结合
通配符和泛型方法可以结合使用,实现更加灵活的泛型操作:
import java.util.*;
public class WildcardDemo {
public static <T> void swap(List<T> list, int i, int j) {
T temp = list.get(i);
list.set(i, list.get(j));
list.set(j, temp);
}
public static void main(String[] args) {
List<String> strList = new ArrayList<>(Arrays.asList("one", "two", "three"));
swap(strList, 0, 2);
System.out.println(strList); // Output: [three, two, one]
}
}
在上述示例中,swap
方法是一个泛型方法,使用类型参数 <T>
,可以接受任何类型的 List
并交换指定位置的元素。
3. 注意事项和使用细节
类型安全性
尽量避免使用原始类型,使用带有通配符的泛型参数代替。例如:
List<?> rawList = new ArrayList(); // 不推荐
List<?> safeList = new ArrayList<>(); // 推荐
不能添加元素到使用上界通配符的集合中
由于类型安全的原因,不能向使用上界通配符的集合中添加元素:
List<? extends Number> list = new ArrayList<>();
// list.add(1); // 编译错误
解决方案:如果需要添加元素,请使用具体类型或下界通配符。
可以从使用上界通配符的集合中读取元素
可以安全地从使用上界通配符的集合中读取元素:
List<? extends Number> list = Arrays.asList(1, 2.0, 3L);
for (Number num : list) {
System.out.println(num);
}
不能从使用下界通配符的集合中读取元素为具体类型
从使用下界通配符的集合中读取元素时,类型为 Object
:
List<? super Integer> list = Arrays.asList(1, 2, 3);
for (Object obj : list) {
System.out.println(obj);
}
可以向使用下界通配符的集合中添加元素
可以向使用下界通配符的集合中添加元素:
List<? super Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
结合泛型方法使用通配符
通配符和泛型方法可以结合使用,实现更加灵活的泛型操作:
import java.util.*;
public class WildcardDemo {
public static <T> void swap(List<T> list, int i, int j) {
T temp = list.get(i);
list.set(i, list.get(j));
list.set(j, temp);
}
public static void main(String[] args) {
List<String> strList = new ArrayList<>(Arrays.asList("one", "two", "three"));
swap(strList, 0, 2);
System.out.println(strList); // Output: [three, two, one]
}
}