Scala反射如何识别并过滤成员列表中来自外部作用域的变量
问题1:为什么par_some$1会被识别为类成员
你在匿名类内部引用了外部作用域的par_some变量,为了实现闭包的变量捕获逻辑,Scala编译器会自动在该匿名类中生成一个对应的私有字段,用于存储捕获到的外部变量的值,命名规则为「原变量名+$+数字后缀」,也就是你看到的par_some$1。该字段是类的真实成员,因此反射遍历成员列表时会被采集到。
之所以isSynthetic、isImplementationArtifact这类标记无法识别它,是因为这类闭包捕获字段在当前Scala反射的符号标记体系中,没有被标记为合成成员或实现制品,和用户自定义成员的标记位完全一致,无法通过这类属性区分。
问题2:如何过滤这类自动生成的成员
提供两种可落地的过滤方案,可根据你的业务场景选择:
- 方案1:按命名规则过滤
Scala编译器自动生成的字段(包括闭包捕获字段、合成字段),命名都会遵循「原名称+$+数字后缀」的规则,正常业务场景下用户自定义的类成员不会使用这类格式的命名。如果你不需要兼容含$+数字后缀的自定义成员,可以直接过滤名称包含$的成员;如果需要保留带$的自定义成员(比如移植自JS的$开头的属性),可以用正则匹配仅过滤以$+数字结尾的成员。
修改后的代码示例:
val items = for { symbol <- currentMirror.classSymbol(m.getClass).toType.members if symbol.isTerm && !symbol.isMethod && !symbol.isModule // 过滤编译器生成的带$+数字后缀的捕获字段 && !symbol.name.toString.matches(""".+\$\d+$""") } yield { val field = anyMirror.reflectField(symbol.asTerm) symbol.name.decodedName.toString }
- 方案2:仅保留公有成员
你自定义的var/val成员默认都是公有级别,而编译器生成的闭包捕获字段都是私有级别,如果你的场景不需要采集自定义私有成员,可以直接加symbol.isPublic过滤条件,就能排除这类自动生成的私有字段。
修改后的代码示例:
val items = for { symbol <- currentMirror.classSymbol(m.getClass).toType.members if symbol.isTerm && !symbol.isMethod && !symbol.isModule && symbol.isPublic } yield { val field = anyMirror.reflectField(symbol.asTerm) symbol.name.decodedName.toString }
两种方案也可以组合使用,进一步提升过滤的准确率。
内容的提问来源于stack exchange,提问作者Suma
相关产品推荐
相关产品推荐

