如何在Scala中递归打印一个类的所有子类?
如何在Scala中递归打印一个类的所有子类?
问题背景
我在使用CDE库做类参数化,项目里有大量继承自Config类的子类(示例如下),用户需要手动在Makefile里指定这些类名,我想实现一个工具自动打印所有Config子类的名称,替代grep查找。
class Cfg0 extends Config(...) class Cfg1 extends Cfg0 class Cfg2 extends Cfg1
解决方案
方法一:运行时反射实现
利用Scala反射API递归遍历已加载的类,收集所有直接/间接继承自Config的子类:
1. 导入反射依赖
import scala.reflect.runtime.universe._ import scala.reflect.runtime.{currentMirror => cm}
2. 递归查找子类的工具函数
def findAllSubclasses(targetType: Type): Set[String] = { // 获取当前类的所有直接子类 val directSubclasses = targetType.typeSymbol.asClass.knownDirectSubclasses .filter(_.isClass) .map(_.asClass) .flatMap { cls => // 确保类已被JVM加载,忽略加载失败的类 try { Some(cm.classSymbol(cm.runtimeClass(cls)).toType) } catch { case _: ClassNotFoundException => None } } // 递归查找子类的子类,合并当前子类和递归结果 directSubclasses.flatMap(findAllSubclasses) ++ directSubclasses.map(_.typeSymbol.fullName) }
3. 调用并输出结果
// 定位Config类的Type val configType = typeOf[Config] // 收集所有子类名称 val allConfigSubclasses = findAllSubclasses(configType) // 排序后打印 println("所有Config子类:") allConfigSubclasses.toList.sorted.foreach(println)
注意事项
- 类加载限制:反射只能找到已被JVM加载的类,如果某些子类未被代码引用导致未加载,需要额外实现类路径扫描逻辑来加载这些类。
- 去重处理:用
Set存储类名自动避免重复统计。 - 适配普通类:虽然参考资料多针对trait/case类,但
knownDirectSubclasses对普通类同样有效,递归逻辑可以覆盖所有间接继承的子类。
方法二:编译期宏实现(更可靠)
如果反射无法覆盖未加载的类,可以用Scala宏在编译期扫描所有Config子类:
1. 实现宏注解
import scala.annotation.StaticAnnotation import scala.language.experimental.macros import scala.reflect.macros.blackbox class collectConfigSubclasses extends StaticAnnotation { def macroTransform(annottees: Any*): Any = macro CollectConfigSubclassesMacro.impl } object CollectConfigSubclassesMacro { def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = { import c.universe._ // 定位Config类符号 val configSymbol = c.typeSymbolOf[Config] // 扫描所有直接子类并收集全限定名 val subclasses = configSymbol.asClass.knownDirectSubclasses .filter(_.isClass) .map(_.fullName) .toList // 生成包含子类信息的打印代码 q""" object ConfigSubclassPrinter { def printAll(): Unit = { println("所有Config子类:") ${subclasses}.sorted.foreach(println) } } ${annottees.head} """ } }
2. 使用宏注解
在任意对象上添加注解,编译后即可调用打印:
@collectConfigSubclasses object ConfigTools
运行时调用:
ConfigTools.ConfigSubclassPrinter.printAll()
内容的提问来源于stack exchange,提问作者Abraham G
相关产品推荐
相关产品推荐

