太失败了,工作5年后,我才知道Java 泛型的上限和下限~
Java 泛型的上限和下限详解
1. 引言
Java 泛型是一种强大的特性,使得类、接口和方法能够操作任何类型的对象,而不需要指定具体的类型。在泛型中,我们可以使用上限(Upper Bound)和下限(Lower Bound)来约束类型参数的范围,从而提高代码的类型安全性和灵活性。
本文将详细介绍 Java 泛型上限和下限的概念,并通过丰富的示例代码来帮助理解和应用这些特性。
2. 泛型的基本概念
首先,让我们简要回顾一下 Java 泛型的基本概念。泛型允许我们定义具有类型参数的类、接口和方法。例如,我们可以定义一个简单的泛型类 Box
,它可以存储任何类型的对象:
public class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
在上述代码中,T
是一个类型参数,可以在实例化 Box
类时指定具体的类型,例如 Box<Integer>
或 Box<String>
。
3. 泛型的上限(Upper Bound)
泛型的上限用于限制类型参数必须是某个特定类型或其子类型。我们使用关键字 extends
来指定上限。
泛型的上限使用 extends
关键字声明,表示参数化的类型可能是所指定的类型或者是此类型的子类。例如,List<? extends Number>
表示这个列表可以包含 Number
类型或其任何子类型的对象,如 Integer
、Double
等。
使用泛型上限的一个典型场景是当你希望方法能够处理多种类型,但同时又希望这些类型具有某种共同的特性或方法时。例如,如果你有一个方法需要处理所有数字类型,你可以使用 Number
作为泛型的上限。
需要注意的是,当使用泛型上限时,你不能向集合中添加元素,因为编译器无法保证集合的实际类型是否接受你尝试添加的元素。你只能从集合中读取元素,并且读取的元素类型将是上限类型或其子类型。
3.1 示例:泛型类中的上限
假设我们希望创建一个只能接受 Number
类型及其子类的 Box
类,可以这样实现:
public class Box<T extends Number> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
在这个例子中,类型参数 T
被限制为 Number
或其子类。因此,可以创建 Box<Integer>
或 Box<Double>
的实例,但不能创建 Box<String>
的实例:
public class Main {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<>();
integerBox.setValue(10);
System.out.println("Integer Box Value: " + integerBox.getValue());
Box<Double> doubleBox = new Box<>();
doubleBox.setValue(10.5);
System.out.println("Double Box Value: " + doubleBox.getValue());
// Box<String> stringBox = new Box<>(); // 编译错误,因为 String 不是 Number 的子类
}
}
3.2 示例:泛型方法中的上限
泛型方法也可以使用上限。例如,我们可以编写一个方法来计算数字列表的总和,该方法接受一个包含数字的列表,其元素类型受限于 Number
及其子类:
import java.util.List;
public class MathUtils {
public static <T extends Number> double sum(List<T> numbers) {
double total = 0.0;
for (T number : numbers) {
total += number.doubleValue();
}
return total;
}
}
在这个例子中,方法 sum
接受一个 List<T>
类型的参数,其中 T
必须是 Number
或其子类。以下是使用该方法的示例:
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3, 4.4, 5.5);
System.out.println("Sum of integers: " + MathUtils.sum(integers));
System.out.println("Sum of doubles: " + MathUtils.sum(doubles));
}
}
输出:
Sum of integers: 15.0
Sum of doubles: 16.5
4. 泛型的下限(Lower Bound)
泛型的下限用于限制类型参数必须是某个特定类型或其父类型。我们使用关键字 super
来指定下限。
泛型的下限使用 super
关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,或是 Object
类。例如,List<? super Integer>
表示这个列表可以是 Integer
类型的列表,或者是 Integer
的父类(如 Number
或 Object
)类型的列表。
泛型下限的主要应用场景是当你需要从集合中取出元素并进行一些操作,而这些操作需要访问父类的方法或属性时。例如,如果你有一个方法需要处理所有 Integer
或其父类型的对象,你可以使用 Integer
作为泛型的下限。
与上限不同,使用泛型下限时,你可以向集合中添加元素,因为编译器知道集合可以容纳指定类型或其父类型的任何元素。但是,从集合中读取元素的类型将是下限类型或其父类型,这可能需要你进行显式的类型转换。
4.1 示例:泛型方法中的下限
假设我们希望创建一个方法,该方法可以向列表中添加整数,但列表的类型可以是 Integer
或其父类型(如 Number
或 Object
)。可以这样实现:
import java.util.List;
public class CollectionUtils {
public static void addIntegers(List<? super Integer> list) {
list.add(10);
list.add(20);
list.add(30);
}
}
在这个例子中,方法 addIntegers
接受一个 List<? super Integer>
类型的参数,这意味着列表可以包含 Integer
或任何 Integer
的父类型。以下是使用该方法的示例:
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Number> numberList = new ArrayList<>();
CollectionUtils.addIntegers(numberList);
System.out.println("Number List: " + numberList);
List<Object> objectList = new ArrayList<>();
CollectionUtils.addIntegers(objectList);
System.out.println("Object List: " + objectList);
// List<Double> doubleList = new ArrayList<>();
// CollectionUtils.addIntegers(doubleList); // 编译错误,因为 Double 不是 Integer 的父类
}
}
输出:
Number List: [10, 20, 30]
Object List: [10, 20, 30]
4.2 使用下限的实际场景
在实际开发中,下限常用于处理协变和逆变问题。协变和逆变是指在泛型类型之间转换时类型参数的变化规则。例如,假设我们有一个方法,它需要处理一组对象,并且这个方法应该能够接受任何类型的父类对象:
import java.util.ArrayList;
import java.util.List;
abstract class Animal {
abstract void sound();
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Bark");
}
}
class Cat extends Animal {
@Override
void sound() {
System.out.println("Meow");
}
}
public class AnimalShelter {
public static void addAnimals(List<? super Dog> animals) {
animals.add(new Dog());
// animals.add(new Cat()); // 编译错误,因为 Cat 不是 Dog 的子类
}
public static void main(String[] args) {
List<Animal> animalList = new ArrayList<>();
addAnimals(animalList);
for (Animal animal : animalList) {
animal.sound();
}
}
}
在这个例子中,方法 addAnimals
接受一个 List<? super Dog>
类型的参数,这意味着列表可以包含 Dog
或任何 Dog
的父类型(如 Animal
或 Object
)。这样,我们可以确保在运行时能够安全地向该列表添加 Dog
对象。
5. 上限和下限的对比
上限和下限在泛型编程中扮演着不同的角色:
- 上限(
extends
)通常用于读取操作,可以确保从泛型对象中读取到指定类型或其子类型的对象。 - 下限(
super
)通常用于写入操作,可以确保向泛型对象中添加指定类型或其父类型的对象。
5.1 上限和下限的组合使用
有时候,我们需要同时使用上限和下限来约束泛型类型。例如,我们可以创建一个方法,它既可以读取也可以写入一个泛型对象:
import java.util.List;
public class MixedUtils {
public static <T> void copy(List<? extends T> source, List<? super T> destination) {
for (T item : source) {
destination.add(item);
}
}
}
在这个例子中,方法 copy
接受两个列表参数:source
和 destination
。source
使用上限通配符 <? extends T>
,表示它可以读取 T
类型或其子类型的对象;destination
使用下限通配符 <? super T>
,表示它可以写入 T
类型或其父类型的对象。
以下是使用该方法的示例:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> source = Arrays.asList(1, 2, 3, 4, 5);
List<Number> destination = new ArrayList<>();
MixedUtils.copy(source, destination);
System.out.println("Destination List: " + destination);
}
}
输出:
Destination List: [1, 2, 3, 4, 5]
6. 总结
本文详细介绍了 Java 泛型的上限和下限,通过具体示例展示了如何使用 extends
和 super
关键字来约束泛型类型参数的范围。上限用于限制类型参数为某个特定类型或其子类型,下限用于限制类型参数为某个特定类型或其父类型。
通过掌握泛型的上限和下限,你可以编写出更灵活和类型安全的代码,从而提高程序的可维护性和可靠性。在实际开发中,合理使用泛型上限和下限可以解决许多复杂的类型转换和对象操作问题,使代码更加健壮和易于扩展。希望本文能帮助你更好地理解和应用 Java 泛型的上限和下限。
转载自:https://juejin.cn/post/7379806770363367451