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.