前回はKotlinにおけるクラスとその周辺の機能、
背景
java.は、nullを使用してしまうことでスローされる例外です。具体的にはString型の変数にnullを代入しておき、lengthメソッドを呼び出した場合にNullPointerException、
nullは、findUserByIdのようなメソッドがUserクラスのインスタンスを返す代わりにnullを返すと言った具合です。
このような観点で、nullは便利に働きます。しかしこのnullのおかげで筆者たちは見たくもない例外、nullチェック、if文などでnullでないことを確認すれば回避できるのですが、
nullを返さないことがわかっているメソッドの戻り値に対してnullチェックはしないのが普通だと思います。ここが重要なのですが、nullを返し得ないメソッドがあっても、nullになり得ます。つまりnullチェックをすべきものと、nullチェックが不要なものがごちゃまぜになっているので、
nullと上手に付き合う方法
nullかもしれないものと、nullではないものを区別するための方法が世の中にはいくつかあります。
メソッドシグネチャの工夫
原始的な方法です。nullを返す可能性があるメソッドのシグネチャを工夫して、getNameOrNullのような名前のメソッドです。名前を見ればnullが返されるかもしれないことに気づくわけです。
静的解析ツール
メソッドにアノテーションを付けて、getNameメソッドがnullを返すかもしれない場合には@Nullable String getName() {...}と記述し、nullを返し得ない場合には@Nonnull StringgetName() {...}と記述します。
型で表現
存在しない可能性のある値を表現するためにnullの代わりに新しく定義した型を使う方法です。具体的にはJava SE 8で導入されたjava.クラスです。値が存在しないときにはOptional#emptyで返されるオブジェクトを使用し、Optional#ofの引数に渡してラップします。Optional型とそれ以外の型で、Optionalにはさまざまな便利なメソッドが提供されています。
Kotlinのnull安全
静的解析ツールやOptionalを使うことはとても良いことです。しかし繰り返しになりますが、nullになり得ます。すべてを台無しにするコードをご覧ください
// Javaコードです
@Nonnull
Optional<String> getName() {
return null;
}
そこでKotlinのnull安全機構の登場です。Kotlinではnullの可能性のある値nullではない値
Optionalを使う方法とは異なり、
基本的な使い方
前回、aはString型です。ここでは型を明示していますが省略しても問題ありません。varキーワードにより変更可能な変数として宣言しているので"Goodbye"を代入できます。しかしその次の行のnullを代入する部分でコンパイルエラーが起こります。変数aはNotNullとして宣言されているのでnullの代入をコンパイラが許しません! 逆を言えば、nullではないと安心して使用できます。
var a: String = "Hello"
a = "Goodbye"
a = null // ここでコンパイルエラー
では、nullを代入できるNullableな変数はどのように宣言すれば良いのでしょうか。簡単です。通常の型アノテーションのあとに?を置くだけです。リスト3を見てください。変数bの型アノテーションがString?になっています。これはnullが代入可能なString型」nullを代入していますが、"Hello"はString?ではなくStringとして推論されるからです。
var b: String? = "Hello"
b = null
KotlinではNullableとNotNullを明確に区別することがわかりました。NotNullの変数にはnullが入ってこないので、nullが入る可能性があるのでNPEが起こりそうです。ということでNPEを起こしてみましょう
val s: String? = null
s.length() // ここでコンパイルエラー
String?な変数sにnullを代入して初期化しています。このsにlengthメソッドを呼び出してNPEを起こそうとしています。が、
しかし現実問題、nullチェックすることです!
リスト5のように変数sがnullでないことを確認すると、sをNotNullとして扱えるようになります。sに"Hello"が代入されていればリスト5を実行すると
if(s != null) {
println(s.length())
}
Nullableの便利な機能
KotlinのNullableを使ううえで必要な知識は、nullチェックをするコードを書くのは退屈で面倒な作業ですので、
安全呼び出し
リスト5ではNullableのメソッドを呼び出すためにnullチェックを行いました。これを簡潔に記述できるように、
リスト6の最初の行ではString?型の変数sのlengthメソッドを安全に呼び出しています。通常のメソッド呼び出しと異なるのは、?を置くことです。これにより安全呼び出しとなり、
// 安全呼び出し方式
val length1: Int? = s?.length()
// nullチェック方式
val length2: Int? = if(s != null) s.length() else null
仮にsがnullだった場合には、nullを返します。1行目の安全呼び出し方式と、nullチェック方式は等価です。
安全呼び出しはメソッドチェーンを形成したい場合などではとくに効果を発揮しますfoo()?.bar()?.baz()のように記述した場合、nullが返されても安全呼び出しがチェーンして最終的にnullが返されるだけです。
// 安全呼び出し方式
val result1 = foo()?.bar()?.baz()
// nullチェック方式
val foo = foo()
val result2 = if(foo != null) {
val bar = foo.bar()
if(bar != null) bar.baz() else null
} else {
null
}
デフォルト値
デフォルト値、nullだった場合に使用する値、?:)s?.length()は安全呼び出しにより、nullが返されます。nullが返された場合、0を使用するように指定しています。nullでない場合、
// エルビス演算子
val length1: Int = s?.length() ?: 0
// nullチェック
val len: Int? = s?.length()
val length2: Int = if(len) len else 0
禁断の!!演算子
最後に紹介するのは禁断の演算子です。
!!演算子は、String?である変数sを!!演算子により強制的にStringに変換しています。
val s: String? = "Hello"
println(s!!.length()) // => 5
この例はたまたまうまく行きました。しかしリスト10は実行時に例外を投げてクラッシュします。nullであるものに対して!!演算子を使うとKotlinNullPointerExceptionを投げます。
val s: String? = null
println(s!!.length())
nullの場合に例外が投げられる。これって結局今までと同じです。!!演算子を使用したくなったらnullチェックや安全呼び出し、!!演算子を使いましょう。その際にはコメントとして使った理由や経緯を記しておくと良いでしょう。
1つ、!!演算子を使いたくなるような例を示します。要素としてnullを許容するリストList<T?>)nullを排除してNotNullな要素だけの新しいリストList<T>)
fun <T> filterNotNull(list: List<T?>): List<T> =
list.filter { it != null }
.map { it!! }
val a: List<String?> = listOf("foo", null, "bar")
val b: List<String> = filterNotNull(a)
println(b) // => ["foo", "bar"]
list.により、null以外のものに絞り込みます。しかしリストの型は依然List<T?>のままです。そこで次のmap { it!! }で強制的に要素の型をT?からTの変換しています。
実際には!!演算子を使用せずに実装できますし、filterNotNullはコレクションの標準メソッドとして提供されています。
標準拡張関数 let
Kotlinの標準ライブラリとして、
fun <T, R> T.let(f: (T) -> R): R = f(this)
letは、this)f)
5.let {
println(it * 3) // => 15
}
関数リテラルに渡る唯一の引数it)letのレシーバと同一オブジェクトですので、itは5です。
さて、
リスト14で、succは関数であり、IntのメソッドifでnullチェックしてNullableを安全にNotNullとして扱えるようにするしかありません。
// NotNullを受け取る関数
fun succ(n: Int): Int = n + 1
// Nullableな変数
val a: Int? = 3
val b: Int? =
if(a != null) succ(a)
else null
println(b) // => 4
ここでletの登場です。まず、letは任意の型の拡張関数ですからa?.let {...}のような安全呼び出しができます。そしてletの引数となる関数f)nullのときはlet拡張関数は呼び出せないので理に適っていますね。
ということでletを使うことでリスト14のsucc適用の部分はリスト15のように書き換えられます。ちなみにリスト15はもっと簡潔に記述できます。第3回で紹介した定義済み関数の参照を得るスタイルで記述するとリスト16のようになります。
val b: Int? = a?.let { succ(it) }
val b: Int? = a?.let(::succ)
JavaですでにOptionalに親しんでいる人には、
Javaからコードを呼び出す
KotlinからJavaコードを呼び出す場合、helloメソッドをKotlinから呼び出して結果を変数に代入する際、val msg= Sample.のように型アノテーションを省略した場合、msgはNotNullとしても、msg.、msg?.length()もコンパイラは許可します。もしmsgがnullであればmsg.はNPEを起こします。
// Javaコードです
public class Sample {
public static String hello(String name) {
return "Hello, " + name + "!";
}
}
// Kotlinコードです
val msg = Sample.hello("World")
println(msg.length()) // => 13
型アノテーションを明示することでNotNull/ ただし、 今回はKotlinのユニークな機能であるnull安全に関して、 少なくとも現時点のJavaではNotNullとNullableを厳格に区別することはできません。Kotlinではこの区別を厳格にすること、 いよいよ次回はKotlinによるAndroidプログラミングについて解説します。 2022年8月18日発売val msg: String = Sample.とすれば、msgはNotNullとして扱えます。もしString.がnullを返すようなことがあれば、msgに代入する時点でjava.を投げます。これは例外を投げるタイミングが早いという観点で、msgの型アノテーションを省略したうえでNotNullとして扱うより幾分マシです。安全側に倒すならval msg: String?とNullableを表明するとよいでしょう。
intやbooleanなどのプリミティブ型を返すメソッドが返す値は、まとめ
本誌最新号をチェック!
Software Design 2022年9月号
B5判/
定価1,342円
MySQL アプリ開発者の必修5科目
不意なトラブルに困らないためのRDB基礎知識
OSSソースコードリーディングのススメ
企業のシステムを支えるOSとエコシステムの全貌
今さら聞けないSSH
MySQLで学ぶ文字コード
新生
