简体   繁体   中英

Scala object initialization bug

I've found a bug in initialization of "handwritten enumeration" in scala. It is handwritten because it is sealed case class and object SomeCode that have objects inside.

What is interesting - if sealed case class have default values - some dark magic happens. For example we have "handwritten enumeration" that looks like:

sealed case class SomeCode(
  id: String,
  legend: String)

object SomeCode {

  object First        extends SomeCode(id = "First", legend = "first legend")
  object Second        extends SomeCode(id = "Second", legend = "second legend")

  val values = Seq(
    SomeCode.First,
    SomeCode.Second)

  private val CACHE: Map[String, SomeCode] = {
    val ids = values.map(_.id)
    (ids zip values).toMap
  }

  def getById(id: String): Option[SomeCode] = CACHE.get(id)
}

And one test:

import com.blabla.SomeCode
import org.scalatest.{Matchers, WordSpec}

class SomeCodeTest extends WordSpec with Matchers {
  "SomeCode" should {
    "work properly" in {
      println(SomeCode.First.toString)
    }
  }
}

When we call SomeCode.Fist the initialization of object SomeCode starts. So initialization for val values and private val CACHE starts too and all fine actually.

But, if we introduce default value for our sealed case class ...:

sealed case class SomeCode(
  id: String,
  legend: String = "default legend")
...
object First        extends SomeCode(id = "First")
object Second        extends SomeCode(id = "Second")

and run our test now - we will have NPE in CACHE .

In test we call SomeCode.First - and this value will be null in values , so values.map(_.id) will throw NPE. If from tests we will call SomeCode.Second then null will be in SomeCode.Second

I know that it is not a good practice to extends sealed case class , but nevertheless it should work properly. Maybe I don't understand something in scala, but currently it looks like a compiler bug for me.

scalaVersion := "2.11.7"

Also I create issue in scala-lang: https://issues.scala-lang.org/browse/SI-9929

The problem is happening because when we call SomeCode.First.toString , the SomeCode.First is ending up as null in the following code:

val values = Seq(SomeCode.First, SomeCode.Second)

You can check the above to be true by printing the output of values in the REPL.

Interestingly, look at the REPL output with the following code:

sealed case class SomeCode(
  id: String,
  legend: String = "default")

object SomeCode {
  println("begin object init")
  object First extends SomeCode(id = "First"){println("initializing First")}
  object Second extends SomeCode(id = "Second"){println("initializing Second")}
  println(SomeCode.First)
  println(SomeCode.Second)
}

SomeCode.First

// REPL Output:
begin object init
null  <--- SomeCode.First is null!!! Why does it not initialize the First object?
initializing Second  <--- Correctly initializes the Second object.
SomeCode(Second,default)
initializing First  <--- Now starts to initialize the First object
res41: SomeCode.First.type = SomeCode(First,default)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM