泛型类型中==运算符的行为差异及编译错误原因问询
问题描述
代码片段1(编译错误)
List<Object> objs1 = Arrays.asList("one", "two"); List<String> strs1 = (List<String>)(List<?>)objs1; assert strs1 == objs1; //compile error
明明==仅比较对象标识,为什么这个仅涉及类型转换的语句会出现编译错误?
代码片段2(运行正常)
String str = "this is it"; Object oo = str; assert str == oo; //asserts true
逻辑类似却能正常运行,为什么==运算符会依据持有对象的引用类型表现不同,即便引用的是同一对象?
问题解答
1. 第一个代码片段编译错误的核心原因:泛型不变性与编译时类型检查
Java泛型遵循**不变性(invariant)**规则:List<String>并不是List<Object>的子类,反之亦然。虽然运行时泛型类型会被擦除,两个变量最终都指向同一个List对象,但编译时编译器会严格检查静态类型:
objs1的静态类型是List<Object>,strs1的静态类型是List<String>,这两个类型在编译层面属于不可转换的类型。- 编译器不允许直接比较两个不可转换类型的引用,哪怕你通过强制转换让
strs1指向了同一个对象——编译阶段只认静态类型,不会考虑运行时的实际对象。 - 你用到的
(List<String>)(List<?>)objs1是一种“绕过编译检查”的unchecked cast,编译器会给出警告,但不会改变strs1的静态类型,所以后续的==比较依然会触发编译错误。
2. 第二个代码片段正常运行的原因:父子类型的可转换性
String是Object的直接子类,属于可转换的类型:
- 编译器允许父类与子类类型的引用进行
==比较,因为它们之间存在明确的继承关系,引用可以合法地互相转换(向上转型是隐式的,向下转型是显式的)。 - 运行时
str和oo指向同一个String对象,==比较的是对象的内存地址,自然返回true。
总结
==本身始终只比较对象的内存标识,但编译器会先校验操作数的静态类型是否允许比较:
- 对于泛型参数化类型,因泛型不变性,不同参数的
List是不可转换类型,编译时直接拦截比较操作; - 对于普通类的父子类型,因存在继承关系,允许编译时比较,运行时再执行对象标识的对比。
内容的提问来源于stack exchange,提问作者theutonium.18
相关产品推荐
相关产品推荐

