Skip to content

Java 泛型 (Generics)

泛型(Generics)是 Java SE 5.0 引入的一个强大特性,它允许我们在定义类、接口和方法时使用类型参数 (type parameters)。使用泛型可以编写出更灵活、更安全、更清晰的代码。

为什么需要泛型?

在泛型出现之前,Java 的集合类(如 ArrayList)只能存储 Object 类型的对象。这样做有两个主要问题:

  1. 类型不安全:你可以向一个 ArrayList 中添加任何类型的对象,但在编译时无法发现错误。
    java
    // 没有泛型
    List list = new ArrayList();
    list.add("Hello");
    list.add(123); // 编译时不会报错
  2. 需要强制类型转换:从集合中取出元素时,你必须手动将其转换为期望的类型,这很繁琐且可能在运行时抛出 ClassCastException
    java
    String first = (String) list.get(0);
    String second = (String) list.get(1); // 运行时错误! ClassCastException

泛型通过将类型检查从运行时提前到编译时,完美地解决了这些问题。

java
// 使用泛型
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
// stringList.add(123); // 编译错误! 类型不匹配

String first = stringList.get(0); // 无需强制转换

泛型类

我们可以定义自己的泛型类。类型参数(通常用单个大写字母表示,如 T for Type, E for Element, K for Key, V for Value)在类名后的尖括号 <> 中声明。

java
// 定义一个泛型类 Box
public class Box<T> {
    private T content;

    public void setContent(T content) {
        this.content = content;
    }

    public T getContent() {
        return content;
    }
}

// 使用泛型类
public class Main {
    public static void main(String[] args) {
        // 创建一个存储 String 的 Box
        Box<String> stringBox = new Box<>();
        stringBox.setContent("这是一个字符串");
        System.out.println(stringBox.getContent());

        // 创建一个存储 Integer 的 Box
        Box<Integer> integerBox = new Box<>();
        integerBox.setContent(100);
        System.out.println(integerBox.getContent());
    }
}

泛型方法

除了泛型类,我们还可以定义泛型方法。泛型方法有自己的类型参数,这些参数在方法返回类型之前声明。

java
public class GenericMethodExample {

    // 定义一个泛型方法
    public static <E> void printArray(E[] inputArray) {
        for (E element : inputArray) {
            System.out.printf("%s ", element);
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Integer[] intArray = {1, 2, 3, 4, 5};
        String[] stringArray = {"Hello", "World"};

        System.out.println("整型数组:");
        printArray(intArray); // 编译器会自动推断 E 是 Integer

        System.out.println("字符串数组:");
        printArray(stringArray); // 编译器会自动推断 E 是 String
    }
}

有界类型参数 (Bounded Type Parameters)

有时,我们希望限制可以用作类型参数的类型。例如,一个方法可能只接受 Number 或其子类的实例。这可以通过有界类型参数实现。

  • <T extends UpperBound>: T 必须是 UpperBound 类型或其子类型。
java
public class BoundedTypeExample {

    // T 必须是 Number 或其子类 (如 Integer, Double)
    public static <T extends Number> double getDoubleValue(T number) {
        return number.doubleValue();
    }

    public static void main(String[] args) {
        System.out.println(getDoubleValue(10));       // Integer
        System.out.println(getDoubleValue(3.14));     // Double
        // getDoubleValue("text"); // 编译错误! String 不是 Number 的子类
    }
}

通配符 (Wildcards)

通配符 ? 用于表示未知的类型,常用于泛型方法的参数中,以增加灵活性。

  1. 上界通配符 (? extends Type): 表示参数类型是 Type 或其任意子类型。这种集合是只读的(不能添加元素,除了 null),因为我们无法确定集合的确切类型。

    java
    // 可以接受 List<Integer>, List<Double> 等
    public static double sumOfList(List<? extends Number> list) {
        double sum = 0.0;
        for (Number n : list) {
            sum += n.doubleValue();
        }
        return sum;
    }
  2. 下界通配符 (? super Type): 表示参数类型是 Type 或其任意父类型。这种集合是只写的(可以添加 Type 及其子类型的实例),但读取时只能得到 Object

    java
    // 可以接受 List<Integer>, List<Number>, List<Object>
    public static void addIntegers(List<? super Integer> list) {
        list.add(1);
        list.add(2);
    }
  3. 无界通配符 (?): 表示任何类型。当类型不重要时使用,例如 List<?>

类型擦除 (Type Erasure)

Java 的泛型是通过类型擦除来实现的。这意味着在编译后,所有泛型类型信息都会被移除。Box<String>Box<Integer> 在运行时都会变成原始的 Box 类,类型参数 T 会被替换为它的边界(默认为 Object)。编译器会在必要的地方自动插入类型转换代码,以保证类型安全。

本站内容仅供学习和研究使用。