12
val i: java.lang.Integer = null
val o: Option[Int] = Option(i) // This yields Some(0)

What is the safe way to convert null: java.lang.Integer to Scala Option[Int]?

Mario Galic
  • 47,285
  • 6
  • 56
  • 98

3 Answers3

21

You are mixing Int and java.lang.Integer so

val i: java.lang.Integer = null
val o: Option[Int] = Option(i)

implicitly converts to

val o: Option[Int] = Option(Integer2int(i))

which becomes

val o: Option[Int] = Option(null.asInstanceOf[Int])

thus

val o: Option[Int] = Some(0)

If you want to work with java.lang.Integer, then write

val o: Option[java.lang.Integer] = Option(i)
// o: Option[Integer] = None
Mario Galic
  • 47,285
  • 6
  • 56
  • 98
  • 2
    This was recently asked somewhere, so it's a real gotcha. Maybe the problem is relying on inference: `Option[Integer](i).map(_.intValue)` seems most idiomatic to me, since it says what it is doing. Also, use `-Xlint` to see the warning for the `val o`! – som-snytt Mar 09 '20 at 18:08
  • To avoid a boxing roundtrip, ` val x: Option[Int] = Option(i).asInstanceOf[Option[Int]]` where `Integer` is inferred. – som-snytt Mar 09 '20 at 18:24
  • more like `forced to` :) – CervEd Jun 21 '20 at 17:02
7

This seems to be happening because you are creating the Option, and converting it to an Int in one step (@MarioGalic's answer explains why this is happening).

This does what you want:

scala> val o: java.lang.Integer = null
o: Integer = null

scala> val i: Option[Int] = Option(o).map(_.toInt)
i: Option[Int] = None

scala> val o1: java.lang.Integer = 1
o1: Integer = 1

scala> val i1: Option[Int] = Option(o1).map(_.toInt)
i1: Option[Int] = Some(1)
Jack Leow
  • 21,945
  • 4
  • 50
  • 55
1

Faced the same issue before. This questionable behavior is known by the Scala team. Seems that changing it breaks something elsewhere. See https://github.com/scala/bug/issues/11236 and https://github.com/scala/scala/pull/5176 .

simpadjo
  • 3,947
  • 1
  • 13
  • 38
  • 2
    The really questionable behaviour is treating `null` as an integer. This is presumably a hangover from `C` where it is OK to assign `0` to a pointer. But this doesn't mean that the resulting pointer is `0`, so it is dodgy to switch between the two even in `C`. – Tim Mar 09 '20 at 13:16
  • `Integer` most probably came from Java code so 'don't treat null as an integer' is not an actionable advice. And we explicitly check this Integer for nullability using `Option.apply`. So we get unexpected output w/o explicitly doing any unsafe operations. – simpadjo Mar 09 '20 at 15:52
  • The point is that you can't blame Scala for "questionable behaviour" when the root cause is Java. The actionable advice is to have explicit conversion from Java types to the equivalent Scala types rather than use implicit conversion. (Hence `JavaConverters` rather than `JavaConversion`) – Tim Mar 09 '20 at 16:22
  • 1
    Well, I can and I do blame Scala for not emitting compilation error/warning in this case. Even runtime crash would be better. Even in my company alone 2 developers with 5+ years of experience in Scala have hit this problem. – simpadjo Mar 09 '20 at 17:52
  • A runtime crash would require a check for `null` every time a `java.lang.Integer` is converted to `Int`, which seems excessive. Don't forget that the issue here is the type declaration on the result, not `Option()` itself. – Tim Mar 09 '20 at 21:46
  • 1
    @Tim It would be very easy to get a runtime crash, by simply calling `theInteger.intValue()`. Avoiding that crash is what costs an extra runtime check. In older versions of Scala, this conversion indeed produced an NPE; it was reported as a bug, and fixed to the current behavior. I'm not a Scala expert, but I dug up [scala-dev#355](https://github.com/scala/scala-dev/issues/355) and [scala#5176](https://github.com/scala/scala/pull/5176) as historical context. – amalloy Mar 09 '20 at 21:56