Java中未检查类型转换的隐患:从List<Map>到List<Student>的映射问题解析

为什么你的Java对象中出现未知属性?

问题出现

我在运行这段代码的时候,发现了一个非常奇怪的点。

java 复制代码
List<Student> Students = (List<Student>) insertMap.get("Student")

首先

  1. insertMap.get("Student") 得到的类型是 List<Map> 类型
  2. 但是这段代码可以运行!
  3. 但是,当我debug时,发现 Students 这了列表集合中子元素并不是Student!而是Map类型。里面出现了非Student类的属性

原因

问题的根源是类型转换未校验Map到对象的映射缺失。通过显式的数据映射(手动或通过框架),可以确保Student对象的属性与数据源一致。

1. 类型擦除与未检查的类型转换

Java的泛型在编译后会经历类型擦除,这意味着List在运行时实际是List<Object>。当你强制将List<Map>转换为List<Student>时,编译器无法在运行时检查元素类型是否匹配。例如:

java 复制代码
List<Map> rawList = (List<Map>) insertMap.get("Student");
List<Student> students = (List<Student>) rawList; // 此处强制类型转换是危险的!

2. 根本原因:Map到Student的映射缺失

问题核心在于:你没有显式地将Map中的数据转换为Student对象。即使类型转换通过,List中的元素实际仍是Map类型,但这些Map被错误地当成了Student对象。当你试图访问Student的属性时,JVM会尝试从Map中查找与Student字段同名的键值,导致以下问题:

  • 存在不属于Student类的属性:因为Map中可能包含其他键,例如Map有一个address字段,而Student类没有该字段。
  • 属性值类型不匹配:例如Map中的age是字符串类型,而Student的age是整型。

为什么代码没有抛出异常?

强制类型转换在以下情况下不会抛出异常:

  • 运行时的 List 元素类型(Map)和目标元素类型(Student)不被检查。Java 不会对泛型类型进行运行时检查,只会检查容器本身是否为 List 类型。
  • 如果你尝试访问 Student 的方法(如 getName),而 Map 中的内容能够支持这种访问(通过类似反射或动态代理机制),程序可能不会立即出错。

但是,这种行为是一种未定义的运行时行为,可能会导致难以调试的错误。

java 复制代码
Map<String, Object> map = new HashMap<>();
map.put("name", "Tom");
map.put("age", 18);
map.put("extra", "unexpected"); // 多出的属性

当你将 List 强制转换为 List 时,程序将把 Map 对象当作 Student 对象处理。如果你通过 Students.get(0).getName() 调用 getName() 方法,程序可能会尝试将 Map 的 get("name") 的结果返回给你。这实际上是因为 Map 和 Student 混淆了,而不是因为 Map 真正是一个 Student。

因此:

  • 如果 Map 中的键值对恰好与 Student 的属性名一致(如 "name" 和 "age"),程序可能会表现得像是正常运行。
  • 如果 Map 中有额外的键值对(如 "extra"),它们不会被 Student 类直接识别,但它们仍然存在于底层对象中,造成了"有不属于 Student 的属性"的现象。

解决方案:显式映射Map到Student

你需要手动将Map中的数据转换为Student对象。以下是两种常见方法:

方案1. 手动转换

java 复制代码
List<Map<String, Object>> mapList = (List<Map<String, Object>>) insertMap.get("Student");
List<Student> students = new ArrayList<>();

for (Map<String, Object> map : mapList) {
    Student student = new Student();
    student.setId((String) map.get("id"));
    student.setName((String) map.get("name"));
    // 显式映射其他字段...
    students.add(student);
}

方案2:使用对象映射框架(推荐)

如果项目中已引入Jackson或Gson,可以直接将Map序列化为JSON,再反序列化为Student对象:

java 复制代码
import com.fasterxml.jackson.databind.ObjectMapper;

ObjectMapper mapper = new ObjectMapper();
List<Map<String, Object>> mapList = (List<Map<String, Object>>) insertMap.get("Student");
List<Student> students = mapList.stream()
    .map(map -> mapper.convertValue(map, Student.class))
    .collect(Collectors.toList());

添加泛型检查

如果你需要确保类型安全,可以在获取数据时添加泛型检查,避免直接使用未经检查的类型转换:

java 复制代码
Object obj = insertMap.get("Student");
if (obj instanceof List<?>) {
    List<?> list = (List<?>) obj;
    if (!list.isEmpty() && list.get(0) instanceof Map) {
        // 进行类型转换
    }
}
相关推荐
雨 子28 分钟前
Idea ⽆ Maven 选项
java·maven·intellij-idea
lihan_freak39 分钟前
java中的抽象类和接口
java·开发语言·接口·抽象类
P7进阶路1 小时前
【Rabbitmq篇】高级特性----TTL,死信队列,延迟队列
java·rabbitmq·java-rabbitmq
sjsjsbbsbsn1 小时前
Java Web 开发中的分页与参数校验
java·spring boot·spring·状态模式·hibernate
lihan_freak1 小时前
java中equals和hashCode为什么要一起重写
java·面试·哈希算法·equals·hashcode
蝴蝶不愿意2 小时前
Java基础学习笔记-static关键字
java·开发语言·学习
荼白z2 小时前
【实战】excel分页写入导出大文件
java·excel
皎味小行家2 小时前
第四十六天|动态规划|子序列|647. 回文子串,5.最长回文子串, 516.最长回文子序列,动态规划总结篇
java·数据结构·算法·leetcode·动态规划
LUCIAZZZ2 小时前
通过代理模式理解Java注解的实现原理
java·开发语言·数据库·spring boot·mysql·spring·代理模式
计算机-秋大田2 小时前
基于Spring Boot的图书个性化推荐系统的设计与实现(LW+源码+讲解)
java·前端·spring boot·后端·spring·课程设计