如何使用Scala反射通过完全限定名获取Scala的单例对象

后端开发 2026-07-09

我在Scala 2.13中有一个实现了某个trait的单例对象:

package com.example

trait MyTrait {
  def myValue: String
}

object MyObject extends MyTrait {
  override final val myValue = "my-value"
}

我想通过运行时反射,将 MyObject 的实例作为 MyTrait 的实例来获取,基于完全限定名 "com.example.MyObject"

我知道如何使用Java反射来做到这一点:

val clazz = Class.forName("com.example.MyObject$")
val instance: MyTrait = clazz.getField("MODULE$").get(null).asInstanceOf[MyTrait]

然而,这有点晦涩,需要对所使用的JVM值的生成有具体的了解。特别是,类名为 "com.example.MyObject$"(尾部带有美元符号)以及实例被暴露为名为 "MODULE$" 的静态字段,都是不明显的实现细节。

我看到Scala在 2.10版本实现了自己的反射能力(反射概览),我想知道是否可以更干净地利用它来实现这个目标。也许在Scala中还有其他机制可以实现这一点。

我看到问题 使用新的Scala反射API获取伴生对象实例,但它处理的是获取类的 伴生对象,而不是按名称获取一个单例对象。

如何在不需要了解JVM类结构实现细节的前提下,按其完全限定名获取一个Scala对象的实例,并将其作为一个trait的实例?

解决方案

Scala 2.13附带一个 scala-reflect 运行时镜像,正是实现这一点的。关键的概念是每个Scala object —— 无论是伴生对象还是独立的单例对象 —— 在Scala的反射词汇中都是一个 模块(module),因此在这里也可以使用链接的Stack Overflow回答中用于伴生对象的相同API。

添加依赖项(默认的classpath中并不包含它):

libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value

然后:

import scala.reflect.runtime.universe
val mirror   = universe.runtimeMirror(getClass.getClassLoader)
val module   = mirror.staticModule("com.example.MyObject")   // ModuleSymbol
val instance = mirror.reflectModule(module).instance.asInstanceOf[MyTrait]

你可以把它们组合成一个方法。为了方便你试用,我在Scastie的 这里 给了一个示例,供你玩耍(代码位于内置的 Playground 模块中,这也是为什么命名会有差异)。

每一步的作用,用与Scala源码相符的术语来表述,而不是按JVM编码来描述:

  • runtimeMirror(classLoader) 基于一个特定的类加载器构建一个 镜像(使用能够看到你的对象的那个类加载器)。
  • staticModule("com.example.MyObject") 根据你在源代码中书写的完全限定名查找 模块符号(module symbol)— 没有 $ 后缀,也不需要了解 MODULE$
  • reflectModule(module).instance 将单例值作为 Any 返回;将其转换为你的trait。

如果名称可能错误或类不在类加载器上,staticModule 将抛出 ScalaReflectionException(如果模块的类不可加载,reflectModule 也可能抛出),因此如果你想优雅地处理这种情况,可以把它放在 Try 中。

关于你所提的区别的一点说明:在字节码和反射层面,“伴生对象”和“独立对象”是同一件事——两者都会被表示为一个 Foo$ 类,并带有一个 MODULE$ 静态字段;两者都对 scala-reflect 进行 ModuleSymbol。因此,链接问题中的答案可以直接适用;那里关于伴生对象与独立对象的区分只是偶然的。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章