Java反射中Scala类方法返回基类的原因及精确类型获取方法
Scala与Java协变方法在Java反射中的差异解析
差异原因:字节码生成逻辑不同
Java从JDK 5开始支持协变返回类型,子类重写父类方法时允许返回更具体的子类类型。编译时,Java会生成两个方法:
- 一个是实际实现的方法,返回子类类型(如
SubClass get()),对外可见; - 另一个是桥接方法,返回父类类型(如
SuperClass get()),用于兼容JVM方法签名规则,但Java反射遍历getDeclaredMethods时,实际实现方法会被优先匹配到,所以你看到返回类型是SubClass。
而Scala处理协变返回的方法重写时,为了适配JVM不允许仅返回类型不同的方法共存的规则,采用了相反的字节码生成策略:
- 生成一个对外可见的桥接方法(名字为
get,返回父类SSuperClass类型),这个方法会被Java反射直接找到; - 实际实现的方法会被重命名(比如Scala 2中会变成
get$),并且标记为私有或合成方法,无法通过普通的名字匹配直接获取。
这就是为什么Java反射拿到Scala子类的get方法时,返回类型是父类。
用Java反射获取精确类型的方法
可以通过过滤桥接方法、匹配实际实现方法的方式拿到精确返回类型,无需依赖Scala反射:
实现思路
- 遍历子类的所有声明方法,排除桥接方法(通过
Method.isBridge()判断); - 匹配目标方法的名字(Scala中实际方法可能带有
$后缀,或直接保留原名,视版本而定)。
修改后的代码示例
object Main { def main(args: Array[String]): Unit = { // Java类的处理(原逻辑不变) val getMethod = classOf[SubClass].getDeclaredMethods.find(_.getName == "get").get val returnType = getMethod.getReturnType println(returnType) // Scala类的处理:过滤桥接方法,匹配实际方法 val getMethodS = classOf[SSubClass].getDeclaredMethods .filter(!_.isBridge) .find(m => m.getName == "get" || m.getName.startsWith("get$")) .get val returnTypeS = getMethodS.getReturnType println(returnTypeS) // 输出SSubClass } }
兼容性说明
这种方式在Scala 2.13和Scala 3中都能生效:
- Scala 2中实际方法会被重命名为
get$; - Scala 3中实际方法可能保留原名
get,但桥接方法依然存在,过滤后即可拿到正确的方法。
内容的提问来源于stack exchange,提问作者Suma
相关产品推荐
相关产品推荐

