【Java】泛型的使用案例

发布于:2025-08-04 ⋅ 阅读:(19) ⋅ 点赞:(0)

Java 泛型(Generics)是 JDK 5 引入的核心特性,用于在编译阶段提供类型安全检查,避免运行时的ClassCastException,同时实现代码复用。其核心思想是 “参数化类型”—— 将类型定义为参数,在使用时动态指定具体类型。以下详细说明泛型的使用规则、核心概念及实战案例。

【一】泛型的基本定义(类、接口、方法)

泛型可作用于类、接口、方法,分别称为泛型类、泛型接口、泛型方法。

【1】泛型类:类定义时声明类型参数

(1)定义规则:

在类名后添加<类型参数>(如),类型参数可在类的字段、方法参数、返回值中使用。

(2)命名规范:

类型参数通常用单个大写字母表示(约定):
T(Type):表示任意类型
E(Element):表示集合中的元素类型
K(Key):表示键类型
V(Value):表示值类型

(3)案例:泛型类Box(存储任意类型的容器)

// 泛型类定义:T为类型参数
public class Box<T> {
    private T content; // 使用泛型参数作为字段类型

    // 构造方法:参数类型为T
    public Box(T content) {
        this.content = content;
    }

    // 方法返回值为T
    public T getContent() {
        return content;
    }

    // 方法参数为T
    public void setContent(T content) {
        this.content = content;
    }
}

// 使用泛型类:指定具体类型(如String、Integer)
public class Test {
    public static void main(String[] args) {
        // 存储字符串:类型参数为String
        Box<String> stringBox = new Box<>("Hello");
        String str = stringBox.getContent(); // 无需强制类型转换

        // 存储整数:类型参数为Integer
        Box<Integer> intBox = new Box<>(123);
        Integer num = intBox.getContent();
    }
}

(4)效果:

编译时检查类型一致性,若向stringBox存入Integer,会直接编译报错(如stringBox.setContent(123) → 编译错误)。

【2】泛型接口:接口定义时声明类型参数

(1)定义规则:

与泛型类类似,在接口名后添加<类型参数>,实现类需指定具体类型或继续保留泛型。

(2)案例:泛型接口Generator(生成器)

// 泛型接口:定义一个生成T类型对象的方法
public interface Generator<T> {
    T generate();
}

// 实现类1:指定具体类型(如String)
public class StringGenerator implements Generator<String> {
    @Override
    public String generate() {
        return "Generated string";
    }
}

// 实现类2:保留泛型(泛型实现类)
public class NumberGenerator<T extends Number> implements Generator<T> {
    private T seed;

    public NumberGenerator(T seed) {
        this.seed = seed;
    }

    @Override
    public T generate() {
        return seed; // 简化示例,实际可生成复杂数字
    }
}

// 使用
public class Test {
    public static void main(String[] args) {
        Generator<String> strGen = new StringGenerator();
        String s = strGen.generate();

        Generator<Integer> intGen = new NumberGenerator<>(100);
        Integer num = intGen.generate();
    }
}

【3】泛型方法:方法定义时声明类型参数

(1)定义规则:

在方法返回值前添加<类型参数>(如),该类型参数仅作用于当前方法,与类的泛型无关(即使类不是泛型类,也可定义泛型方法)。

(2)案例:泛型方法swap(交换数组元素)

public class ArrayUtil {
    // 泛型方法:<T>声明类型参数,参数为T[]数组
    public static <T> void swap(T[] array, int i, int j) {
        if (i < 0 || j < 0 || i >= array.length || j >= array.length) {
            return;
        }
        T temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
}

// 使用
public class Test {
    public static void main(String[] args) {
        // 交换String数组
        String[] strs = {"A", "B", "C"};
        ArrayUtil.swap(strs, 0, 2); // 交换后:["C", "B", "A"]

        // 交换Integer数组
        Integer[] nums = {1, 2, 3};
        ArrayUtil.swap(nums, 0, 1); // 交换后:[2, 1, 3]
    }
}

关键:泛型方法的类型参数由调用时的实际参数类型自动推断(如swap(strs, …)推断T为String)。

【二】泛型的核心使用规则

【1】类型参数必须是引用类型,不能是基本类型

泛型仅支持引用类型(如Integer、String),不支持int、char等基本类型。若需使用基本类型,需用其包装类。

// 错误:基本类型不能作为泛型参数
Box<int> intBox = new Box<>(123); // 编译错误

// 正确:使用包装类
Box<Integer> intBox = new Box<>(123); // 正确

【2】泛型参数在编译时会被 “类型擦除”

Java 泛型是 “编译时特性”,编译后字节码中泛型信息会被擦除,替换为 “原生类型”(Raw Type)。例如:
(1)Box 擦除后为 Box(原生类型)
(2)List 擦除后为 List

类型擦除的影响:
(1)运行时无法获取泛型的具体类型(如instanceof List 编译错误)。
(2)泛型数组不能直接实例化(如new T[10] 编译错误,需通过反射间接创建)。

【3】泛型类 / 接口不能定义静态泛型字段

泛型参数属于实例级别的信息(每个实例的类型参数可能不同),而静态字段属于类级别,无法关联具体的泛型参数。

public class Box<T> {
    // 错误:静态字段不能使用泛型参数
    private static T staticContent; // 编译错误
}

【4】泛型类的继承规则:泛型不具有协变性

若A是B的子类,Box不是Box的子类(泛型不支持协变),这是为了类型安全。

// Integer是Number的子类,但List<Integer>不是List<Number>的子类
List<Integer> intList = new ArrayList<>();
List<Number> numList = intList; // 编译错误(禁止协变)

为什么禁止? 若允许,可能导致向numList添加Double(合法,因Double是Number),但intList实际存储的是Integer,运行时会报错。

【5】泛型通配符(?):解决泛型协变问题

通配符用于表示 “未知类型”,分为无界通配符、上界通配符、下界通配符,用于灵活处理泛型的继承关系。

(1)无界通配符<?>:表示任意类型

适用于 “只读取,不写入” 的场景(如打印任意类型的集合)。

// 无界通配符:接收任意类型的List
public static void printList(List<?> list) {
    for (Object obj : list) {
        System.out.println(obj);
    }
}

// 使用
List<String> strList = Arrays.asList("A", "B");
List<Integer> intList = Arrays.asList(1, 2);
printList(strList); // 正确
printList(intList); // 正确

限制:无法向List<?>添加任意元素(除null),因类型未知(如list.add(“a”) 编译错误)。

(2)上界通配符<? extends T>:表示 “T 及其子类”

适用于 “只读取” 的场景(如获取集合中元素的最大值,元素必须是Comparable的子类)。

// 上界通配符:元素必须是Number及其子类(Integer、Double等)
public static double sum(List<? extends Number> numbers) {
    double total = 0;
    for (Number num : numbers) {
        total += num.doubleValue(); // 可安全调用Number的方法
    }
    return total;
}

// 使用
List<Integer> ints = Arrays.asList(1, 2, 3);
List<Double> doubles = Arrays.asList(1.5, 2.5);
System.out.println(sum(ints)); // 6.0
System.out.println(sum(doubles)); // 4.0

限制:无法向List<? extends Number>添加元素(除null),因无法确定具体类型(可能是Integer或Double)。

(3)下界通配符<? super T>:表示 “T 及其父类”

适用于 “只写入” 的场景(如向集合添加元素,元素必须是T或其子类)。

// 下界通配符:元素必须是Integer及其父类(Number、Object)
public static void addIntegers(List<? super Integer> list) {
    list.add(1); // 正确:Integer是下界类型
    list.add(2);
    list.add(new Integer(3));
}

// 使用
List<Number> nums = new ArrayList<>();
List<Object> objs = new ArrayList<>();
addIntegers(nums); // 正确:Number是Integer的父类
addIntegers(objs); // 正确:Object是Integer的父类
System.out.println(nums); // [1, 2, 3]

限制:从List<? super Integer>读取元素时,只能用Object接收(因父类类型不确定)。

【三】泛型的高级应用案例

【1】泛型与集合框架(JDK 内置泛型)

Java 集合框架(如ArrayList、HashMap)是泛型的典型应用,通过泛型确保集合中元素类型的一致性。

// ArrayList<String>:只能存储String
List<String> strList = new ArrayList<>();
strList.add("Java");
strList.add(123); // 编译错误(类型不匹配)

// HashMap<String, Integer>:键为String,值为Integer
Map<String, Integer> map = new HashMap<>();
map.put("age", 20);
map.put("score", "90"); // 编译错误(值类型应为Integer)

【2】泛型与反射(创建泛型实例)

由于类型擦除,无法直接new T(),但可通过反射的Class创建泛型实例。

public class ObjectFactory {
    // 泛型方法:通过Class对象创建T类型实例
    public static <T> T createInstance(Class<T> clazz) throws Exception {
        return clazz.getConstructor().newInstance(); // 调用无参构造器
    }
}

// 使用
public class Test {
    public static void main(String[] args) throws Exception {
        String str = ObjectFactory.createInstance(String.class);
        Integer num = ObjectFactory.createInstance(Integer.class);
    }
}

【3】泛型与通配符组合(边界限定)

通过 “上界 + 下界” 组合,实现更灵活的类型控制。例如:复制集合元素(源集合读取,目标集合写入)。

// 从源集合(读取)复制到目标集合(写入)
public static <T> void copy(List<? extends T> source, List<? super T> target) {
    for (T item : source) {
        target.add(item); // 安全写入:target可接收T类型
    }
}

// 使用
List<Integer> ints = Arrays.asList(1, 2, 3);
List<Number> nums = new ArrayList<>();
copy(ints, nums); // 正确:source是Integer(extends Number),target是Number(super Integer)
System.out.println(nums); // [1, 2, 3]

【四】泛型的常见错误与解决方案

【五】总结

Java 泛型的核心价值是编译时类型安全和代码复用,其使用规则可归纳为:
(1)泛型类 / 接口 / 方法通过<类型参数>声明,类型参数需为引用类型。
(2)类型擦除导致运行时无泛型信息,需避免依赖运行时泛型类型的操作。
(3)通配符(<?>、<? extends T>、<? super T>)解决泛型协变问题,分别适用于 “任意类型”“读取场景”“写入场景”。
合理使用泛型可显著减少类型转换代码,降低运行时错误风险,是 Java 开发中不可或缺的特性。


网站公告

今日签到

点亮在社区的每一天
去签到