Scala Reflection:实例化单例对象

Scala Reflection: instantiating a singleton object

我正在使用以下代码实例化一个 Scala 对象。这行得通,但似乎有一个问题:println 打印了 两次 ,每次都使用另一个哈希码。

import scala.reflect.runtime.universe._
import scala.reflect.runtime.{universe => ru}

object Test2 { println("init"+hashCode())}

val mirror = ru.runtimeMirror(getClass.getClassLoader)
val m = ru.typeOf[Test2.type].members.filter(_.isConstructor).head.asMethod
val m2 = mirror.reflectClass(typeOf[Test2.type].typeSymbol.asClass)
val cm = m2.reflectConstructor(m)
val e = cm.apply()

结果:

init472467991
init2051378291
e: Any = Test2$@7a458c73

ehashCode等于后一个(2051378291)。我想知道这是为什么,因为据我所知应该只有一个?

编辑:使用 scala 版本 2.12.4

JVM 没有单例*

您正在调用 class 的私有构造函数。 Scala 反射允许它。当您调用构造函数时,您会得到一个新实例。

在普通 Java 中创建单例实际上非常困难,因为除了使用 new Something 之外,还有其他方法可以构造实例。例如,不调用任何构造函数代码的反序列化might not call any constructors besides one of Object. And there's sun.misc.Unsafe#allocateInstance that can conjure new instances of any class

Scala object 在幕后做了一些工作以确保您不会在任何正常使用期间意外创建第二个实例(例如它隐藏构造函数并处理反序列化),但它不能保护您来自 故意 创建一个。当您开始使用反射时,您就是这样做的。

甚至 Java enum 也可以使用反射在 运行 时实例化。你不能直接在枚举上调用 Class#newInstance(实现禁止它),但了解一些内部细节可以让你到达那里**

import java.nio.file.StandardOpenOption // first std enum I could remember for a quick dirty sample

val ctor = classOf[StandardOpenOption].getDeclaredConstructors.head

val aac = ctor.getClass.getDeclaredMethod("acquireConstructorAccessor")
aac.setAccessible(true) // unlimited power!

val ctorAccess = aac.invoke(ctor)
val newInstanceCall = ctorAccess.getClass.getDeclaredMethod("newInstance", classOf[Array[AnyRef]])
newInstanceCall.setAccessible(true)

// note that it does not throw ClassCastException, so it's a fine instance
val uhOh = newInstanceCall.invoke(ctorAccess, Array("UhOh", 42)).asInstanceOf[StandardOpenOption]
assert(uhOh.name == "UhOh")
assert(uhOh.ordinal == 42)

(interactive version @ Scastie)


要获得 "default" 实例,您 can access a public static field named MODULE$ using reflection. You can also

对您来说,无论您想要实现什么,都不依赖反思可能是最好的选择。


顺便说一句,有可能 ScalaReflectionException 尝试 运行 您在 IntelliJ 工作表或 Scastie 工作表模式下的代码,因为这些东西用 main 方法将您的代码包装在另一个对象中


* 仅在几个版本的 HotSpot JVM 上测试

** 请不要在任何严肃的代码中这样做!我只是用这个来证明一个观点。这也没什么用,因为它不会更改 valuesvalueOf。是的,我只在 JDK8 附带的 HotSpot 上检查过。