Java 泛型(Generics)
                           
天天向上
发布: 2025-03-03 23:05:46

原创
448 人浏览过

Java 泛型(Generics)是 Java SE 5 引入的一个特性,它允许在 类、接口和方法 中使用 类型参数,以提供更高的类型安全性和代码复用性。


1. 为什么需要泛型?

在 Java 1.5 之前,使用集合(如 ArrayList)时,存储的是 Object 类型,需要手动进行类型转换:

import java.util.ArrayList;

public class Test {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add("Hello");
        list.add(123);  // 不安全

        String str = (String) list.get(0); // 需要强制转换
        System.out.println(str);
    }
}

缺点:

  1. 类型不安全(可以向 ArrayList 添加任意类型的对象)。
  2. 需要类型转换,容易导致 ClassCastException

解决方案:使用泛型

import java.util.ArrayList;

public class Test {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Hello");
        // list.add(123); // 编译错误

        String str = list.get(0); // 不需要强制转换
        System.out.println(str);
    }
}

2. 泛型的基本使用

2.1 泛型类

泛型类在定义时指定一个或多个类型参数:

// T 代表一个类型参数
class Box<T> {
    private T value;

    public void setValue(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }
}

public class Test {
    public static void main(String[] args) {
        Box<String> stringBox = new Box<>();
        stringBox.setValue("Hello");
        System.out.println(stringBox.getValue());

        Box<Integer> intBox = new Box<>();
        intBox.setValue(100);
        System.out.println(intBox.getValue());
    }
}

总结:

  • T 代表泛型类型,可以替换为任何引用类型。
  • 在实例化对象时,指定具体的类型,如 Box<String>

2.2 泛型方法

泛型方法可以定义在普通类或泛型类中:

class Util {
    // 泛型方法
    public static <T> void print(T data) {
        System.out.println(data);
    }
}

public class Test {
    public static void main(String[] args) {
        Util.print("Hello");  // 自动推断 T 为 String
        Util.print(100);      // 自动推断 T 为 Integer
        Util.print(3.14);     // 自动推断 T 为 Double
    }
}

总结:

  • 泛型方法在返回类型前声明 <T>
  • Java 自动推断 类型,无需手动指定。

2.3 泛型接口

泛型接口允许指定类型:

// 定义一个泛型接口
interface Container<T> {
    void add(T item);
    T get();
}

// 实现泛型接口(指定具体类型)
class StringContainer implements Container<String> {
    private String item;

    public void add(String item) {
        this.item = item;
    }

    public String get() {
        return item;
    }
}

// 实现泛型接口(保留泛型)
class GenericContainer<T> implements Container<T> {
    private T item;

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

    public T get() {
        return item;
    }
}

public class Test {
    public static void main(String[] args) {
        StringContainer sc = new StringContainer();
        sc.add("Hello");
        System.out.println(sc.get());

        GenericContainer<Integer> gc = new GenericContainer<>();
        gc.add(100);
        System.out.println(gc.get());
    }
}

总结:

  • 泛型接口可以在实现时指定具体类型,也可以在实现类中继续使用泛型。

3. 泛型的类型限制(上界 & 下界)

3.1 上界通配符 <? extends T>

用于限制类型必须是 T 或 T 的子类,常用于 读取数据 的场景:

import java.util.*;

public class Test {
    public static void printList(List<? extends Number> list) {
        for (Number n : list) {
            System.out.println(n);
        }
    }

    public static void main(String[] args) {
        List<Integer> intList = Arrays.asList(1, 2, 3);
        List<Double> doubleList = Arrays.asList(3.14, 2.71);

        printList(intList);  // OK,Integer 是 Number 子类
        printList(doubleList); // OK,Double 是 Number 子类
    }
}

规则:

  • ? extends Number 表示泛型类型必须是 Number 或其子类。
  • 但不能向 list 添加新元素,因为 Java 不能确定具体的子类类型。

3.2 下界通配符 <? super T>

用于限制类型必须是 T 或 T 的父类,常用于 写入数据 的场景:

import java.util.*;

public class Test {
    public static void addNumbers(List<? super Integer> list) {
        list.add(10);
        list.add(20);
        // list.add(3.14); // 编译错误
    }

    public static void main(String[] args) {
        List<Number> numList = new ArrayList<>();
        addNumbers(numList); // OK,Number 是 Integer 父类

        List<Object> objList = new ArrayList<>();
        addNumbers(objList); // OK,Object 是 Integer 父类
    }
}

规则:

  • ? super Integer 表示泛型类型必须是 Integer 或其父类(如 NumberObject)。
  • 可以向 list 添加 Integer 或其子类,但不能保证读取时的具体类型

4. 类型擦除(Type Erasure)

Java 的泛型是类型擦除的,即编译时检查类型,运行时移除泛型信息

public class Test {
    public static void main(String[] args) {
        List<String> list1 = new ArrayList<>();
        List<Integer> list2 = new ArrayList<>();

        System.out.println(list1.getClass() == list2.getClass()); // true
    }
}

注意:

  • List<String>List<Integer> 在编译后都变成 List<Object>,无法在运行时区分。

5. 不能使用泛型的情况

  • 基本数据类型不能作为泛型类型(需使用包装类,如 int -> Integer)。
  • 静态方法或变量不能使用类的泛型参数
  • 不能创建泛型数组(如 T[] array = new T[10];,需使用 Object[])。
  • 不能实例化泛型类型(如 new T();)。

6. 总结

泛型特性作用
泛型类定义时使用泛型,提高代码复用性
泛型方法方法级别的泛型,独立于类
泛型接口使接口支持泛型类型
上界 <? extends T>适用于读取数据,不能写入
下界 <? super T>适用于写入数据,不能安全读取
类型擦除泛型信息在运行时被擦除

更多详细内容请关注其他相关文章。

发表回复 0

Your email address will not be published. Required fields are marked *