太失败了,工作5年后,我才知道Java 泛型的上限和下限~

Java 泛型的上限和下限详解

1. 引言

Java 泛型是一种强大的特性,使得类、接口和方法能够操作任何类型的对象,而不需要指定具体的类型。在泛型中,我们可以使用上限(Upper Bound)和下限(Lower Bound)来约束类型参数的范围,从而提高代码的类型安全性和灵活性。

本文将详细介绍 Java 泛型上限和下限的概念,并通过丰富的示例代码来帮助理解和应用这些特性。

2. 泛型的基本概念

首先,让我们简要回顾一下 Java 泛型的基本概念。泛型允许我们定义具有类型参数的类、接口和方法。例如,我们可以定义一个简单的泛型类 Box,它可以存储任何类型的对象:

java 复制代码
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 类型或其任何子类型的对象,如 IntegerDouble 等。

使用泛型上限的一个典型场景是当你希望方法能够处理多种类型,但同时又希望这些类型具有某种共同的特性或方法时。例如,如果你有一个方法需要处理所有数字类型,你可以使用 Number 作为泛型的上限。

需要注意的是,当使用泛型上限时,你不能向集合中添加元素,因为编译器无法保证集合的实际类型是否接受你尝试添加的元素。你只能从集合中读取元素,并且读取的元素类型将是上限类型或其子类型。

3.1 示例:泛型类中的上限

假设我们希望创建一个只能接受 Number 类型及其子类的 Box 类,可以这样实现:

java 复制代码
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> 的实例:

java 复制代码
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 及其子类:

java 复制代码
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 或其子类。以下是使用该方法的示例:

java 复制代码
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));
    }
}

输出:

yaml 复制代码
Sum of integers: 15.0
Sum of doubles: 16.5

4. 泛型的下限(Lower Bound)

泛型的下限用于限制类型参数必须是某个特定类型或其父类型。我们使用关键字 super 来指定下限。

泛型的下限使用 super 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,或是 Object 类。例如,List<? super Integer> 表示这个列表可以是 Integer 类型的列表,或者是 Integer 的父类(如 NumberObject)类型的列表。

泛型下限的主要应用场景是当你需要从集合中取出元素并进行一些操作,而这些操作需要访问父类的方法或属性时。例如,如果你有一个方法需要处理所有 Integer 或其父类型的对象,你可以使用 Integer 作为泛型的下限。

与上限不同,使用泛型下限时,你可以向集合中添加元素,因为编译器知道集合可以容纳指定类型或其父类型的任何元素。但是,从集合中读取元素的类型将是下限类型或其父类型,这可能需要你进行显式的类型转换。

4.1 示例:泛型方法中的下限

假设我们希望创建一个方法,该方法可以向列表中添加整数,但列表的类型可以是 Integer 或其父类型(如 NumberObject)。可以这样实现:

java 复制代码
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 的父类型。以下是使用该方法的示例:

java 复制代码
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 的父类
    }
}

输出:

less 复制代码
Number List: [10, 20, 30]
Object List: [10, 20, 30]

4.2 使用下限的实际场景

在实际开发中,下限常用于处理协变和逆变问题。协变和逆变是指在泛型类型之间转换时类型参数的变化规则。例如,假设我们有一个方法,它需要处理一组对象,并且这个方法应该能够接受任何类型的父类对象:

java 复制代码
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 的父类型(如 AnimalObject)。这样,我们可以确保在运行时能够安全地向该列表添加 Dog 对象。

5. 上限和下限的对比

上限和下限在泛型编程中扮演着不同的角色:

  • 上限(extends)通常用于读取操作,可以确保从泛型对象中读取到指定类型或其子类型的对象。
  • 下限(super)通常用于写入操作,可以确保向泛型对象中添加指定类型或其父类型的对象。

5.1 上限和下限的组合使用

有时候,我们需要同时使用上限和下限来约束泛型类型。例如,我们可以创建一个方法,它既可以读取也可以写入一个泛型对象:

java 复制代码
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 接受两个列表参数:sourcedestinationsource 使用上限通配符 <? extends T>,表示它可以读取 T 类型或其子类型的对象;destination 使用下限通配符 <? super T>,表示它可以写入 T 类型或其父类型的对象。

以下是使用该方法的示例:

java 复制代码
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);
    }
}

输出:

ini 复制代码
Destination List: [1, 2, 3, 4, 5]

6. 总结

本文详细介绍了 Java 泛型的上限和下限,通过具体示例展示了如何使用 extendssuper 关键字来约束泛型类型参数的范围。上限用于限制类型参数为某个特定类型或其子类型,下限用于限制类型参数为某个特定类型或其父类型。

通过掌握泛型的上限和下限,你可以编写出更灵活和类型安全的代码,从而提高程序的可维护性和可靠性。在实际开发中,合理使用泛型上限和下限可以解决许多复杂的类型转换和对象操作问题,使代码更加健壮和易于扩展。希望本文能帮助你更好地理解和应用 Java 泛型的上限和下限。

相关推荐
煸橙干儿~~6 分钟前
分析JS Crash(进程崩溃)
java·前端·javascript
2401_854391087 分钟前
Spring Boot大学生就业招聘系统的开发与部署
java·spring boot·后端
Amor风信子8 分钟前
华为OD机试真题---跳房子II
java·数据结构·算法
虽千万人 吾往矣27 分钟前
golang gorm
开发语言·数据库·后端·tcp/ip·golang
杨荧34 分钟前
【JAVA开源】基于Vue和SpringBoot的洗衣店订单管理系统
java·开发语言·vue.js·spring boot·spring cloud·开源
陈逸轩*^_^*1 小时前
Java 网络编程基础
java·网络·计算机网络
这孩子叫逆1 小时前
Spring Boot项目的创建与使用
java·spring boot·后端
星星法术嗲人1 小时前
【Java】—— 集合框架:Collections工具类的使用
java·开发语言
一丝晨光1 小时前
C++、Ruby和JavaScript
java·开发语言·javascript·c++·python·c·ruby
夜流冰1 小时前
工具方法 - 面试中回答问题的技巧
面试·职场和发展