【问题标题】:Scala Reflection: instantiating a singleton objectScala反射:实例化一个单例对象
【发布时间】:2018-02-02 08:51:45
【问题描述】:

我正在使用以下代码来实例化一个 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

【问题讨论】:

  • 请提供有关 Scala 版本的信息
  • 我同意@greenshade。对于 2.11 版,我得到:scala.ScalaReflectionException: object Test2 is an inner class, use reflectClass on an InstanceMirror to obtain its ClassMirror
  • scala 版本 2.12.4

标签: scala object reflection instantiation


【解决方案1】:

JVM 没有单例*

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

在纯 Java 中创建单例实际上非常困难,因为除了使用 new Something 之外,还有其他方法可以构造实例。比如反序列化might not call any constructors besides one of Object。还有sun.misc.Unsafe#allocateInstance 可以在不调用任何构造函数代码的情况下召唤任何类sans java.lang.Class 的新实例。

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

甚至 Java enums 也可以在运行时使用反射进行实例化。你不能直接在枚举上调用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)


要获得“默认”实例,您需要使用反射 can access 一个名为 MODULE$ 的公共静态字段。你也可以run whole scala compiler at runtime

对你来说,最好的选择可能是不要依赖反思来实现你想要实现的目标。


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


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

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

【讨论】:

  • 创建一个新的enum 实例更加简单。只需获取Constructor,调用setAccessible(true)unreflectMethodHandle。在newInstance做的检查已经忘记转换(7到9的所有版本),调用invokeExact时,原则上不会检查。另见this answer
猜你喜欢
  • 2015-07-22
  • 1970-01-01
  • 2020-07-10
  • 1970-01-01
  • 1970-01-01
  • 2012-11-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多