在 Java 泛型中,使用 extends
和 super
关键字来定义通配符的上界和下界,主要是为了保证类型安全,并且能够灵活地处理不同类型的集合。具体来说,使用 extends
和 super
的原因可以通过理解 PECS(Producer Extends, Consumer Super)原则来解释。
PECS 原则
- Producer Extends: 如果需要一个只读的泛型集合(生产者),使用
extends
。 - Consumer Super: 如果需要一个只写的泛型集合(消费者),使用
super
。
当使用泛型通配符 <? extends T>
时,集合被视为生产者,即我们可以从集合中读取数据,但不能向集合中添加数据。让我们深入了解这句话的含义。
理解 extends
用于生产者
使用 <? extends T>
通配符意味着这个集合可以是 T
类型或 T
的子类型。这种设计的目的是为了从集合中读取数据时保证类型安全,但是它也限制了向集合中添加数据的能力。
具体例子
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<? extends Number> list = new ArrayList<Integer>();
// list.add(1); // 编译错误
Number num = list.get(0); // 可以读取数据
}
}
为什么不能添加数据
当我们使用 <? extends T>
通配符时,编译器无法确定集合的具体类型。它只知道集合中的元素类型是 T
或 T
的子类型,但是不能确定具体是哪一个子类型。由于这一点,向集合中添加元素会带来类型安全的问题。
编译器无法确定类型一致性
考虑以下代码:
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<? extends Number> list = new ArrayList<Integer>();
// 编译器不知道 list 是什么类型的子类型
// list.add(1); // 编译错误
}
}
在这个例子中,list
的实际类型是 ArrayList<Integer>
,但是编译器只知道它是某种 Number
的子类型。编译器无法确定 list
实际上是 ArrayList<Integer>
,所以它也不能保证我们要添加的元素类型是否与集合的实际类型一致。
如果编译器允许我们向 list
中添加元素,可能会导致运行时错误。考虑这种情况:
List<? extends Number> list = new ArrayList<Integer>();
list.add(3.14); // 如果编译器允许这行代码,会导致运行时错误
在这里,list
实际上是一个 ArrayList<Integer>
类型的集合。如果编译器允许我们添加 3.14
这样的 Double
类型元素,这会导致类型不一致的问题,最终导致运行时错误。
阅读数据是安全的
当我们从集合中读取数据时,使用 <? extends T>
是安全的,因为所有的元素至少是 T
类型,因此可以安全地赋值给 T
类型的变量。例如:
List<? extends Number> list = new ArrayList<Integer>();
Number num = list.get(0); // 读取是安全的
在这里,编译器知道集合中的元素是 Number
的子类型,因此可以安全地将元素赋值给 Number
类型的变量。
总结
<? extends T>
用于生产者:集合可以是T
类型或T
的子类型。这个设计确保从集合中读取数据时是安全的。- 不能向集合中添加数据:由于编译器无法确定集合的具体子类型,所以不能添加元素到集合中,防止类型不一致的问题。
- 读取数据是安全的:因为所有元素都是
T
的子类型,读取操作是安全的,可以保证类型一致性。
通过理解这一点,可以帮助我们编写更加安全和灵活的泛型代码,避免潜在的类型转换问题。
在 Java 泛型中,使用通配符 <? super T>
指定类型的下界,通常用于消费者场景,即只写操作。这种设计可以确保向集合中添加数据是类型安全的,但读取数据时会有一些限制。具体来说,不能假设读取的数据类型,因为集合中可能包含 T
的父类型的对象。
理解 super
用于消费者
当使用 <? super T>
时,表示这个集合可以持有 T
类型及其任何父类型的对象。这种设计适用于将数据添加到集合中的场景(即消费者)。
具体例子
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<? super Integer> list = new ArrayList<Number>();
list.add(1); // 可以添加 Integer 类型
list.add(2);
// 编译器无法确定读取的数据类型
// Integer num = list.get(0); // 编译错误
Object obj = list.get(0); // 可以读取为 Object 类型
}
}
为什么不能假设读取的数据类型
当我们使用 <? super T>
通配符时,编译器知道集合可以包含 T
类型或 T
的任何父类型的对象。这意味着集合中的元素类型可能比 T
更广泛,可能是 T
的任意父类。因此,编译器不能保证从集合中读取的对象类型是 T
。
编译器无法确定类型一致性
考虑以下代码:
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<? super Integer> list = new ArrayList<Number>();
list.add(1); // 可以添加 Integer 类型
list.add(2);
// 编译器不知道 list 中的元素确切类型
// Integer num = list.get(0); // 编译错误
}
}
在这个例子中,list
的实际类型是 ArrayList<Number>
,但编译器只知道它是某种 Integer
的父类型的集合。编译器不能确定 list
实际上是 ArrayList<Number>
,所以它也不能保证从 list
中读取的元素类型是 Integer
。
如果编译器允许我们将读取的数据赋值给 Integer
,可能会导致类型转换错误。考虑这种情况:
List<? super Integer> list = new ArrayList<Number>();
list.add(1); // 可以添加 Integer 类型
list.add(2);
// 假设编译器允许如下操作:
Integer num = list.get(0); // 如果编译器允许这行代码,可能导致 ClassCastException
在这里,list
实际上是一个 ArrayList<Number>
类型的集合。如果编译器允许我们将 list.get(0)
的返回值赋值给 Integer
类型的变量,而实际存储的是 Number
类型的对象(比如 Double
),则会在运行时抛出 ClassCastException
。
阅读数据的限制
因为集合中可以包含 T
的任意父类型,编译器只能确保从集合中读取的对象是 Object
类型(因为 Object
是所有类的父类)。这意味着我们不能直接将读取的数据赋值给 T
类型的变量。
示例代码
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<? super Integer> list = new ArrayList<Number>();
list.add(1); // 可以添加 Integer 类型
list.add(2);
// 读取数据时只能确保是 Object 类型
Object obj = list.get(0);
System.out.println(obj);
}
}
总结
<? super T>
用于消费者:表示集合可以持有T
类型及其父类型的对象。这个设计确保向集合中添加T
类型的对象是安全的。- 读取数据的限制:由于集合中可以包含
T
的父类型的对象,编译器无法确定从集合中读取的数据类型。因此,读取操作只能返回Object
类型,不能假设读取的数据类型是T
。 - 类型安全:使用
super
可以保证添加操作的类型安全,但读取操作的类型不确定性需要通过类型检查和转换来处理。
通过理解这一点,可以帮助我们编写更加灵活和类型安全的泛型代码,特别是在处理需要添加和读取数据的集合时。
标签:PECS,Java,List,ArrayList,list,类型,编译器,泛型,集合 From: https://blog.csdn.net/weixin_43844521/article/details/140966054