ClassCastException是JVM在强转时发现对象实际类型不匹配而抛出的异常,常见于集合取值、反射、反序列化后未校验就硬转;应优先用instanceof+安全强转或JDK14+模式匹配,结合泛型约束和工具方法防御。

ClassCastException 发生时,JVM 其实已经告诉你“类型不对”了
这个异常不是运行时突然崩掉才出现的,而是你在 Object 到子类(比如 String、ArrayList)强转时,JVM 检查发现堆里那个对象**根本不是你要转成的类型**,直接抛出。常见于从集合取值、反射调用、序列化反解后不做校验就硬转。
最典型的错误写法:(String) obj —— 一旦 obj 实际是 Integer 或 null,立刻炸。
- 别依赖“我传进去的肯定是 String”,集合、Map、JSON 反序列化结果都可能混类型
-
instanceof是低成本防御手段,但注意它对null返回false,不会 NPE,这点比强转安全 - 泛型擦除后,
List里存什么 runtime 才知道,编译器拦不住你乱 cast
用 instanceof + 安全强转替代裸 cast
这是最常用也最可控的方式。重点不是“加个判断”,而是把判断和转换绑在一起,避免中间被修改或并发干扰。
if (obj instanceof String) {
String s = (String) obj; // 此时强转 100% 安全
System.out.println(s.length());
}
- 不要写成两段:
if (obj instanceof String) {...}然后后面再(String)obj—— 中间如果有其他代码改了obj(比如多线程),就失效 -
instanceof对原始类型包装类有效(Integer、Boolean),但不能用于基本类型(int、boolean) - JDK 14+ 支持模式匹配(
if (obj instanceof String s)),直接绑定变量,更简洁,但需确认目标环境版本
从 Collection / Map 取值时,优先用泛型约束源头
很多 ClassCastException 其实源于“本该是类型安全的地方,用了裸类型”。比如 Map 存了 String 和 Integer,取的时候不检查就强转。
立即学习“Java免费学习笔记(深入)”;
- 声明时就用泛型:
Map<string object> map = new HashMap();</string>比Map map = new HashMap();强得多 - 如果必须存多种类型,考虑封装为枚举+判别式方法,而不是靠外部强转
- 用
Map.getOrDefault(key, defaultValue)时注意:默认值类型要和实际 value 类型一致,否则后续强转仍可能失败 - Gson / Jackson 反序列化时,明确指定泛型类型(如
gson.fromJson(json, new TypeToken<list>>(){}.getType())</list>),避免得到LinkedTreeMap后硬转Foo
自定义工具方法做带默认值的安全转型
重复写 instanceof + 强转很啰嗦,尤其在 DTO 转换、配置解析等场景。封装一个工具方法能减少出错概率。
public static <T> T cast(Object obj, Class<T> type, T defaultValue) {
return type.isInstance(obj) ? type.cast(obj) : defaultValue;
}
-
Class.cast()是类型安全的强转方法,比裸括号语法更语义清晰 - 返回
null还是默认值,取决于业务逻辑;但注意:如果defaultValue是null,且type是基础类型包装类(如Integer.class),别让调用方误以为“成功转出了 null” - 这个方法无法绕过泛型擦除问题,所以仍需确保传入的
Class对象准确(比如不能传List.class去试图转ArrayList)
真正难防的不是 ClassCastException,而是你以为“这里不可能出错”的地方——比如 JSON 字段名拼错导致反序列化成 LinkedHashMap,或者测试数据全是 String,上线后用户传了数字。类型检查得落在关键路径上,而不是只在 IDE 编译通过那一刻。










