今更な話題なのですが、解説したくなったので解説を書いておきます。
ここでいうunboxingとは「以下のような変換」のこと、とします。 Scalaのversionは2.13.8としておきますが、2.12でも3でもおそらく変わりません。
scala.Int
, scala.Boolean
などは、実態はJVM上でのprimitive型なので、Javaにおけるunboxingと同じと思ってもらって構いません。
そもそも、普通にScalaを書くと、あまりunboxingを意識することはありません。 Scala自体が、Javaと異なり型引数にJavaでいうprimitiveを含めた任意の型を当てはめることが可能になっているからです。
意識するとしたら、Javaのライブラリ側がboxingされたjava.lang.Integerなどを返す場合です。 Javaにおいても、java.util.Optionalなどが後から追加されたため、それらを使ってくれていれば、以下の問題は気にする必要が薄れるのですが。
それらは、以下のPredefの定義で勝手にimplicitに変換されるため、unboxingされるからといって普段は気にする必要がありません。
https://github.com/scala/scala/blob/v2.13.8/src/library/scala/Predef.scala#L487-L502
問題は
「Javaのライブラリが、あえてnullを返すためにboxingされた型を使用している場合」
です。 例えば天下のgoogleのライブラリなどでも「値があったら返し、なかったらnull返す」と、堂々とjavadocに書かれたIntegerを返すメソッドがライブラリ内部に存在していたりします。 (googleは一例であって、Javaではよくある・・・?)
実態の値がnullの場合にunboxingが発生すると、ScalaやJavaではどうなるでしょうか?
$ jshell | Welcome to JShell -- Version 11.0.15 | For an introduction type: /help intro jshell> Integer a = null a ==> null jshell> int b = a; | Exception java.lang.NullPointerException | at (#2:1) jshell> java.util.Optional<Integer> c = java.util.Optional.ofNullable(a) c ==> Optional.empty
Welcome to Scala 2.13.8 (OpenJDK 64-Bit Server VM, Java 1.8.0_332). Type in expressions for evaluation. Or try :help. scala> def a: Integer = null def a: Integer scala> val b: Int = a val b: Int = 0 scala> val c: Option[Int] = Option(a) val c: Option[Int] = Some(0) scala> val d: Option[Integer] = Option(a) val d: Option[Integer] = None
微妙にnullの扱いが異なるのがわかると思います。
また、nullをOption.applyによってNoneに変換しようとしても、implicit conversionによるunboxingは、あくまで IntegerからInt
へ関するだけで Option[Integer]
から Option[Int]
へ変換してくれるわけではないので、上記の val c: Option[Int] = Option(nullかもしれないIntegerの値)
というコードを書いた場合は、おそらく意図しない結果になっているパターンがあり得るかもしれません。
しかし、これ検知するの結構面倒というか難しいですね???
というわけでwartremoverで検知する君を作って本体に入れてリリースしました。
https://github.com/wartremover/wartremover/commit/ae298b2b771db12ad867eb01ff1f33f9bb9d044c
以上、解説含めた「新しいwartremoverのrule作ったよ」という宣伝でした。