Edited at

ScalaとJavaの境界におけるnullの扱い方

Scalaを日頃使っている方にとっては当たり前であり、また、Scalaを他の言語のプログラマ向けに紹介するときにもよく言われることではありますが、Scalaではnullを滅多に使いません。その代わりに Option[+A] 型を使います。これによって、



  • null に相当する値が出現する箇所が型によって明示される


  • null を単に無視することができなくなる(必ず処理をしなければいけない)


  • mapflatMapなどの高階関数を通じて、簡潔にOptionを扱える

といった利点が得られます。JavaでもJava 8以降で入ったOptionalを使って似た効果を得ることが出来ますが、既存のライブラリがnullを普通に使っているといった事情、フィールドなどに格納する使い方は想定されていない、といったこともあり、Scalaと同等以上の効果は見込めないでしょう(また、そもそも慣習としてOptionalを使うことがそこまで一般的でない以上、Optionalを使ったコードとそうでないコードが混ざることの問題もありそうです)。

さて、一般論としては、それでいいのですが、一つ問題があります。それは、ScalaからJavaで書かれたライブラリ(Java SEの標準ライブラリを含む)を呼び出すときです。引数としてnullを渡す必要がある場面は多くありませんが、返り値としてnullを返し得るライブラリは数多くあります。このようなライブラリの呼び出し側で、nullをどのように取り扱うかというのが今回の話です。

一つ目のやり方としては、Option(expr)という形で、とにかくJavaの境界面ではかならずOption型に変換するというものです。特に、Scalaに慣れていない人だと、Javaとの境界を意識していない人が割といるので、これはこれで一つのやり方としてありだと思います。

ただ、このような機械的なやり方には問題があるかな、と思うこともあります。それは、あるJavaライブラリのメソッド呼び出しがnullを返し得ないということが(返したとしたら、ライブラリのバグか呼び出し側のミス)あらかじめわかっている場合です。たとえば、以下のコードについて考えてみます。

import java.util

def useMap(config: util.Map[String, String]): Unit = {
// "version" に対応する値が必ずあるとわかっているとする
val version = config.get("version")
...
}

ここで、configjava.util.Mapなので、Javaライブラリの呼び出しにあたります。では、これを強制的にOptionでラップすべきか…というと微妙に思えます。というのは、ここでは仮定として、"version"があるとしているので、Optionでラップしたとしても、後で強制的にOption#getというあまり使うべきでないメソッドを呼ぶ羽目になりそうです。

とはいえ、もし呼び出し側のミスなどがあった場合に単にNullPointerExceptionが発生するのは(エラーの理由がわかりづらく)あまりうれしくなさそうです。よりよいやりかたは色々ありそうですが、こういう場合には何もしないか、あるいは、以下のようにassertを書いておくのが良さそうだ、というのが暫定的な結論です(こういうときにassertを使う慣習がそこまで一般的でなさそうなのが微妙なところですが)。

import java.util

def useMap(config: util.Map[String, String]): Unit = {
assert(config.containsKey("version"))
val version: String = config.get("version")
...
}

null安全であるとされるKotlinに関してもおそらく基本的には同じ問題があると思うのですが、どういう方針で対処しているのかは多少興味があるところです。

他によい方法の提案があれば(あるいは、Kotlinではこうしているよ!といったことがあれば)、コメントお待ちしています。