Java 泛型详解

Java 泛型(Generics)是 Java 5 引入的一个重要特性,它允许在类、接口和方法中使用类型参数。泛型的主要目的是提高代码的类型安全性和复用性,同时减少强制类型转换的需求。

一、什么是泛型?

泛型允许你在定义类、接口或方法时使用类型参数,而不是具体的类型。这些类型参数在实际使用时会被替换为具体的类型(如 StringInteger 等)。泛型的核心思想是“参数化类型”,即让代码能够适用于多种类型,而不需要为每种类型重复编写代码。

二、泛型的好处

  1. 类型安全性

    • 使用泛型可以避免将错误类型的对象添加到集合中。
    • 编译器会在编译期检查类型是否匹配,从而避免运行时的 ClassCastException
  2. 代码复用

    • 泛型让你可以编写通用的代码,适用于多种类型,而不需要为每种类型单独实现。
  3. 减少类型转换

    • 在没有泛型的情况下,从集合中取出元素时需要显式地进行类型转换。使用泛型后,编译器会自动推断类型,无需手动转换。
  4. 更清晰的代码

    • 泛型使代码更加直观,调用者可以清楚地知道某个类或方法支持哪些类型。

三、泛型的基本语法

1. 定义泛型类

public class Box<T> {
    private T item;

    public void setItem(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }
}
  • <T> 是类型参数,表示这个类可以处理任意类型的对象。
  • T 是一个占位符,代表某种类型,具体类型在实例化时确定。

使用泛型类:

Box<String> stringBox = new Box<>();
stringBox.setItem("Hello");
String value = stringBox.getItem(); // 不需要类型转换

Box<Integer> integerBox = new Box<>();
integerBox.setItem(123);
int number = integerBox.getItem();

2. 定义泛型方法

泛型方法可以在普通类中定义,也可以在泛型类中定义。

public class Utils {
    public static <T> void printArray(T[] array) {
        for (T element : array) {
            System.out.println(element);
        }
    }
}

使用泛型方法:

String[] strings = {"A", "B", "C"};
Utils.printArray(strings);

Integer[] numbers = {1, 2, 3};
Utils.printArray(numbers);

3. 定义泛型接口

public interface Container<T> {
    void add(T item);
    T get(int index);
}

使用泛型接口:

public class StringContainer implements Container<String> {
    private List<String> items = new ArrayList<>();

    @Override
    public void add(String item) {
        items.add(item);
    }

    @Override
    public String get(int index) {
        return items.get(index);
    }
}

四、泛型的通配符

1. 无界通配符<?>

表示未知类型,可以匹配任何类型。

public void printList(List<?> list) {
    for (Object elem : list) {
        System.out.println(elem);
    }
}

使用场景:当方法只需要读取集合中的元素,而不需要修改集合时,可以使用 <?>

2. 上界通配符<? extends T>

表示类型参数必须是 T 或其子类。

public void processNumbers(List<? extends Number> numbers) {
    for (Number num : numbers) {
        System.out.println(num.doubleValue());
    }
}

使用场景:当你希望方法接受某种类型及其子类的集合时,可以使用 <? extends T>

3. 下界通配符<? super T>

表示类型参数必须是 T 或其父类。

public void addNumbers(List<? super Integer> list) {
    list.add(1);
    list.add(2);
}

使用场景:当你希望方法能够向集合中添加某种类型及其子类的对象时,可以使用 <? super T>

五、泛型的限制

  1. 不能使用基本类型

    • 泛型不支持基本类型(如 intdouble 等),只能使用包装类(如 IntegerDouble 等)。
    • 例如:Box<int> 是非法的,但 Box<Integer> 是合法的。
  2. 类型擦除

    • 泛型信息在编译后会被擦除(Type Erasure),这意味着运行时无法获取泛型的具体类型。
    • 例如:new ArrayList<String>() 和 new ArrayList<Integer>() 在运行时都是 ArrayList 类型。
  3. 不能创建泛型数组

    • 你不能直接创建泛型数组,例如 new T[10] 是非法的。
    • 可以通过其他方式绕过此限制,例如使用 Object 数组并进行类型转换。
This article was updated on