首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >泛型的如何使用

泛型的如何使用

作者头像
Han.miracle
发布2025-12-22 14:10:17
发布2025-12-22 14:10:17
10
举报

泛型的定义

泛型(Generics)是 Java 等编程语言中一种参数化类型的特性,它允许在定义类、接口或方法时,通过类型参数来表示未知类型,直到使用时再指定具体类型。

泛型通俗来讲就是,适用于多种的类型

注意:

泛型不具有继承性,但是数据具有继承性

泛型的 “不可变性”(无继承性)与数据本身 “可继承性” 的对立统一

泛型的核心作用:

代码复用 无需为不同类型重复编写逻辑相同的代码。例如,一个泛型容器类List<T>可以存储IntegerString等任意类型,而无需分别定义IntListStringList

类型安全 编译时就会检查类型匹配,避免运行时出现ClassCastException。例如,向List<Integer>中添加String会直接编译报错,而不是在运行时崩溃。

消除强制类型转换

使用泛型后,从容器中获取元素时无需手动强转。例如:

代码语言:javascript
复制
// 非泛型(需要强转)
List list = new ArrayList();
list.add(10);
Integer num = (Integer) list.get(0); // 必须强转

// 泛型(无需强转)
List<Integer> list = new ArrayList<>();
list.add(10);
Integer num = list.get(0); // 直接获取Integer类型

泛型的三种形式

1. 泛型类(Generic Class)

在类定义时声明类型参数,作用于整个类的成员变量、方法参数和返回值。 语法class 类名<类型参数列表> { ... }

代码语言:javascript
复制
// 多类型参数:T代表键,V代表值
class Pair<T, V> {
    private T key;
    private V value;
    
    public Pair(T key, V value) {
        this.key = key;
        this.value = value;
    }
    
    public T getKey() { return key; }
    public V getValue() { return value; }
}

// 使用:指定T为String,V为Integer
Pair<String, Integer> pair = new Pair<>("age", 20);
String key = pair.getKey();  // String类型
Integer value = pair.getValue();  // Integer类型
2. 泛型接口(Generic Interface)

与泛型类类似,在接口定义时声明类型参数,实现类需指定具体类型或继续保留泛型。 语法interface 接口名<类型参数列表> { ... }

代码语言:javascript
复制
// 泛型接口:定义“可比较”的行为
interface Comparable<T> {
    int compareTo(T other);  // 比较当前对象与另一个T类型对象
}

// 实现类:指定T为Integer
class IntegerComparable implements Comparable<Integer> {
    private int num;
    
    @Override
    public int compareTo(Integer other) {
        return Integer.compare(num, other);
    }
}

泛型接口实现的两种方法:

1.实现类给出具体的类型

代码语言:javascript
复制
// 泛型接口
interface Repository<T> {
    void save(T data);
    T findById(int id);
}

// 实现时直接指定具体类型(这里是 String)
class StringRepository implements Repository<String> {
    @Override
    public void save(String data) {
        System.out.println("保存字符串:" + data);
    }

    @Override
    public String findById(int id) {
        return "固定字符串"; // 简单演示
    }
}

public class Demo1 {
    public static void main(String[] args) {
        Repository<String> repo = new StringRepository();
        repo.save("Hello Generics");
        System.out.println(repo.findById(1));
    }
}

2.实现类继续延续泛型,创建实现类对象再确定类型

代码语言:javascript
复制
// 泛型接口
interface Repository<T> {
    void save(T data);
    T findById(int id);
}

// 实现类仍然带泛型参数
class MemoryRepository<T> implements Repository<T> {
    @Override
    public void save(T data) {
        System.out.println("保存数据:" + data);
    }

    @Override
    public T findById(int id) {
        return null; // 简单演示
    }
}

public class Demo2 {
    public static void main(String[] args) {
        // 这里才决定类型
        Repository<Integer> intRepo = new MemoryRepository<>();
        intRepo.save(100);

        Repository<Double> doubleRepo = new MemoryRepository<>();
        doubleRepo.save(3.14);
    }
}

3. 泛型方法(Generic Method)

在单个方法中声明类型参数,作用范围仅限当前方法,与类是否泛型无关。 语法修饰符 <类型参数列表> 返回值类型 方法名(参数列表) { ... }

代码语言:javascript
复制
class ArrayUtils {
    // 泛型方法:交换数组中两个位置的元素
    public static <T> void swap(T[] array, int i, int j) {
        T temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
}

// 使用:无需定义泛型类,直接调用泛型方法
String[] strArray = {"A", "B", "C"};
ArrayUtils.swap(strArray, 0, 2);  // 自动适配String类型

Integer[] intArray = {1, 2, 3};
ArrayUtils.swap(intArray, 0, 2);  // 自动适配Integer类型

类型参数的约束(Bounds)

泛型允许通过extendssuper限制类型参数的范围,避免 “无限制的泛型” 导致的功能局限。

1. 上界约束(extends

限制类型参数必须是指定类的子类或指定接口的实现类。 语法<T extends 上界类型>

  • 上界可以是类(最多 1 个,必须放在首位)+ 接口(多个,用&连接)
代码语言:javascript
复制
// T必须是Number的子类,且实现Comparable接口
class Calculator<T extends Number & Comparable<T>> {
    public T max(T a, T b) {
        // 因T实现了Comparable,可调用compareTo方法
        return a.compareTo(b) >= 0 ? a : b;
    }
    
    public double sum(T a, T b) {
        // 因T是Number子类,可调用doubleValue方法
        return a.doubleValue() + b.doubleValue();
    }
}

// 合法:Integer是Number子类且实现Comparable
Calculator<Integer> intCalc = new Calculator<>();
intCalc.max(10, 20);  // 正确
intCalc.sum(10.5, 20.3);  // 错误:Double类型与Integer不匹配

// 非法:String不是Number子类
Calculator<String> strCalc = new Calculator<>();  // 编译报错
2. 下界约束(super

限制类型参数必须是指定类的父类(包括自身)。 语法<T super 下界类型>

通常用于通配符(? super T),限制写入操作的类型范围。

代码语言:javascript
复制
// 向集合中添加Fruit及其子类(如Apple)
public static void addFruits(List<? super Fruit> list) {
    list.add(new Apple());  // 合法:Apple是Fruit子类
    list.add(new Fruit());  // 合法:Fruit是自身
    // list.add(new Food());  // 错误:Food是Fruit父类,超出下界
}

通配符(Wildcards)

通配符 用于表示 “未知类型”,配合extendssuper实现灵活的泛型适配,解决泛型的 “不可变性” 问题(如List<Apple>不是List<Fruit>的子类)。

1. 无界通配符(?

表示任意类型,适用于 “只读取不修改” 且不依赖具体类型的场景。

代码语言:javascript
复制
// 打印任意类型集合的元素
public static void printList(List<?> list) {
    for (Object obj : list) {
        System.out.println(obj);
    }
}

// 可接收List<String>、List<Integer>等任意泛型集合
printList(Arrays.asList("A", "B"));
printList(Arrays.asList(1, 2, 3));
2. 上界通配符(? extends T

表示 “T 及其子类”,适用于 “读取为主” 的场景(能安全获取 T 类型数据,但限制写入)。

代码语言:javascript
复制
// 计算水果重量总和(Fruit及其子类都有weight属性)
public static double sumWeight(List<? extends Fruit> fruits) {
    double total = 0;
    for (Fruit f : fruits) {  // 可安全转型为Fruit
        total += f.getWeight();
    }
    return total;
}

// 合法:List<Apple>、List<Banana>都是Fruit的子类集合
sumWeight(Arrays.asList(new Apple(), new Banana()));
3. 下界通配符(? super T

表示 “T 及其父类”,适用于 “写入为主” 的场景(能安全写入 T 类型数据,但读取时只能作为 Object)

代码语言:javascript
复制
// 向集合中添加整数(Integer及其父类集合均可接收)
public static void addIntegers(List<? super Integer> list) {
    list.add(10);  // 合法:Integer是自身
    list.add(20);
}

// 合法:List<Integer>、List<Number>、List<Object>均可接收
addIntegers(new ArrayList<Integer>());
addIntegers(new ArrayList<Number>());

底层原理:类型擦除(Type Erasure)

Java 泛型是编译时特性,编译后会将泛型信息 “擦除”,仅保留原始类型(Raw Type),运行时不包含泛型参数。

无约束的泛型(如<T>)擦除为Object

有上界的泛型(如<T extends Number>)擦除为上界类型(Number)。

代码语言:javascript
复制
// 编译前:泛型类
class Box<T> {
    private T value;
    public T getValue() { return value; }
}

// 编译后:类型擦除为Object
class Box {
    private Object value;
    public Object getValue() { return value; }
}

影响

运行时无法获取泛型参数的具体类型(如list.getClass()返回ArrayList,而非ArrayList<String>);

不能创建泛型数组(如new T[10]不允许,需通过(T[]) new Object[10]间接实现)。

类型擦除的影响

无法在运行时获取泛型参数类型:如list.getClass()返回ArrayList,而非ArrayList<String>

不能创建泛型数组new T[10]编译报错(需通过(T[]) new Object[10]间接实现);

泛型参数不能用于instanceof判断if (obj instanceof List<String>)编译报错;

可能破坏多态性:子类重写泛型方法时,擦除后的方法签名可能与父类不一致(需桥接方法解决)。

桥接

类型擦除可能导致子类重写的泛型方法与父类方法签名不一致,从而破坏多态性。桥接方法是编译器自动生成的合成方法,用于修复这种签名冲突,保证多态性正常工作。

代码语言:javascript
复制
// 父类:泛型接口
interface Comparable<T> {
    int compareTo(T o);
}

// 子类:实现接口并指定T为Integer
class IntegerComparable implements Comparable<Integer> {
    @Override
    public int compareTo(Integer o) { // 重写方法,参数为Integer
        return 0;
    }
}

类型擦除后:

  • 父类Comparable<T>擦除为Comparable,方法变为int compareTo(Object o)
  • 子类IntegerComparablecompareTo(Integer o)与父类擦除后的方法签名不一致(参数类型不同)。

此时,若没有桥接方法,IntegerComparable实际上并未重写父类的compareTo方法,多态性会失效。

代码语言:javascript
复制
class IntegerComparable implements Comparable {
    // 1. 程序员编写的方法(参数为Integer)
    public int compareTo(Integer o) {
        return 0;
    }

    // 2. 编译器自动生成的桥接方法(参数为Object,匹配父类擦除后的签名)
    public int compareTo(Object o) {
        // 调用实际的compareTo方法,并进行类型转换
        return compareTo((Integer) o);
    }
}
2. 桥接方法的生成

编译器会为IntegerComparable自动生成一个桥接方法,确保与父类擦除后的方法签名一致:

  • 桥接方法的参数类型为Object(父类擦除后的类型);
  • 桥接方法内部会调用程序员编写的实际方法,并自动进行类型转换;
  • 桥接方法是合成方法(synthetic method),在字节码中标记为ACC_SYNTHETIC,通常不会在源码中显示。
3. 桥接方法的验证

//这里是博主找的资料去了解的,反射还没有学

通过反射可以查看类中是否存在桥接方法:

代码语言:javascript
复制
import java.lang.reflect.Method;

public class TestBridge {
    public static void main(String[] args) {
        Method[] methods = IntegerComparable.class.getDeclaredMethods();
        for (Method m : methods) {
            System.out.println("方法名:" + m.getName() + 
                               ",参数类型:" + m.getParameterTypes()[0].getSimpleName() +
                               ",是否桥接方法:" + m.isBridge());
        }
    }
}

// 输出:
// 方法名:compareTo,参数类型:Integer,是否桥接方法:false
// 方法名:compareTo,参数类型:Object,是否桥接方法:true
类型擦除与桥接方法的关系
  1. 因果关系:类型擦除导致泛型方法签名在编译后改变,可能破坏多态性;桥接方法是为了修复这种破坏而产生的解决方案。
  2. 协同作用
    • 类型擦除保证了泛型代码与老版本 Java(无泛型)的兼容性;
    • 桥接方法保证了泛型场景下多态性的正常工作。
  3. 底层实现:两者都是编译器的行为(仅在编译阶段处理),JVM 运行时并不知道泛型或桥接方法的存在。

核心作用与优势

  • 类型安全:编译时检查类型匹配,避免运行时ClassCastException(如向List<Integer>中添加String会直接报错)。
  • 代码复用:一套逻辑适配多种类型(如ArrayList<T>可存储任意类型,无需为每个类型单独定义集合)。
  • 消除强转:从泛型容器中获取元素时无需手动强转(如list.get(0)直接返回String,而非Object)。
  • 精确约束:通过上下界限制类型范围,确保代码能安全调用特定类 / 接口的方法(如T extends Number可安全调用doubleValue())。
  • 泛型不是协变的List<Apple>不是List<Fruit>的子类,即使AppleFruit的子类(需用通配符? extends Fruit解决)。
  • 基本类型不能作为泛型参数:需使用包装类(如List<int>错误,应改为List<Integer>)。
  • 静态成员不能使用泛型参数:泛型参数属于实例级别的类型,静态成员无实例关联,无法直接使用(如static T value;错误)。

多约束

在 Java 泛型中,约束不仅可以是一个类,还可以同时约束多个接口,甚至可以是 “一个类 + 多个接口” 的组合约束。具体规则如下:

基本语法:用 & 连接多个约束

泛型参数的多约束通过 & 符号连接,格式为:

代码语言:javascript
复制
<T extends 类名 & 接口1 & 接口2 & ...>

注意

  • 最多只能有一个类(必须放在第一个位置),后面可以跟多个接口;
  • 不能有多个类(因为 Java 不支持多继承)。
 常见约束组合案例
(1)仅多个接口约束
代码语言:javascript
复制
// T必须同时实现Comparable和Serializable接口
class MyClass<T extends Comparable<T> & java.io.Serializable> {
    public void method(T t) {
        t.compareTo(t); // 可调用Comparable接口方法
        // 可进行序列化操作(因实现了Serializable)
    }
}
(2)一个类 + 多个接口约束
代码语言:javascript
复制
// T必须是Number的子类,且同时实现Comparable和Cloneable接口
class DataProcessor<T extends Number & Comparable<T> & Cloneable> {
    public void process(T data) {
        data.doubleValue(); // 调用Number类的方法
        data.compareTo(data); // 调用Comparable接口方法
        try {
            data.clone(); // 调用Cloneable接口相关方法
        } catch (CloneNotSupportedException e) {
            // 处理异常
        }
    }
}
. 关键规则总结
  • 类只能有一个:若包含类约束,必须放在第一个位置(如 Number 在 & 之前);
  • 接口可以多个:接口之间用 & 连接,无顺序要求(但通常按逻辑排序);
  • 约束的叠加效果T 必须满足所有约束(既是指定类的子类,又实现了所有指定接口)。
代码语言:javascript
复制
// 合法:Integer是Number子类,且实现了Comparable<Integer>和Cloneable
DataProcessor<Integer> intProcessor = new DataProcessor<>();

// 合法:Double符合所有约束
DataProcessor<Double> doubleProcessor = new DataProcessor<>();

// 非法:String不是Number子类,不满足类约束
DataProcessor<String> strProcessor = new DataProcessor<>(); // 编译报错

// 非法:Number未实现Comparable接口(需具体子类实现)
DataProcessor<Number> numProcessor = new DataProcessor<>(); // 编译报错

多重泛型

在 Java 中,“多重泛型” 通常指一个泛型类 / 接口 / 方法声明多个类型参数(即包含多个泛型变量),这些参数可以独立约束、独立使用,用于处理更复杂的多类型关联场景。

例如class Pair<K, V>中,KV就是两个独立的泛型参数,分别代表 “键” 和 “值” 的类型,这种多参数设计是实现Map等数据结构的基础。

多重泛型的基本语法

声明多个泛型参数时,用逗号分隔,格式为:

代码语言:javascript
复制
// 泛型类:多个类型参数用逗号分隔
class 类名<T1, T2, ..., Tn> { ... }

// 泛型接口
interface 接口名<T1, T2, ..., Tn> { ... }

// 泛型方法
修饰符 <T1, T2, ..., Tn> 返回值类型 方法名(参数列表) { ... }

注意:每个类型参数可以单独设置约束(如extends),彼此独立。

带约束的多重泛型

每个泛型参数可以单独设置约束(extends),实现更精确的类型限制。

代码语言:javascript
复制
// 三个泛型参数,分别带不同约束:
// T:必须是Number子类(数值类型)
// U:必须实现Comparable接口(可比较类型)
// V:无约束(任意类型)
class DataHandler<T extends Number, U extends Comparable<U>, V> {
    // 方法1:将数值类型T转换为字符串
    public String convertNumber(T num) {
        return String.valueOf(num);
    }

    // 方法2:比较两个U类型对象的大小
    public U getMax(U a, U b) {
        return a.compareTo(b) >= 0 ? a : b;
    }

    // 方法3:存储任意类型V的数据
    public void storeData(V data) {
        System.out.println("存储数据:" + data);
    }
}

// 使用示例
public class TestDataHandler {
    public static void main(String[] args) {
        // 实例化:
        // T=Double(Number子类),U=String(实现了Comparable),V=Boolean(任意类型)
        DataHandler<Double, String, Boolean> handler = new DataHandler<>();

        // 调用方法:自动匹配类型
        String numStr = handler.convertNumber(3.14);       // Double→String
        String maxStr = handler.getMax("apple", "banana"); // 比较字符串
        handler.storeData(true);                           // 存储Boolean

        System.out.println(numStr);  // 输出:3.14
        System.out.println(maxStr);  // 输出:banana(按字典序"banana" > "apple")
    }
}
泛型方法中的多重参数

泛型方法也可以声明多个类型参数,用于处理方法级别的多类型逻辑。

代码语言:javascript
复制
class ArrayConverter {
    // 泛型方法:声明两个参数T(源类型)和U(目标类型)
    // 功能:将T类型数组转换为U类型数组(通过转换器函数)
    public static <T, U> U[] convert(T[] source, Converter<T, U> converter) {
        U[] result = (U[]) new Object[source.length];
        for (int i = 0; i < source.length; i++) {
            result[i] = converter.convert(source[i]);
        }
        return result;
    }
}

// 转换器接口(也使用多重泛型)
interface Converter<T, U> {
    U convert(T source);
}

// 使用示例
public class TestConverter {
    public static void main(String[] args) {
        // 1. 将Integer数组转换为String数组(如100 → "100元")
        Integer[] numbers = {100, 200, 300};
        String[] strs = ArrayConverter.convert(numbers, n -> n + "元");
        // 输出:[100元, 200元, 300元]

        // 2. 将String数组转换为Integer数组(如"10" → 10)
        String[] numberStrs = {"10", "20", "30"};
        Integer[] ints = ArrayConverter.convert(numberStrs, Integer::parseInt);
        // 输出:[10, 20, 30]
    }
}
多重泛型的核心特点

参数独立性:每个泛型参数(如KV)是独立的,类型可以不同(如K=StringV=Integer),也可以相同(如K=V=String)。

约束独立性:每个参数可单独设置约束,互不影响。例如:

代码语言:javascript
复制
// T必须是Number子类,U必须实现Serializable,V无约束
class Example<T extends Number, U extends java.io.Serializable, V> { ... }

类型安全强化:相比单泛型参数,多重泛型能更精确地描述多类型之间的关联(如 “键 - 值”“源 - 目标”),编译时会分别检查每个参数的类型匹配。

代码复用最大化:一套逻辑可适配多种类型组合,例如Pair<K, V>既可以处理 “字符串 - 整数”,也可以处理 “整数 - 对象” 等任意组合。

常见误区

  • 参数顺序不可混淆:实例化时类型参数的顺序必须与声明一致。例如Pair<K, V>声明为Pair<String, Integer>,则第一个参数必须是K的类型,第二个是V的类型。
  • 避免过度泛型化:并非参数越多越好,过多的泛型参数(如 4 个以上)会导致代码可读性下降,应根据实际需求设计。
  • 通配符的组合使用:多重泛型配合通配符时需注意上下界的匹配,例如:
代码语言:javascript
复制
// 接收"键为任意类型,值为Number子类"的Pair
void processPair(Pair<?, ? extends Number> pair) { ... }
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-09-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 泛型的定义
    • 泛型的核心作用:
  • 泛型的三种形式
  • 类型参数的约束(Bounds)
  • 通配符(Wildcards)
  • 底层原理:类型擦除(Type Erasure)
  • 桥接
    • 类型擦除与桥接方法的关系
  • 核心作用与优势
  • 多约束
    • 基本语法:用 & 连接多个约束
    •  常见约束组合案例
    • . 关键规则总结
  • 多重泛型
    • 多重泛型的基本语法
    • 多重泛型的核心特点
  • 常见误区
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档