一、什么是泛型?一个生活中的例子

想象一个"万能盒子"

// 没有泛型之前:只能放特定东西的盒子
class AppleBox {
    private Apple apple;
    public void set(Apple apple) { this.apple = apple; }
    public Apple get() { return apple; }
}

class BookBox {
    private Book book;
    public void set(Book book) { this.book = book; }
    public Book get() { return book; }
}

// 每个类型都要写一个盒子,太麻烦了!

泛型就是"填空"的思想

// 泛型:像一个可以填空的盒子
class Box<T> {  // T 就是"空位",你要放什么类型?
    private T item;  // 这个空位可以是任何类型
    
    public void set(T item) {
        this.item = item;
    }
    
    public T get() {
        return item;
    }
}

// 使用时填空
Box<Apple> appleBox = new Box<>();  // T 填成 Apple
appleBox.set(new Apple());          // 只能放 Apple
Apple apple = appleBox.get();        // 取出就是 Apple,不用转换

Box<Book> bookBox = new Box<>();    // T 填成 Book
bookBox.set(new Book());             // 只能放 Book
Book book = bookBox.get();           // 取出就是 Book

二、为什么要用泛型?

没有泛型之前的问题

// 问题1:需要强制类型转换
List list = new ArrayList();
list.add("Hello");
list.add(123);

String str = (String) list.get(0);  // 必须强制转换
Integer num = (Integer) list.get(1); // 容易出错

// 如果不小心
String wrong = (String) list.get(1); // 运行时错误!ClassCastException

使用泛型后

// 问题解决:类型安全,不用强制转换
List<String> list = new ArrayList<>();
list.add("Hello");        // ✅ 只能加 String
// list.add(123);          // ❌ 编译错误,不能加 Integer

String str = list.get(0);  // ✅ 直接得到 String,不用转换

三、泛型的三种用法

1. 泛型类 - 类级别的"填空"

// 定义一个泛型类
public class Pair<K, V> {  // 两个空位:K 和 V
    private K key;
    private V value;
    
    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }
    
    public K getKey() { return key; }
    public V getValue() { return value; }
}

// 使用
Pair<String, Integer> pair = new Pair<>("age", 25);
String key = pair.getKey();    // key 是 String
Integer value = pair.getValue(); // value 是 Integer

Pair<Integer, String> pair2 = new Pair<>(1, "One");

2. 泛型方法 - 方法级别的"填空"

public class GenericMethod {
    
    // 泛型方法:在返回值前声明 <T>
    public <T> T echo(T thing) {
        System.out.println("收到:" + thing);
        return thing;  // 返回什么类型,T 就是什么类型
    }
    
    // 更实用的例子:数组转List
    public <T> List<T> arrayToList(T[] array) {
        List<T> list = new ArrayList<>();
        for (T item : array) {
            list.add(item);
        }
        return list;
    }
}

// 使用
GenericMethod gm = new GenericMethod();
String result = gm.echo("Hello");  // T 自动推断为 String
Integer num = gm.echo(123);        // T 自动推断为 Integer

String[] arr = {"A", "B", "C"};
List<String> list = gm.arrayToList(arr);  // String 类型的 List

3. 泛型接口 - 接口的"填空"

// 泛型接口
public interface Comparator<T> {
    int compare(T o1, T o2);  // 比较两个 T 类型的对象
}

// 实现时可以指定具体类型
class StringComparator implements Comparator<String> {
    @Override
    public int compare(String s1, String s2) {
        return s1.length() - s2.length();
    }
}

class IntegerComparator implements Comparator<Integer> {
    @Override
    public int compare(Integer i1, Integer i2) {
        return i1 - i2;
    }
}

四、通配符:处理不确定的类型

1. 无界通配符 ? - “任何类型”

public class WildcardDemo {
    
    // 可以打印任何类型的 List
    public static void printList(List<?> list) {  // ? 表示任何类型
        for (Object item : list) {
            System.out.print(item + " ");
        }
        System.out.println();
    }
}

// 使用
List<String> strings = Arrays.asList("A", "B", "C");
List<Integer> numbers = Arrays.asList(1, 2, 3);

WildcardDemo.printList(strings);  // ✅ 可以
WildcardDemo.printList(numbers);  // ✅ 可以

2. 上界通配符 ? extends - “T或T的子类”

// ? extends Number 表示:Number 或 Number 的子类
public static double sum(List<? extends Number> list) {
    double total = 0;
    for (Number num : list) {
        total += num.doubleValue();
    }
    return total;
}

// 使用
List<Integer> ints = Arrays.asList(1, 2, 3);
List<Double> doubles = Arrays.asList(1.5, 2.5, 3.5);

double sum1 = sum(ints);     // ✅ Integer 是 Number 的子类
double sum2 = sum(doubles);  // ✅ Double 是 Number 的子类
// sum(Arrays.asList("A", "B"));  // ❌ String 不是 Number 的子类

3. 下界通配符 ? super - “T或T的父类”

// ? super Integer 表示:Integer 或 Integer 的父类
public static void addNumbers(List<? super Integer> list) {
    list.add(1);  // 可以添加 Integer
    list.add(2);
    list.add(3);
}

// 使用
List<Number> numbers = new ArrayList<>();
List<Object> objects = new ArrayList<>();
List<Integer> integers = new ArrayList<>();

addNumbers(numbers);   // ✅ Number 是 Integer 的父类
addNumbers(objects);   // ✅ Object 是 Integer 的父类
addNumbers(integers);  // ✅ Integer 本身
// addNumbers(Arrays.asList("A"));  // ❌ String 不是 Integer 的父类

五、类型擦除:泛型的"障眼法"

泛型只在编译时有效,运行时会被擦除:

public class TypeErasure {
    public static void main(String[] args) {
        List<String> strings = new ArrayList<>();
        List<Integer> integers = new ArrayList<>();
        
        // 运行时,它们都是 ArrayList,泛型信息被擦除了
        System.out.println(strings.getClass() == integers.getClass());  // true
        
        // 不能这样写(编译错误)
        // if (strings instanceof List<String>) { }  // ❌
    }
}

// 编译前:
List<String> list = new ArrayList<>();
list.add("Hello");
String s = list.get(0);

// 编译后(类型擦除):
List list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0);  // 自动添加了强制转换

六、实际应用场景

1. 集合框架 - 最常用

// 各种集合都用了泛型
List<String> list = new ArrayList<>();
Set<Integer> set = new HashSet<>();
Map<String, User> map = new HashMap<>();

list.add("Hello");
String item = list.get(0);  // 类型安全,不用转换

2. 自定义返回结果

// 通用的返回结果类
public class Result<T> {
    private int code;
    private String message;
    private T data;  // 数据可以是任何类型
    
    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.code = 200;
        result.message = "success";
        result.data = data;
        return result;
    }
    
    public static <T> Result<T> error(String message) {
        Result<T> result = new Result<>();
        result.code = 500;
        result.message = message;
        return result;
    }
    
    public T getData() { return data; }
}

// 使用
Result<User> userResult = Result.success(new User());
User user = userResult.getData();  // 直接得到 User,不用转换

Result<List<String>> listResult = Result.success(Arrays.asList("A", "B"));
List<String> list = listResult.getData();  // 直接得到 List<String>

3. DAO 层基类

// 通用的 DAO 基类
public abstract class BaseDao<T> {
    private Class<T> clazz;
    
    public BaseDao() {
        // 获取 T 的真实类型
        ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass();
        clazz = (Class<T>) type.getActualTypeArguments()[0];
    }
    
    public T findById(Long id) {
        // 根据 id 查询
        // 返回 T 类型
        return null;
    }
    
    public List<T> findAll() {
        // 查询所有
        return new ArrayList<>();
    }
    
    public void save(T entity) {
        // 保存实体
    }
}

// 具体的 DAO
public class UserDao extends BaseDao<User> {
    // 继承了所有泛型方法
}

// 使用
UserDao userDao = new UserDao();
User user = userDao.findById(1L);  // 直接返回 User 类型
List<User> users = userDao.findAll();  // 直接返回 List<User>

4. 泛型方法结合通配符

public class CollectionUtils {
    
    // 从源集合复制到目标集合
    public static <T> void copy(List<? extends T> src, List<? super T> dest) {
        for (T item : src) {  // src 可以读(extends)
            dest.add(item);    // dest 可以写(super)
        }
    }
}

// 使用
List<Integer> src = Arrays.asList(1, 2, 3);
List<Number> dest = new ArrayList<>();

CollectionUtils.copy(src, dest);  // Integer 到 Number,安全复制

七、常见的泛型命名约定

字母含义示例
TType(类型)List<T>
EElement(元素)List<E> 集合中的元素
KKey(键)Map<K, V>
VValue(值)Map<K, V>
NNumber(数字)Number
RResult(结果)Result<R>

八、总结

泛型的三大好处

// 1. 类型安全
List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(123);  // ❌ 编译时就报错

// 2. 消除强制转换
String s = list.get(0);  // ✅ 直接得到 String

// 3. 通用代码
class Box<T> {  // 可以放任何类型
    private T item;
    // ...
}

记住这个比喻

  • 泛型 = 填空题
  • T = 空格(等着被填)
  • 使用泛型 = 填空(指定具体类型)
  • 通配符? = “任意答案”(不确定是什么)

一句话总结:泛型就是让你写出更通用、更安全的代码,像填空题一样,把类型作为参数传进去!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐