java泛型讲解(通俗易懂版)
·
一、什么是泛型?一个生活中的例子
想象一个"万能盒子"
// 没有泛型之前:只能放特定东西的盒子
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,安全复制
七、常见的泛型命名约定
| 字母 | 含义 | 示例 |
|---|---|---|
| T | Type(类型) | List<T> |
| E | Element(元素) | List<E> 集合中的元素 |
| K | Key(键) | Map<K, V> |
| V | Value(值) | Map<K, V> |
| N | Number(数字) | Number |
| R | Result(结果) | 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 = 空格(等着被填)
- 使用泛型 = 填空(指定具体类型)
- 通配符? = “任意答案”(不确定是什么)
一句话总结:泛型就是让你写出更通用、更安全的代码,像填空题一样,把类型作为参数传进去!
更多推荐




所有评论(0)