Agent Grow Tech Notes
🍣

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では非推奨なので、地味ながら便利!

終わりに

本当はまだまだ書けるんですが、ここまでにしようと思います。

この手の技術記事執筆は初めてで、一応気を付けたつもりですが、
内容の誤り・古さ・誤解を与えそうな記述等あれば、ご助言・ご指摘いただけると幸いです。

最後まで見ていただき、ありがとうございました。

脚注
  1. 厳密にはプロパティです。Kotlinではフィールドとgetter/setterを合わせてプロパティと呼びます。 ↩︎

Agent Grow Tech Notes
Agent Grow Tech Notes

Discussion