Kotlinのビックリマークの意味と使いどころ

Kotlin初心者向けの文法解説です。今回は変数の後ろにつける!!演算子の意味と使いどころを説明します。

!!を含むKotlin特有の演算子については、「Kotlinの名前が分からないアレ」という記事でも紹介していますので、良ければご覧ください。

Not-Null Assertion Operator

!!演算子の名前はnot-null assertion operatorといいます。変数の後ろにビックリマーク(エクスクラメーションマーク)を2つ並べて記述するこの演算子は、nullableな型の変数を強制的にnon-nullに変換する働きをします。

val str: String? = "test"
val len = str!!.length     // 4

上記の例ではstrString?ですのでnullableですが、明らかにnullではないことが分かっているので、not-null assertion operator!!でnon-nullなStringに変換しています。一方で下記のように、nullが代入された変数に対して!!を使った場合は、NullPointerExceptionが発生します。

val str: String? = null
val len = str!!.length     // NullPointerException

Null安全

Kotlinは、実行時にNullPointerExceptionを極力起こさせないように設計されている言語です。nullを代入できる型(nullable)と代入できない型(non-null)を明確に区別し、nullableな変数のプロパティやメソッドを直接呼び出すことはできません。そして通常は、このNull安全な言語仕様を活かして、安全呼び出し?.やエルビス演算子?:を使います。!!を使うということは、この安全性を犠牲にして「あえて」non-nullとして扱うということになります。

kotlinlang.orgにおもしろい説明が載っていたので引用します。

The third option is for NPE-lovers: the not-null assertion operator (!!) converts any value to a non-null type and throws an exception if the value is null.

https://kotlinlang.org/docs/null-safety.html#the-operator

Thus, if you want an NPE, you can have it, but you have to ask for it explicitly, and it does not appear out of the blue.

https://kotlinlang.org/docs/null-safety.html#the-operator

NPE-lover(笑)。そんな人がいるんでしょうか。JavaからKotlinに移行してNullPointerExceptionが出なくなったことでさみしさを感じている人がいるんでしょうかね。まぁこの説明からも分かる通り、NullPointerExceptionを発生させたければ、強い意志を持って!!を使いましょう。そうでなければ、NullPointerExceptionが勝手に突然出てくることはないですよ、ってことですね。

!!演算子の使いどころ

そんな推奨されない演算子!!は、どんな場面で使うのでしょうか。

はっきり言うと、!!は使うべきではありません。コーディング規約で禁止している場合も多いと聞きます。せっかくのKotlinの安全性を無に帰す演算子ですので、使わない方がいいです。上の例であれば、次のように書くことで安全に処理できます。

val str: String? = null
val len = str?.length ?: 0     // 0

それでもあえて使いどころを挙げるとすれば、「フレームワークの都合上nullableな変数が必要だけれど、実際にその変数を使うタイミングではnon-nullであることが保証されている」というような場合です。例えばデベロッパーガイドの「フラグメントでビューバインディングを使用する」には以下のようなサンプルコードが記載されています。

private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    _binding = ResultProfileBinding.inflate(inflater, container, false)
    val view = binding.root
    return view
}

override fun onDestroyView() {
    super.onDestroyView()
    _binding = null
}

_bindingは、メモリリークを避けるためにonDestroyView()でnullを代入する必要があるので、nullableになっています。ですが、onCreateView()以降onDestroyView()までの期間はnon-nullであることが分かり切っているので、毎回「?.」を使う手間を避けるため、「!!」を使ってnot-nullなbindingのgetterを定義しています。

当然、onCreateView()より前やonDestroyView()より後にbindingにアクセスすると、NullPointerExceptionが発生します。ですが、そんなコードがあるとしたら、Viewが存在しない状態でViewにアクセスしているということなので、そもそもの実装が間違っています。フレームワーク上妥当なコードを書いている限りは、NullPointerExceptionは発生しないということになります。

また、実際にbindingを使う際には、binding.textView.textのように後ろにいくつものプロパティやメソッドがつながることが多いので、bindingがnullableだと「?.」がいくつも連なることになってしまうので、bindingをnot-nullとして扱うことはメリットがあるとも言えます。

以上、Kotlinのビックリマーク、not-null assertion operatorの意味と使いどころの紹介でした。場合によってはメリットがあることもありますが、「?.」を使うのが面倒だからなどの理由で「!!」を多用していると、大事な時に例外が発生して苦労するというようなことになりかねないので、使う場面は慎重に選んでください。