Java 泛型 (Generics)

泛型是 Java 5 引入的重要特性,它允许在定义类、接口和方法时使用类型参数(type parameters),从而使代码可以适用于多种类型,同时提供编译时类型安全检查。

泛型的基本概念

  1. 泛型类
java 复制代码
public class Box<T> {
    private T content;
    
    public void setContent(T content) {
        this.content = content;
    }
    
    public T getContent() {
        return content;
    }
}

// 使用

java 复制代码
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello");
String value = stringBox.getContent(); // 无需类型转换
  1. 泛型接口
    java
    public interface Pair<K, V> {
    K getKey();
    V getValue();
    }
  2. 泛型方法
    java
    public class Util {
    public static T getMiddle(T[] array) {
    return array[array.length / 2];
    }
    }

// 使用

String[] names = {"Alice", "Bob", "Charlie"};

String middle = Util.getMiddle(names); // 类型推断可省略

泛型的类型参数命名约定

E - 元素 (Element),用于集合框架

K - 键 (Key)

V - 值 (Value)

N - 数字 (Number)

T - 类型 (Type)

S, U, V 等 - 第二、第三、第四类型

类型通配符

  1. 无界通配符 (?)
    java
    public void printList(List<?> list) {
    for (Object elem : list) {
    System.out.println(elem);
    }
    }
  2. 上界通配符 (? extends Type)
    java
    public double sumOfList(List<? extends Number> list) {
    double sum = 0.0;
    for (Number num : list) {
    sum += num.doubleValue();
    }
    return sum;
    }
  3. 下界通配符 (? super Type)
    java
    public void addNumbers(List<? super Integer> list) {
    for (int i = 1; i <= 5; i++) {
    list.add(i);
    }
    }
    类型擦除
    Java 泛型是通过类型擦除实现的,这意味着泛型信息只在编译时存在,运行时会被擦除:

泛型类型参数会被替换为它们的边界或 Object

必要时插入类型转换

桥方法被生成以保持多态

java

// 编译前

List list = new ArrayList<>();

// 编译后(类型擦除)

List list = new ArrayList();

泛型的限制

不能使用基本类型作为类型参数:

java

List // 错误,必须使用 List

不能创建泛型数组:

java

T[] array = new T[10]; // 错误

不能实例化类型参数:

java

public T create() {

return new T(); // 错误

}

不能在静态上下文中使用类的类型参数:

java

public class Foo {

private static T value; // 错误

}

不能抛出或捕获泛型类的实例:

java

try {

// ...

} catch (T ex) { // 错误

// ...

}

泛型与继承

泛型类即使类型参数有继承关系,它们之间也没有继承关系:

java

List 不是 List 的子类

通配符与继承

java

List<? extends Number> 可以接受 List 或 List

List<? super Integer> 可以接受 List 或 List 或 List

PECS 原则 (Producer-Extends, Consumer-Super)

Producer (生产者,提供数据): 使用 extends

Consumer (消费者,接收数据): 使用 super

java

// 生产者示例

public static void copy(List<? extends T> source, List<? super T> destination) {

for (T item : source) {

destination.add(item);

}

}

范型方法特别说明:

在 Java 泛型方法中,如果参数是泛型类型(如 T),而该方法需要处理具体类型(如 String 和 Integer)的特定逻辑,可以通过以下几种方式实现:

方法 1:使用 instanceof 进行类型检查

在泛型方法内部,可以通过 instanceof 检查泛型参数的具体类型,然后执行不同的逻辑。

示例代码

java

public class GenericMethodExample {

复制代码
// 泛型方法
public static <T> void process(T input) {
    if (input instanceof String) {
        // 处理 String 类型
        String str = (String) input;
        System.out.println("String: " + str.toUpperCase());
    } else if (input instanceof Integer) {
        // 处理 Integer 类型
        Integer num = (Integer) input;
        System.out.println("Integer: " + (num * 2));
    } else {
        // 其他类型
        System.out.println("Unknown type: " + input);
    }
}

public static void main(String[] args) {
    process("hello");  // 输出: String: HELLO
    process(123);      // 输出: Integer: 246
    process(3.14);     // 输出: Unknown type: 3.14
}

}

说明

instanceof 检查泛型参数的具体类型。

需要手动强制类型转换((String) input)。

适用于少量已知类型的情况,但如果类型很多,代码会变得冗长。

方法 2:方法重载(推荐)

如果逻辑差异较大,可以直接定义多个重载方法,而不是使用泛型方法。

示例代码

java

public class OverloadExample {

复制代码
// 处理 String
public static void process(String input) {
    System.out.println("String: " + input.toUpperCase());
}

// 处理 Integer
public static void process(Integer input) {
    System.out.println("Integer: " + (input * 2));
}

// 处理其他类型
public static <T> void process(T input) {
    System.out.println("Unknown type: " + input);
}

public static void main(String[] args) {
    process("hello");  // 调用 String 版本
    process(123);      // 调用 Integer 版本
    process(3.14);     // 调用泛型版本
}

}

说明

更符合 Java 的类型安全机制。

避免了 instanceof 和强制类型转换。

代码更清晰,推荐使用。

方法 3:使用 Class 参数(反射)

如果需要动态处理类型,可以传入 Class 参数,结合反射进行操作。

示例代码

java

public class ClassParamExample {

复制代码
public static <T> void process(T input, Class<T> type) {
    if (type == String.class) {
        String str = (String) input;
        System.out.println("String: " + str.toUpperCase());
    } else if (type == Integer.class) {
        Integer num = (Integer) input;
        System.out.println("Integer: " + (num * 2));
    } else {
        System.out.println("Unknown type: " + input);
    }
}

public static void main(String[] args) {
    process("hello", String.class);  // 输出: String: HELLO
    process(123, Integer.class);     // 输出: Integer: 246
    process(3.14, Double.class);     // 输出: Unknown type: 3.14
}

}

说明

适用于需要动态类型检查的场景。

仍然需要强制类型转换,但避免了 instanceof 的冗余检查。

方法 4:使用策略模式(面向对象)

如果逻辑复杂,可以使用策略模式,将不同类型的处理逻辑封装成不同的类。

示例代码

java

interface Processor {

void process(T input);

}

class StringProcessor implements Processor {

@Override

public void process(String input) {

System.out.println("String: " + input.toUpperCase());

}

}

class IntegerProcessor implements Processor {

@Override

public void process(Integer input) {

System.out.println("Integer: " + (input * 2));

}

}

public class StrategyPatternExample {

复制代码
public static <T> void process(T input, Processor<? super T> processor) {
    processor.process(input);
}

public static void main(String[] args) {
    process("hello", new StringProcessor());  // 输出: String: HELLO
    process(123, new IntegerProcessor());     // 输出: Integer: 246
}

}

说明

适用于复杂逻辑,符合开闭原则(OCP)。

扩展性强,但代码量较多。

推荐方案

如果类型较少且明确 → 方法重载(最简洁、最安全)。

如果需要动态处理 → Class 参数 或 策略模式。

避免滥用 instanceof,除非逻辑非常简单。

希望这些方法能帮助你灵活处理泛型参数! 🚀

总结

Java 泛型提供了编译时类型安全,减少了运行时类型转换错误,使代码更清晰、更安全。理解类型擦除、通配符和 PECS 原则对于有效使用泛型至关重要。

相关推荐
摇滚侠14 分钟前
面试实战 问题二十三 如何判断索引是否生效,什么样的sql会导致索引失效
java
悟纤21 分钟前
当生产环境卡成 PPT:Spring Boot 线程 Dump 捉妖指南 - 第544篇
java·spring boot·后端
江影影影2 小时前
Spring Boot 2.6.0+ 循环依赖问题及解决方案
java·spring boot·后端
chilavert3183 小时前
技术演进中的开发沉思-62 DELPHI VCL系列:VCL下的设计模式
开发语言·delphi
Jonathan丶BNTang3 小时前
IntelliJ IDEA 2025.2 重磅发布
java·ide·intellij-idea
tanxiaomi4 小时前
学习分库分表的前置知识:高可用系统架构理论与实践
java·mysql·spring cloud·系统架构·springboot
晨非辰4 小时前
#C语言——刷题攻略:牛客编程入门训练(六):运算(三)-- 涉及 辗转相除法求最大公约数
c语言·开发语言·经验分享·学习·学习方法·visual studio
m0_741574754 小时前
tomcat
java·tomcat
跟着珅聪学java5 小时前
Redis 的使用场景
java
钢铁男儿5 小时前
C# 异步编程(计时器)
开发语言·c#