Java中使用通配符的限制与使用案例解析
在Java中,通配符(Wildcard)是一种在泛型(Generics)中使用的特殊类型参数,表示可以匹配任意类型。通配符主要有两种形式:? extends T和? super T。它们分别表示上界通配符和下界通配符。
上界通配符 ? extends T
使用? extends T声明的通配符表示该类型是T或T的子类型。这种通配符主要用于读取数据,不能向其中添加新的元素(除了null)。
例子:
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(10); // 编译错误
// list.add(10.5); // 编译错误
// 可以读取元素
Number num = list.get(0); // 编译通过
// 可以添加 null
list.add(null); // 编译通过
}
}
在这个例子中,List<? extends Number>可以持有任何Number的子类类型的列表。你可以从这个列表中读取Number类型的元素,但不能向其中添加具体的元素,因为编译器无法确定列表实际持有的具体类型。
下界通配符 ? super T
使用? super 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>();
// 可以添加 Integer 及其子类的元素
list.add(10); // 编译通过
list.add(20); // 编译通过
// 不能读取具体类型的元素(只能读取为Object)
// Integer num = list.get(0); // 编译错误
Object obj = list.get(0); // 编译通过
}
}
在这个例子中,List<? super Integer>可以持有任何Integer的父类类型的列表。你可以向这个列表中添加Integer类型的元素,但从中读取时只能作为Object类型,因为编译器无法确定列表实际持有的具体类型。
结论
使用通配符声明的泛型类型在以下方面具有限制:
? extends T:允许读取T或T的子类型的元素,不允许添加具体元素(除了null)。? super T:允许添加T或T的子类型的元素,但读取时只能作为Object类型。
这些限制是为了确保类型安全性,避免在运行时出现类型转换异常。
package com.itheima.demo;
import java.util.ArrayList;
import java.util.Collections;
public class Grade {
private ArrayList<Teacher> teacherList = new ArrayList<>();
private ArrayList<Student> studentList = new ArrayList<>();
public Grade() {
Teacher t1 = new Teacher("J1001", "张三", 36, 8800);
Teacher t2 = new Teacher("J1002", "李四", 28, 6500);
Teacher t3 = new Teacher("J1003", "王五", 38, 9600);
Teacher t4 = new Teacher("J1004", "赵六", 48, 10800);
Collections.addAll(teacherList,t1,t2,t3,t4);
Student s1 = new Student("S1001", "王刚", 15, 130);
Student s2 = new Student("S1002", "李婷", 16, 118);
Student s3 = new Student("S1003", "张震", 14, 126);
Student s4 = new Student("S1004", "赵宇", 15, 110);
Collections.addAll(studentList,s1,s2,s3,s4);
}
public ArrayList<Teacher> getTeacherList() {
return teacherList;
}
public void setTeacherList(ArrayList<Teacher> teacherList) {
this.teacherList = teacherList;
}
public ArrayList<Student> getStudentList() {
return studentList;
}
public void setStudentList(ArrayList<Student> studentList) {
this.studentList = studentList;
}
public Grade(ArrayList<Teacher> teacherList, ArrayList<Student> studentList) {
this.teacherList = teacherList;
this.studentList = studentList;
}
public void showAllInfo(ArrayList<? extends Person> list) {
for (Person p : list) {
if(p instanceof Student) {
System.out.println(p);
}
if(p instanceof Teacher) {
System.out.println(p);
}
}
}
public void sortPerson(ArrayList<? extends Person> list) {
if(!list.isEmpty()) {
return;
}
Person p = list.get(0);
if(p instanceof Student) {
for (int i = 0; i < list.size(); i++) {
for (int j = 0; j < list.size() - i - 1; j++) {
Student s1 = (Student) list.get(j);
Student s2 = (Student) list.get(j + 1);
if (s1.getScore() < s2.getScore()) {
Student temp = s1;
list.set(j, list.get(j + 1));
list.set(j + 1, temp);
}
}
}
}
if(p instanceof Teacher) {
for (int i = 0; i < list.size(); i++) {
for (int j = 0; j < list.size()-i-1; j++) {
Teacher t1 = (Teacher) list.get(j);
Teacher t2 = (Teacher) list.get(j+1);
if(t1.getSalary()<t2.getSalary()) {
Teacher temp = t1;
list.set(j,list.get(j+1));
list.set(j+1,temp);
}
}
}
}
}
}
在你的代码中,通配符? extends Person确实会导致无法通过编译,因为通配符上界限制了列表的类型,这意味着编译器不能确定列表中元素的具体类型,只能保证是Person或其子类。因此,你不能对这个列表进行添加或修改操作。
详细解释
使用通配符? extends Person声明的列表只能保证其元素是Person或Person的子类,但不能确定具体的类型。这使得编译器无法保证向列表中添加元素时的类型安全性。
具体来说,以下代码会引起编译错误:
list.set(j, list.get(j + 1)); // 编译错误
list.set(j + 1, temp); // 编译错误
因为list的类型是ArrayList<? extends Person>,编译器无法确定list.get(j + 1)的具体类型,所以不允许调用set方法。
解决方案
为了能够对列表进行修改,可以使用下界通配符? super T。下界通配符允许向列表中添加或修改元素。
修改sortPerson方法
可以将sortPerson方法的参数类型修改为ArrayList<? super Student>或ArrayList<? super Teacher>,具体取决于你需要排序的列表类型。因为你的方法需要处理两种不同类型的列表,所以最好拆分成两个方法:
public void sortStudents(ArrayList<? super Student> list) {
for (int i = 0; i < list.size(); i++) {
for (int j = 0; j < list.size() - i - 1; j++) {
Student s1 = (Student) list.get(j);
Student s2 = (Student) list.get(j + 1);
if (s1.getScore() < s2.getScore()) {
list.set(j, s2);
list.set(j + 1, s1);
}
}
}
}
public void sortTeachers(ArrayList<? super Teacher> list) {
for (int i = 0; i < list.size(); i++) {
for (int j = 0; j < list.size() - i - 1; j++) {
Teacher t1 = (Teacher) list.get(j);
Teacher t2 = (Teacher) list.get(j + 1);
if (t1.getSalary() < t2.getSalary()) {
list.set(j, t2);
list.set(j + 1, t1);
}
}
}
}
调用方法
在调用时,你需要根据实际的列表类型来调用相应的方法:
Grade grade = new Grade();
ArrayList<Student> students = grade.getStudentList();
ArrayList<Teacher> teachers = grade.getTeacherList();
grade.sortStudents(students);
grade.sortTeachers(teachers);
总结
由于上界通配符? extends T的限制,无法对列表进行添加或修改操作。通过使用下界通配符? super T,可以解决这个问题,从而允许对列表进行添加或修改操作。
这种情况可以归结为泛型的约束和限制。Java中的泛型通过类型参数的使用来实现类型安全和代码重用,但也会引入一些操作上的限制。具体来说,上界通配符? extends T和下界通配符? super T都有其各自的约束和使用场景。
泛型的约束和限制
上界通配符 ? extends T
- 约束:指定类型必须是T或T的子类型。
- 限制:不能向集合中添加元素(除了
null),只能读取元素。 - 适用场景:当你需要从集合中读取数据且不修改集合时使用。
List<? extends Number> list = new ArrayList<Integer>();
// 不能添加元素
// list.add(10); // 编译错误
// list.add(10.5); // 编译错误
// 可以读取元素
Number num = list.get(0); // 编译通过
// 可以添加 null
list.add(null); // 编译通过
下界通配符 ? super T
- 约束:指定类型必须是T或T的父类型。
- 限制:可以向集合中添加T或T的子类型的元素,但读取时只能读取为Object类型。
- 适用场景:当你需要向集合中写入数据且不需要读取具体类型时使用。
List<? super Integer> list = new ArrayList<Number>();
// 可以添加 Integer 及其子类的元素
list.add(10); // 编译通过
list.add(20); // 编译通过
// 不能读取具体类型的元素(只能读取为Object)
Object obj = list.get(0); // 编译通过
浙公网安备 33010602011771号