问题
泛型中K、T、V、E、Object等分别代表什么含义?
答案
核心概念
泛型中的字母是类型参数(Type Parameter) 的占位符,本质上可以使用任意标识符,但Java约定使用单个大写字母命名,以提高代码可读性。
常用类型参数约定
| 字母 | 含义 | 英文全称 | 典型使用场景 |
|---|---|---|---|
| T | 类型 | Type | 通用类型参数 |
| E | 元素 | Element | 集合元素类型 |
| K | 键 | Key | Map的键类型 |
| V | 值 | Value | Map的值类型 |
| N | 数字 | Number | 数值类型 |
| S, U, V | 第2、3、4个类型 | 2nd, 3rd, 4th type | 多类型参数 |
| ? | 通配符 | Wildcard | 不确定的类型 |
1. T - Type(类型)
最常用的泛型参数,表示任意类型。
// 泛型类
public class Box<T> {
private T data;
public void set(T data) {
this.data = data;
}
public T get() {
return data;
}
}
// 泛型方法
public <T> T getFirst(List<T> list) {
return list.isEmpty() ? null : list.get(0);
}
// 泛型接口
public interface Comparable<T> {
int compareTo(T other);
}
2. E - Element(元素)
集合框架中广泛使用,表示集合中的元素类型。
// List接口
public interface List<E> extends Collection<E> {
boolean add(E e);
E get(int index);
E remove(int index);
}
// Set接口
public interface Set<E> extends Collection<E> {
boolean add(E e);
boolean contains(Object o);
}
// 使用
List<String> names = new ArrayList<>();
names.add("Alice");
String first = names.get(0);
Set<Integer> numbers = new HashSet<>();
numbers.add(100);
3. K, V - Key, Value(键值对)
Map集合中使用,K表示键,V表示值。
// Map接口
public interface Map<K, V> {
V put(K key, V value);
V get(Object key);
Set<K> keySet();
Collection<V> values();
Set<Map.Entry<K, V>> entrySet();
}
// Map.Entry内部接口
interface Entry<K, V> {
K getKey();
V getValue();
V setValue(V value);
}
// 使用
Map<String, User> userMap = new HashMap<>();
userMap.put("user001", new User("张三"));
User user = userMap.get("user001");
// 遍历Map
for (Map.Entry<String, User> entry : userMap.entrySet()) {
String key = entry.getKey();
User value = entry.getValue();
}
4. N - Number(数字)
表示数值类型,通常与数学计算相关。
// 数值计算工具类
public class MathUtils {
// 求和
public static <N extends Number> double sum(List<N> numbers) {
return numbers.stream()
.mapToDouble(Number::doubleValue)
.sum();
}
// 求最大值
public static <N extends Number & Comparable<N>> N max(N a, N b) {
return a.compareTo(b) > 0 ? a : b;
}
}
// 使用
List<Integer> ints = Arrays.asList(1, 2, 3);
double sum = MathUtils.sum(ints);
Integer max = MathUtils.max(10, 20);
5. S, U, V - 多类型参数
当需要多个类型参数时,使用S、U、V等字母。
// 函数式接口
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}
// 三元组
public class Triple<T, U, V> {
private final T first;
private final U second;
private final V third;
public Triple(T first, U second, V third) {
this.first = first;
this.second = second;
this.third = third;
}
public T getFirst() { return first; }
public U getSecond() { return second; }
public V getThird() { return third; }
}
// 使用
Triple<String, Integer, Boolean> triple =
new Triple<>("名称", 100, true);
6. ? - Wildcard(通配符)
表示不确定的类型,只能用于声明,不能用于定义。
// 无界通配符
public void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
// 上界通配符
public double sumOfNumbers(List<? extends Number> numbers) {
return numbers.stream()
.mapToDouble(Number::doubleValue)
.sum();
}
// 下界通配符
public void addNumbers(List<? super Integer> list) {
list.add(1);
list.add(2);
}
// 使用
List<Integer> ints = Arrays.asList(1, 2, 3);
List<Double> doubles = Arrays.asList(1.5, 2.5);
printList(ints); // ✅ 可以传递任意类型的List
sumOfNumbers(ints); // ✅ 可以传递Number及其子类的List
sumOfNumbers(doubles); // ✅
类型参数 vs Object
虽然类型擦除后都变成Object,但泛型提供编译期类型检查。
// 使用Object
public class ObjectBox {
private Object data;
public void set(Object data) {
this.data = data;
}
public Object get() {
return data;
}
}
// 使用泛型
public class GenericBox<T> {
private T data;
public void set(T data) {
this.data = data;
}
public T get() {
return data;
}
}
// 对比
ObjectBox objBox = new ObjectBox();
objBox.set("字符串");
objBox.set(100); // ✅ 编译通过,但类型不安全
String str = (String) objBox.get(); // ❌ 运行时ClassCastException
GenericBox<String> genBox = new GenericBox<>();
genBox.set("字符串");
// genBox.set(100); // ❌ 编译错误,类型安全
String str = genBox.get(); // ✅ 无需强制转换
自定义命名规范
虽然可以使用任意标识符,但建议遵循约定。
// ❌ 不推荐:使用完整单词
public class Container<ElementType> {
private ElementType data;
}
// ✅ 推荐:使用单个大写字母
public class Container<E> {
private E data;
}
// ✅ 语义明确的场景可以使用缩写
public class Repository<Entity, ID> {
public Entity findById(ID id) { ... }
}
// ✅ 或使用约定字母
public class Repository<T, ID> {
public T findById(ID id) { ... }
}
实战应用场景
1. 通用响应封装(T)
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.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;
}
}
// 使用
Result<User> userResult = Result.success(user);
Result<List<Order>> orderResult = Result.success(orders);
2. 分页结果(E)
public class Page<E> {
private int pageNum;
private int pageSize;
private long total;
private List<E> records;
// Getter/Setter
}
// 使用
Page<User> userPage = userService.findUsers(1, 10);
Page<Product> productPage = productService.findProducts(2, 20);
3. 键值对缓存(K, V)
public class Cache<K, V> {
private Map<K, V> cache = new ConcurrentHashMap<>();
public void put(K key, V value) {
cache.put(key, value);
}
public V get(K key) {
return cache.get(key);
}
public void remove(K key) {
cache.remove(key);
}
}
// 使用
Cache<String, User> userCache = new Cache<>();
userCache.put("user001", user);
Cache<Long, Order> orderCache = new Cache<>();
orderCache.put(1001L, order);
4. 数据转换器(S, T)
@FunctionalInterface
public interface Converter<S, T> {
T convert(S source);
}
// 实现
Converter<String, Integer> stringToInt = Integer::parseInt;
Converter<User, UserDTO> userToDTO = user ->
new UserDTO(user.getName(), user.getAge());
// 使用
Integer num = stringToInt.convert("123");
UserDTO dto = userToDTO.convert(user);
5. Builder模式(T)
public class Builder<T> {
private final T instance;
public Builder(Class<T> clazz) throws Exception {
this.instance = clazz.newInstance();
}
public Builder<T> set(String property, Object value) {
// 使用反射设置属性
return this;
}
public T build() {
return instance;
}
}
// 使用
User user = new Builder<>(User.class)
.set("name", "张三")
.set("age", 25)
.build();
JDK源码中的应用
1. Collections工具类
public class Collections {
// 单一类型参数
public static <T> void sort(List<T> list) { ... }
// 多类型参数
public static <T, S extends T> void copy(List<? super T> dest,
List<? extends S> src) { ... }
// 通配符
public static void reverse(List<?> list) { ... }
}
2. Optional类
public final class Optional<T> {
private final T value;
public static <T> Optional<T> of(T value) { ... }
public static <T> Optional<T> ofNullable(T value) { ... }
public T get() { ... }
public <U> Optional<U> map(Function<? super T, ? extends U> mapper) { ... }
}
3. Stream API
public interface Stream<T> {
Stream<T> filter(Predicate<? super T> predicate);
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
<R> R collect(Collector<? super T, A, R> collector);
}
命名最佳实践
// ✅ 好的命名
public class Pair<K, V> { } // 键值对
public interface List<E> { } // 列表元素
public class Optional<T> { } // 通用类型
public interface Comparable<T> { } // 通用类型
// ⚠️ 可接受的命名(语义明确)
public class Repository<Entity, ID> { }
public class Converter<Source, Target> { }
// ❌ 避免的命名(过于冗长)
public class Container<ElementType> { }
public class DataStructure<DataType> { }
答题总结
泛型类型参数是占位符,Java约定使用单个大写字母命名:
- T(Type):通用类型,最常用
- E(Element):集合元素类型(List、Set)
- K(Key)、V(Value):Map的键值类型
- N(Number):数值类型
- S、U、V:多类型参数场景
- ?(Wildcard):通配符,表示不确定的类型
这些约定提高了代码可读性,虽然可以使用任意标识符(如ElementType),但推荐遵循单字母约定。泛型相比Object的优势在于编译期类型检查,避免运行时类型转换错误。