Scala Reflection: instantiating a singleton object

2019-08-31 01:22发布

I'm using the following code to instantiate a scala object. This works, but there seems to be one problem: the println is printed out twice, each time with another hashcode.

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()

Results in:

init472467991
init2051378291
e: Any = Test2$@7a458c73

the hashCode of e is equal to the latter one (2051378291). I'm wondering why this is because as far as I know there should be only one?

EDIT: using scala version 2.12.4

1条回答
干净又极端
2楼-- · 2019-08-31 02:16

JVM has no singletons*

You're invoking a private constructor of a class. Scala reflection allows it. And when you invoke a constructor, you get a new instance back.

It's actually pretty hard to make a singleton in plain Java because there are ways to construct an instance except using new Something. For instance, de-serialization might not call any constructors besides one of Object. And there's sun.misc.Unsafe#allocateInstance that can conjure new instances of any class sans java.lang.Class without calling any constructor code.

Scala object does some job behind the hood to ensure you don't accidentally create a second instance during any normal usage (e.g. it hides the constructor and handles de-serialization), but it cannot protect you from deliberately creating one. When you start using reflection, you do exactly that.

Even Java enums can be instantiated at runtime using reflection. You cannot call Class#newInstance on an enum directly (the implementation forbids it), but knowing a bit of internal details can get you there**

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)


To get the "default" instance, you can access a public static field named MODULE$ using reflection. You can also run whole scala compiler at runtime

It's likely to be the best bet for you to not rely on reflection in whatever you're trying to achieve.


BTW, it is possible to get ScalaReflectionException trying to run your code in IntelliJ worksheet or Scastie in worksheet mode, because these things wrap your code in another object with main method


* Only tested on few versions of HotSpot JVM

** Please don't do this in any serious code! I only use this to prove a point. This is also pretty useless because it does not change values or valueOf. And yes, I only checked it on HotSpot that comes with JDK8.

查看更多
登录 后发表回答