diff --git a/W8/Cache.java b/W8/Cache.java new file mode 100644 index 0000000..ca21d13 --- /dev/null +++ b/W8/Cache.java @@ -0,0 +1,64 @@ +import java.util.HashMap; +import java.util.Map; + +public class Cache { + // 底层用 HashMap 存储数据 + private final Map cacheMap; + + public Cache() { + this.cacheMap = new HashMap<>(); + } + + // 存入缓存 + public void put(K key, V value) { + cacheMap.put(key, value); + } + + // 从缓存获取 + public V get(K key) { + return cacheMap.get(key); + } + + // 删除缓存 + public void remove(K key) { + cacheMap.remove(key); + } + + // 判断缓存是否包含某个 key + public boolean containsKey(K key) { + return cacheMap.containsKey(key); + } + + // 清空缓存 + public void clear() { + cacheMap.clear(); + } + + // 获取缓存大小 + public int size() { + return cacheMap.size(); + } + + // 测试主方法 + public static void main(String[] args) { + Cache studentScoreCache = new Cache<>(); + + // 存入数据 + studentScoreCache.put("张三", 90); + studentScoreCache.put("李四", 85); + studentScoreCache.put("王五", 95); + + // 查询数据 + System.out.println("张三的分数:" + studentScoreCache.get("张三")); + System.out.println("缓存大小:" + studentScoreCache.size()); + + // 删除数据 + studentScoreCache.remove("李四"); + System.out.println("删除李四后缓存大小:" + studentScoreCache.size()); + System.out.println("是否包含李四?" + studentScoreCache.containsKey("李四")); + + // 清空缓存 + studentScoreCache.clear(); + System.out.println("清空后缓存大小:" + studentScoreCache.size()); + } +} \ No newline at end of file diff --git a/W8/Java泛型擦除后如何通过反射获取泛型信息.txt b/W8/Java泛型擦除后如何通过反射获取泛型信息.txt new file mode 100644 index 0000000..8159e3b --- /dev/null +++ b/W8/Java泛型擦除后如何通过反射获取泛型信息.txt @@ -0,0 +1,84 @@ + 一、Java 泛型擦除的本质 +Java 的泛型是一种编译期语法糖,并非原生支持的语言特性。为了兼容 JDK 1.5 之前没有泛型的代码,编译器会在编译阶段执行类型擦除(Type Erasure): + +1. 擦除泛型参数 + 所有泛型类型参数(如 ``、``)会被替换为它们的上界类型: + - 未指定上界时,默认擦除为 `Object`。 + 例如 `List` → 编译后变为 `List`,`add` 方法变成 `add(Object)`,`get` 方法返回 `Object`。 + - 指定上界时,擦除为上界类型。 + 例如 `List` → 编译后擦除为 `List`。 + +2. 插入强制类型转换 + 为了保证代码在运行时的类型安全,编译器会在读取泛型元素的地方自动插入强制类型转换。 + 例如 `String s = list.get(0);` 编译后会变成 `String s = (String) list.get(0);`。 + +核心结果: +运行时 JVM 只能看到原始类型(如 `List`、`Map`),无法直接感知到 `String`、`Integer` 这类泛型参数信息。 + + 二、为什么还能通过反射获取部分泛型信息? +虽然大部分泛型信息被擦除了,但有一部分结构级别的泛型信息会被编译器刻意保留,并写入 `.class` 字节码文件的 `Signature` 属性中。 + +1. 哪些泛型信息会被保留? +只有定义在类、接口、成员变量、方法签名上的泛型信息会被保留,具体包括: +- 类/接口的父类/实现接口声明 + 例如 `public class MyList extends ArrayList`,`ArrayList` 这个带泛型的父类信息会被保留。 +- 类中成员变量(Field)的泛型类型 + 例如 `private List scores;`,`List` 的泛型信息会被保留。 +- 方法(Method)的参数类型与返回值类型 + 例如 `public List getNames(List ids)`,参数和返回值的泛型信息会被保留。 + + 2. 为什么这些信息会被保留? +- 为了保证编译器的类型检查:IDE 和编译器需要这些信息来检查代码的类型安全。 +- 为了支持反射 API:JDK 设计了专门的反射接口来读取这些信息,保证框架(如 Spring、Jackson)能基于泛型做自动化处理。 +- 这些信息保存在字节码的 `Signature` 属性中,不会影响运行时的兼容性。 + + 三、通过反射获取泛型信息的步骤 + + 步骤 1:获取目标的反射元数据对象 +根据你要获取的泛型类型,先拿到对应的元数据对象: +- 获取类/父类的泛型:拿到目标类的 `Class` 对象。 +- 获取成员变量的泛型:拿到目标变量的 `Field` 对象。 +- 获取方法参数/返回值的泛型:拿到目标方法的 `Method` 对象。 + + 步骤 2:调用 `getGenericXXX()` 方法获取带泛型的 `Type` 对象 +反射 API 提供了专门的方法,来读取字节码中保留的 `Signature` 信息,返回一个 `java.lang.reflect.Type` 对象(它是所有类型的通用接口): +- 父类:`Class.getGenericSuperclass()` +- 实现接口:`Class.getGenericInterfaces()` +- 成员变量类型:`Field.getGenericType()` +- 方法返回值类型:`Method.getGenericReturnType()` +- 方法参数类型:`Method.getGenericParameterTypes()` + +注意:这些方法和普通的 `getSuperclass()`、`getType()` 不同,后者返回的是擦除后的原始类型(如 `ArrayList`),而前者返回的是保留了泛型信息的 `Type` 对象。 + +步骤 3:判断并解析 `ParameterizedType`(参数化类型) +`Type` 是一个空接口,它的一个重要实现是 `ParameterizedType`,用来表示带泛型的参数化类型(如 `List`、`Map`)。 + +你需要先判断获取到的 `Type` 是否是 `ParameterizedType` 实例: +- 如果是,就可以调用它的核心方法 `getActualTypeArguments()`,这个方法会返回一个 `Type[]` 数组,里面就是定义时声明的真实泛型参数。 +- 数组里的每个元素,都对应着原始声明中的泛型参数(如 `List` 会返回一个长度为 1 的数组,元素为 `String`;`Map` 会返回长度为 2 的数组,分别为 `K` 和 `V`)。 + +步骤 4:递归解析嵌套泛型(如 `List>`) +如果泛型本身还是一个参数化类型(如 `List>`),`getActualTypeArguments()` 返回的 `Type` 仍然是一个 `ParameterizedType`,你可以重复步骤 3,递归解析嵌套的泛型信息。 + + 四、不同场景下的获取能力对比 +| 场景 | 示例 | 能否通过反射获取泛型信息? | 原因 | + +| 类继承的父类泛型 | `class A extends B` | ✅ 能 | 信息保存在类的 `Signature` 属性中 | +| 类实现的接口泛型 | `class C implements D` | ✅ 能 | 信息保存在类的 `Signature` 属性中 | +| 成员变量的泛型 | `private List list;` | ✅ 能 | 信息保存在字段的 `Signature` 属性中 | +| 方法返回值的泛型 | `public List getList()` | ✅ 能 | 信息保存在方法的 `Signature` 属性中 | +| 方法参数的泛型 | `public void setList(List list)` | ✅ 能 | 信息保存在方法的 `Signature` 属性中 | +| 方法内局部变量的泛型 | `public void test() { List l = new ArrayList<>(); }` | ❌ 不能 | 局部变量的泛型信息编译后完全擦除,无任何保留 | +| 直接创建的泛型对象 | `new ArrayList()` | ❌ 不能 | 仅在编译期有效,运行时没有任何元数据 | + +五、局限性与注意事项 +1. 只能获取定义时声明的泛型,无法获取运行时传入的类型 + 例如 `List list = new ArrayList<>();`,反射无法知道 `list` 里存的是 `String`,只能知道 `list` 这个变量声明时的类型是 `List`。 +2. 无法获取通配符泛型的具体类型 + 例如 `List`,反射能知道它的上界是 `Number`,但无法知道运行时实际传入的具体子类型。 +3. 类型变量(如 ``)的解析需要上下文 + 如果是未绑定的类型变量(如 `class Box` 中的 `T`),反射只能获取到变量名,无法知道它的具体类型,除非它被绑定到了具体的类上(如 `class StringBox extends Box`)。 + + + 六、总结 +Java 泛型擦除后,运行时大部分泛型信息会被移除,但类、接口、成员变量、方法签名上声明的泛型信息会被保留在字节码的 `Signature` 属性中。通过反射 API 获取这些元数据,将其解析为 `ParameterizedType` 并调用 `getActualTypeArguments()`,即可读取对应的泛型信息;而方法内局部变量的泛型信息会被完全擦除,无法通过反射获取。 diff --git a/W8/Pair.java b/W8/Pair.java new file mode 100644 index 0000000..68a30ae --- /dev/null +++ b/W8/Pair.java @@ -0,0 +1,47 @@ +public class Pair { + private K key; + private V value; + + public Pair(K key, V value) { + this.key = key; + this.value = value; + } + + public K getKey() { + return key; + } + + public void setKey(K key) { + this.key = key; + } + + public V getValue() { + return value; + } + + public void setValue(V value) { + this.value = value; + } + + // swap 方法:交换 key 和 value + public Pair swap() { + return new Pair<>(this.value, this.key); + } + + @Override + public String toString() { + return "Pair{" + + "key=" + key + + ", value=" + value + + '}'; + } + + // 测试主方法 + public static void main(String[] args) { + Pair pair = new Pair<>("年龄", 20); + System.out.println("原对象:" + pair); + + Pair swapped = pair.swap(); + System.out.println("交换后:" + swapped); + } +} \ No newline at end of file diff --git a/W8/代码审查报告.md b/W8/代码审查报告.md new file mode 100644 index 0000000..9a37f07 --- /dev/null +++ b/W8/代码审查报告.md @@ -0,0 +1,190 @@ +# 代码审查报告(Cache 泛型缓存类) + +### 一、代码整体评价 + +这段代码整体质量很高,实现了一个基于泛型的键值缓存,功能完整、结构清晰、符合 Java 编码规范,完全满足作业要求。 + +#### 二、代码优点 + +正确使用泛型 泛型定义规范,支持任意类型的键值对,通用性强。 + +封装性良好使用 private final Map 存储数据,外部无法直接修改,保证数据安全。 + +功能完整实现了缓存的核心操作: + +添加 put + +获取 get + +删除 remove + +判断存在 containsKey + +清空 clear + +获取大小 size + +代码简洁易读方法命名规范、注释清晰、逻辑直白,符合 Java 标准写法。 + +自带测试方法main 方法提供完整测试用例,可直接运行验证功能。 + +依赖合理基于 HashMap 实现,性能高效,适合缓存场景。 + +### 三、代码存在的问题 + +这些不是错误,只是不够规范: + +没有对 null 做安全处理 + +允许存入 null 键 / 值 + +获取时可能返回 null,外部使用容易空指针 + +→ 建议增加非空校验 + +线程不安全HashMap 是非线程安全的,多线程环境下会出现数据错误。 + +没有最大容量限制无缓存上限,不断存入会导致内存占用过高。 + +无过期策略真实缓存都有过期时间,当前代码数据永久保存。 + +缺少 toString () 方法无法直接打印查看缓存内容,调试不方便。 + +### 四、改进后的优化代码 + +import java.util.HashMap; + +import java.util.Map; + +import java.util.Objects; + + + +public class Cache { + + private final Map cacheMap; + + + + public Cache() { + + this.cacheMap = new HashMap<>(); + + } + + + + // 存入缓存(增加非空判断) + + public void put(K key, V value) { + + Objects.requireNonNull(key, "键不能为空"); + + Objects.requireNonNull(value, "值不能为空"); + + cacheMap.put(key, value); + + } + + + + // 获取缓存 + + public V get(K key) { + + return cacheMap.get(key); + + } + + + + // 删除 + + public void remove(K key) { + + cacheMap.remove(key); + + } + + + + // 是否包含 + + public boolean containsKey(K key) { + + return cacheMap.containsKey(key); + + } + + + + // 清空 + + public void clear() { + + cacheMap.clear(); + + } + + + + // 大小 + + public int size() { + + return cacheMap.size(); + + } + + + + // 打印缓存内容 + + @Override + + public String toString() { + + return "Cache{" + "data=" + cacheMap + '}'; + + } + + + + // 测试 + + public static void main(String\[] args) { + + Cache studentScoreCache = new Cache<>(); + + + + studentScoreCache.put("张三", 90); + + studentScoreCache.put("李四", 85); + + studentScoreCache.put("王五", 95); + + + + System.out.println(studentScoreCache); + + System.out.println("张三的分数:" + studentScoreCache.get("张三")); + + System.out.println("缓存大小:" + studentScoreCache.size()); + + + + studentScoreCache.remove("李四"); + + System.out.println("删除李四后:" + studentScoreCache); + + + + studentScoreCache.clear(); + + System.out.println("清空后缓存大小:" + studentScoreCache.size()); + + } + +} +