1

I want to run some code in the body of an scala companion object before the class is instantiated. The idea is to register a bunch of object in a Set. Here is the code

trait Delegate {
  def make: Ins
}

//EDIT: Changed constructor to private
//class Ins
class Ins private()

//this is the companion object that will be registered with the InsDelegate
object Ins extends Delegate{
//here is the code that do the registration but doesn't run
  InsDelegate.register(this)

  override def make: Ins = {
    println("This is an Ins")
    new Ins()
  }
}

Here is the code for the InsDelegate

object InsDelegate {
  private val objectSet = new mutable.HashSet[Delegate]()

  def register(obj: Delegate): Unit = objectSet.add(obj)

  def getRegisteredObj: Set[Delegate] = objectSet.toSet
}

When I run this test, nothing gets printed.

object test extends App {
  InsDelegate.getRegisteredObj.foreach(_.make)
}

The code that register the companion object doesn't run. I know that unlike java, in order to run the companion object code you need to instantiate the class of the object. How do I accomplish what I am trying to do???

locorecto
  • 1,178
  • 3
  • 13
  • 40
  • put that in the class's constructor. Why are you putting it inside the companion object (which is supposed to hold the class members which would have been "static" in Java classes) ? – sarveshseri May 01 '17 at 14:22
  • Or... put it outside anywhere... – sarveshseri May 01 '17 at 14:24
  • @SarveshKumarSingh thank for comment. The test program doesn't know anything about the class Ins. I changed the constructor to private to demonstrate this. They only way to instantiate the class would be thru the companion object. I will registering a bunch of object and the test program will only know about those registered objects in the InsDelegate. – locorecto May 01 '17 at 14:33
  • You are just doing many weird things with your delegate + scala combination. :p – sarveshseri May 01 '17 at 14:40
  • @SarveshKumarSingh The Java equivalent is static initializer, so the companion object is the right place for it. – Alexey Romanov May 01 '17 at 19:30
  • Yes... but as a member value or a member function... not that way. – sarveshseri May 02 '17 at 08:20

2 Answers2

3

Scala objects are lazy, so they're only constructed when first used. In your example, the test application never creates any instances, so object Ins is never constructed.

Your code should work, but you would need to create an instance of class Ins in your test code:

object test extends App {
  val temp = Ins.make()
  InsDelegate.getRegisteredObj.foreach(_.make)
}

Incidentally, the convention for functions with side-effects (Delegate.make) is to take parentheses; a version without parentheses indicates that the function has no side-effects, which make clearly has (registering the Ins object, creating a new Ins element).

Another Scala convention is to name factory methods apply, rather than make. If you did that, you could create new Ins class instances using Ins(), instead of Ins.make(). (Ins() is interpreted to be the same as Ins.apply().)

Update: Forgot to mention this: if you want to register Ins without creating any instances first, you will need to reference it in some way. This quickly leads to ugly solutions along the lines of:

object Ins extends Delegate{
  InsDelegate.register(this)

  // Dummy method to get object to register itself.
  def register(): Unit = {}

  override def make: Ins = {
    println("This is an Ins")
    new Ins()
  }
}

object InsDelegate {
  private val objectSet = new mutable.HashSet[Delegate]()

  def register(obj: Delegate): Unit = objectSet.add(obj)

  def getRegisteredObj: Set[Delegate] = objectSet.toSet

  // Create delegate objects...
  Ins.register()
}

However, if we're going to go to that much trouble, we might as well forego registration and add objects in the InsDelegate object:

object Ins extends Delegate{
  override def make: Ins = {
    println("This is an Ins")
    new Ins()
  }
}

object InsDelegate {
  // Set of delegate objects available. Note: this is public, replaces getRegisteredObj.
  val objectSet: Set[Delegate] = Set(Ins)
}

The downside is that Delegate objects no longer register themselves, but that's a blessing in disguise as you can now test delegate creation separately from testing InsDelegate.

Mike Allen
  • 8,139
  • 2
  • 24
  • 46
  • Thanks for the clarification. It's weird but right before coming to see this answer, a co-worker was telling me exactly the same thing. I ended up writing an empty `init` method in order for the class loader to load the companion object and run the register code, which is somehow dump but it will help with the eventual goal. Also thanks for the comment regarding scala naming convention. – locorecto May 01 '17 at 16:11
0

I know that unlike java, in order to run the companion object code you need to instantiate the class of the object

Actually, your Java code would have the same result. What you need to do in both cases is to load the class, and instantiating it is just one way to do it. You can also use Class.forName, ClassLoader.loadClass, load any class which uses it somewhere in a signature... One very well-known case is (or was, before JDBC 4.0) loading JDBC drivers.

Unfortunately, in Scala the class you need to load is actually Ins$ (the class of the companion object) and instantiating Ins (or loading it in some other way) isn't necessarily enough.

Community
  • 1
  • 1
Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487