java中?和T的区别以及注解和接口的区别
一、?
和T
的区别
?
和 T
是 Java 泛型中的两个重要概念,它们有本质的区别:
1. 基本概念
?
- 通配符(Wildcard)
Class<?> // 表示任意类型的 Class
List<?> // 表示任意类型的 List
T
- 类型参数(Type Parameter)
Class<T> // 表示特定类型的 Class
List<T> // 表示特定类型的 List
2. 详细对比
?
通配符的特点:
// 1. 代表未知类型
public void processClass(Class<?> clazz) {
// clazz 可以是 Class<String>、Class<Integer> 等任意类型
System.out.println(clazz.getName()); // 可以调用 Object 方法
// clazz.newInstance(); // 编译错误:无法创建实例
}
// 2. 只读操作安全
public void printList(List<?> list) {
for (Object item : list) { // 只能当作 Object 处理
System.out.println(item);
}
// list.add("abc"); // 编译错误:无法添加元素
}
T
类型参数的特点:
// 1. 代表特定但未确定的类型
public <T> void processClass(Class<T> clazz) {
// T 是确定的类型,只是在编译时还不知道具体是什么
try {
T instance = clazz.newInstance(); // 可以创建实例
System.out.println(instance.getClass().getName());
} catch (Exception e) {
e.printStackTrace();
}
}
// 2. 类型安全的操作
public <T> void copyList(List<T> source, List<T> target) {
for (T item : source) {
target.add(item); // 类型安全
}
}
3. 实际应用示例
使用 ?
通配符:
// EasyExcel 中的字段处理
public List<Field> getProcessableFields(Class<?> clazz) {
List<Field> result = new ArrayList<>();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (!field.isAnnotationPresent(ExcelIgnore.class)) {
result.add(field);
}
}
return result;
}
// 调用
List<Field> userFields = getProcessableFields(User.class);
List<Field> productFields = getProcessableFields(Product.class);
使用 T
类型参数:
// EasyExcel 中的数据读取
public <T> List<T> readExcel(String fileName, Class<T> clazz) {
// T 是具体的类型,比如 User、Product 等
List<T> result = new ArrayList<>();
// 读取数据并创建 T 类型的实例
T instance = clazz.newInstance(); // 可以创建实例
// 设置字段值...
result.add(instance);
return result;
}
// 调用
List<User> users = readExcel("users.xlsx", User.class);
List<Product> products = readExcel("products.xlsx", Product.class);
4. 有界泛型的对比
上界通配符 vs 上界类型参数:
// 通配符形式
public double sumNumbers(List<? extends Number> numbers) {
double sum = 0;
for (Number num : numbers) { // 只能当作 Number 处理
sum += num.doubleValue();
}
return sum;
}
// 类型参数形式
public <T extends Number> double sumNumbers(List<T> numbers) {
double sum = 0;
for (T num : numbers) { // 可以当作具体类型 T 处理
sum += num.doubleValue();
}
return sum;
}
下界通配符 vs 下界类型参数:
// 通配符形式
public void addNumbers(List<? super Integer> numbers) {
numbers.add(42); // 可以添加 Integer
// Number num = numbers.get(0); // 只能当作 Object 处理
}
// 类型参数形式
public <T super Integer> void addNumbers(List<T> numbers) {
// 这种语法在 Java 中不支持
// Java 不支持下界类型参数
}
5. 在 EasyExcel 中的实际应用
?
的使用场景:
// 处理任意类的元数据(不需要创建实例)
public void analyzeClassStructure(Class<?> clazz) {
System.out.println("分析类结构: " + clazz.getSimpleName());
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
System.out.println("字段: " + field.getName());
}
}
T
的使用场景:
// 读取并创建具体类型的实例
public <T> List<T> readExcelData(String filePath, Class<T> clazz) throws Exception {
List<T> result = new ArrayList<>();
// 创建 T 类型的实例
T instance = clazz.getDeclaredConstructor().newInstance();
// 填充数据到 instance
populateData(instance);
result.add(instance);
return result;
}
6. 总结对比
特性 | ? 通配符 |
T 类型参数 |
---|---|---|
含义 | 未知类型 | 特定但未确定的类型 |
实例化 | 不能创建实例 | 可以创建实例 |
类型安全 | 只能当作 Object 使用 | 可以当作具体类型使用 |
添加元素 | 通常不能添加 | 可以添加(类型安全) |
读取元素 | 只能当作 Object 读取 | 可以当作具体类型读取 |
使用场景 | 只读操作、类型检查 | 需要创建实例、类型安全操作 |
选择使用 ?
还是 T
取决于你的具体需求:
- 如果只需要读取或检查类型信息,使用
?
- 如果需要创建实例或进行类型安全的操作,使用
T
二、注解和接口的区别
不是的!添加 [@ExcelIgnore](file://com\alibaba\excel\annotation\ExcelIgnore.java#L2-L6) 注解并不意味着实现了任何接口。
让我澄清这个重要的概念:
注解 ≠ 接口实现
1. 注解的本质
// @ExcelIgnore 是一个注解,不是接口
public @interface ExcelIgnore {
}
// 使用注解
public class User {
@ExcelIgnore
private String password; // 这里只是添加了一个标记
}
2. 注解 vs 接口实现的区别
注解的作用:
// 注解只是添加元数据标记
@ExcelIgnore // 这只是一个标记,不改变类的结构
private String password;
接口实现的作用:
// 实现接口会改变类的结构和行为
public class UserService implements ServiceInterface {
// 必须实现接口中的所有方法
@Override
public void doSomething() {
// 具体实现
}
}
注解的实际工作原理
1. 注解只是元数据
// 当你写这个代码时:
public class User {
@ExcelIgnore
private String password;
}
// 实际上只是在 password 字段上添加了一个标记
// User 类本身没有实现任何接口
// password 字段也没有实现任何接口
2. 框架通过反射读取注解
// EasyExcel 框架内部的处理逻辑
public class ExcelProcessor {
public void processClass(Class<?> clazz) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 检查字段是否有 @ExcelIgnore 注解(读取元数据)
if (field.isAnnotationPresent(ExcelIgnore.class)) {
// 发现注解标记,决定忽略这个字段
continue; // 跳过处理
}
// 处理没有被注解标记的字段
processField(field);
}
}
}
完整的对比示例
错误理解:
// ❌ 错误理解:以为添加注解就是实现接口
@ExcelIgnore
private String password; // 用户可能以为这样就"实现"了某种忽略逻辑
正确理解:
// ✅ 正确认识:注解只是添加标记
@ExcelIgnore // 这只是在字段上添加了一个标记
private String password; // 字段本身没有任何改变
// 真正的"实现"是在框架内部:
// EasyExcel 框架检查到 @ExcelIgnore 标记,然后执行忽略逻辑
类比理解
注解就像标签:
// 想象注解就像给物品贴标签
@ExcelIgnore // 就像贴一个"忽略"标签
private String password;
// EasyExcel 就像一个机器人,会检查物品上的标签
// 看到"忽略"标签就跳过这个物品
接口实现就像契约:
// 实现接口就像签署合同,必须履行义务
public class UserService implements ServiceInterface {
// 必须按照接口规定提供具体实现
@Override
public void requiredMethod() {
// 必须实现的方法
}
}
总结
添加 [@ExcelIgnore](file://com\alibaba\excel\annotation\ExcelIgnore.java#L2-L6) 注解的含义是:
- 添加元数据标记:告诉 EasyExcel 框架这个字段需要被忽略
- 不改变类结构:不会让类实现任何接口
- 不添加行为:注解本身不包含任何执行逻辑
- 被动标记:需要框架主动检查并处理这个标记
真正的工作是在 EasyExcel 框架内部通过反射机制检查这个注解标记,然后决定是否处理该字段。