Javaエンジニアの皆様、Kotlinいかがですか?(1)
はじめに
突然ですが、Kotlin(ことりん)というプログラミング言語をご存じでしょうか。
私はここ約1年半、サーバサイドエンジニアとしてKotlinを使って開発をしてきました。
それまではJavaでの開発がメインで、Kotlin何それ?という状態でプロジェクトに参画しましたが、
今では「Javaにはもう戻れないな・・・」と思えるほど、Kotlinの良さを実感しています。
要約すると 「めっちゃ簡潔!」「本質的なことに集中できる!」 に尽きます。
ここから3記事で、そう言わせる理由を語っていこうと思います。
1. (本記事)Null安全である
2. 検査例外チェックからの解放
3. 拡張関数の存在
Null安全である
Null安全とは、null
起因の実行時エラーを防ぐ仕組みのことです。
KotlinがNull安全であることは他記事でもよく語られており、
Javaに対する大きな優位性になっています。
Javaで開発してると誰もが遭遇するだろうNullPointerException
、Kotlinではほぼ遭いません。
Javaでも、そもそもNullableな値を扱わなければいいんですが、
現実そうはいかないこともあります(以下例)。
- あるテーブルのレコードを表現したクラスにおける、NOT NULL制約ありカラムのフィールド
- Map#getのような、戻り値がnullになりうるものを格納した変数
Javaでは以下のような回避策が採れますが、いずれも不完全と考えます。
回避策 | 問題点 |
---|---|
if ( a != null ) {...} と記述する |
記述が長く、ネストが深くなる |
Optional でラップする |
引数やフィールドでの使用は非推奨で、使える場面が限定的 |
@Nullable や@NotNull を利用する |
開発プロジェクトの中で徹底させることが難しい |
では、KotlinではどのようにNull安全を実現しているのでしょうか。
a. Null許容型の導入
Kotlinで型を指定するとき、型とセットでNullableかどうかを書きます。
具体的には下記の通りで、: String
の後方にある?
が、Nullableであることを示します
?
を付与した型をNull許容型と呼びます。
// Null許容型の変数宣言
val value: String? = null
// 戻り値がNull許容型である関数の宣言
fun getValue(): String? {
return value
}
Kotlin文法知らないんだけど?という方への補足
変数でも関数でも、その名前の後で: String
と書いて型を指定する
val
: 変数冒頭に書く。val
だと再代入不可(immutable)変数になり、var
だとmutableとなる。
fun
: 関数冒頭に書く。Kotlinの場合アクセス修飾子を省略できる(省略するとpublic
になる)
通常のString
等(Null非許容型)にNull許容型を代入するとコンパイルエラーになります。
そのため実行されることがなく、NullPointerException
にも繋がりません。
では、String?
等(Null許容型)のフィールドやメソッドを用いる場合、
通常と同様String.toLowerCase()
のように書けるのでしょうか。
答えはNoで、次にその時の書き方について述べます。
b. Nullableを快適に扱える仕組み
JavaでのNullableの扱い方はすでに紹介した通りですが、
Kotlinでは非常に簡単な記述でNullableを扱うことができます。
Null許容型で初期化した、次の変数を扱うケースで具体例を紹介します。
val sample: String? = "sample text"
b1. フィールドやメソッドへのアクセス
sample?.toCharArray()
.
ではなく?.
に続けます。こうしなければコンパイルエラーで下記メッセージが出ます。
Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type 'String?'
この例の場合、sampleがnull
だと結果はnull
となります。
※sample?
より後は実行されない というイメージです!
b2. nullの場合の値指定(1) - エルビス演算子
sample?.length ?: 0
Kotlin文法知らないんだけど?という方への補足
KotlinではStringの文字列長は、length()メソッドでなくlengthフィールド参照で取得できます。
?:
(エルビス演算子)を使って「nullの場合はこれ」を指定できます。
Javaではif...else...
や3項演算子を使う場面かと思いますが、断然簡潔ですね!
b3. nullの場合の値指定(2) - if...else...式の利用
if (sample != null) sample.length else 0
if...else...
は、Kotlinでは式(値を返すもの)として使えるので、こんな書き方もできます。
因みにこの例のif節内では、()
内の条件によりsample
が非nullと確定しているので、
sample?.length
ではなくsample.length
と書いて参照でき、コンパイルエラーにもなりません。
スマートキャストと言いますが、このような仕組みも言語として備えています。
これもNullableを快適に扱える理由の一つですが、本記事ではこれくらいにしておきます。
b4. nullでない場合の後続処理
// (1)後続処理結果(値)を利用しない
sample?.let {
println(it) // コンソールに"sample text"が表示される
}
// (2)後続処理結果(値)を利用する
val sampleUpperCase: String? = sample?.let {
it.uppercase()
}
一見よくわからないと思うので解説します。私は初見で次のようになりました。
-
let
って何? -
let
の直後の{...}って何? -
it
って何?
let
は全クラスが持つインスタンスメソッドで、次のようなイメージです。
- 「
?.let
の直前変数を引数に取り、何らかの値を返す関数」を引数に取る -
let
自体の戻り値は、引数で与えた関数の戻り値である - Null許容型でも使用でき、その場合は
?.let
と呼び出す
正確な呼び出し方はlet( { ... } )
ですが、省略してlet { ... }
と書けます。
{ ... }はラムダ式で、Kotlinでの書式は次の通りです。
// String型の1引数を大文字化する関数を例にする
// (1)省略なしの記載(※funcの型はラムダ式から推論されるので記述しないことが多い)
val func = { text: String -> text.uppercase() }
// (2)1引数関数の場合、関数内でitとして自身を参照できるので、こんな書き方ができる
// ※この場合、引数型を正確に特定できないので、funcの型を要記述
val func: (String) -> String = { it.uppercase() }
記述の意味ばかりになってしまいましたが、伝えたいことは
let
関数をうまく使うことで、nullでない場合の処理を 簡潔に!流れるように! 書ける!
ということです。
b4. 引数やフィールドにも指定可能
data class Person(
val name: String,
val age: Int,
val phoneNumber: String?
)
Kotlin文法知らないんだけど?という方への補足
data classは、データ保持に特化したクラスで、Javaで書くと下記のイメージです。
/** finalにより継承不可 */
public final class Person {
/** フィールド。getterなしで参照可能。後から変更不可。*/
public final String name;
public final int age;
public final String phoneNumber;
/** コンストラクタ。全フィールド指定あり、単純代入のみ。*/
Person(@NotNull String name, int age, String phoneNumber) {
this.name = name;
this.age = age;
this.phoneNumber = phoneNumber;
}
// setterはなし
// 他、toString(), equals(), hashCode(), copy()を必要に応じて実装するが省略
}
これはフィールド[1]にNullableを指定した例です。
Optional
でnullの可能性を示唆したフィールドはJavaでは非推奨なので、地味ながら便利!
終わりに
本当はまだまだ書けるんですが、ここまでにしようと思います。
この手の技術記事執筆は初めてで、一応気を付けたつもりですが、
内容の誤り・古さ・誤解を与えそうな記述等あれば、ご助言・ご指摘いただけると幸いです。
最後まで見ていただき、ありがとうございました。
-
厳密にはプロパティです。Kotlinではフィールドとgetter/setterを合わせてプロパティと呼びます。 ↩︎
Discussion