おはようエンジニア

まともなエンジニアになりたい

『Kotlinイン・アクション』第6章を読む (その1)

第6章は「Kotlinの型システム」です。 本記事は型システムの中で非常に重要とされるnull許容性に関する内容です。

null許容型と演算子

  • Kotlinは null許容型 と null非許容型 を明確に区別している
    • null許容型でなければnullの参照を保持できない
>>> val s: String? = null
>>> s.toUpperCase() // nullの可能性があるのでコンパイルエラー

安全呼び出し演算子 (?.)

  • 非nullの場合 -> そのまま呼び出す
  • nullの場合 -> nullを返す
// s1 と s2 は等価
val s1 = if (s != null) s.toUpperCase() else null
val s2 = s?.toUpperCase()  // 安全呼び出し

複数のnull許容型のプロパティをつなげて呼ぶときに便利

val country: String? = this.company?.address?.country

エルビス演算子 (?:)

// 安全呼び出しの結果がnullなら 0 を返す
fun strLenSafe(s: String): Int = s?.length ?: 0

安全キャスト (as?)

  • キャストできる場合 -> キャストする
  • キャストできない場合 -> nullを返す
override fun equals(o: Any?) : Boolean {
    val otherPerson = o as? Person ?: return false
    ...

非null表明 (!!)

  • 非nullの場合 -> null非許容型に変換する
  • nullの場合 -> 例外(NullPointerException)を投げる
fun ignoreNulls(s: String?) {
    val sNotNull: String = s!!
    println(sNotNull.length)
}

null許容型の扱い方

安全呼び出し + let関数

  • let関数
    • 呼び出されたオブジェクトをラムダの引数へ変換する
    • 安全呼び出しと組み合わせることで、null非許容型の引数をとる関数にnull許容型を渡す場合に役立つ
fun sendEmailTo(email: String) { ... }
val email: String? = "hoge@example.com"

// パターン1: nullチェックしてnull非許容型として扱う
if (email != null) sendEmailTo(email)
// パターン2: letの中でnull非許容型として扱う
email?.let { sendEmailTo(it) }

遅延初期化プロパティ

  • lateinit をつけることでプロパティをコンストラクタで初期化しなくてよくなる
    • すぐに初期化できないプロパティもnull非許容型で宣言できる
  • 遅延初期化プロパティはvarになる
  • 初期化前に参照すると例外が発生する

null許容型の拡張関数

  • null許容型の拡張関数を定義すると安全呼び出しせずに関数を呼べる
    • 空文字チェックとnullチェックを同じ関数で実行したい場合など
    • 通常はnull非許容型の関数として定義するのがよい

型パラメータのnull許容性

型パラメータはnull許容型になる

fun <T> printHash(t: T) {
    println(t?.hashCode())  // null許容型なので安全呼び出しが必要
} 

null非許容にする場合は明示的に指定する

fun <T: Any> printHash(t: T) {
    println(t.hashCode())  // 安全呼び出し不要
}

Javaとの相互運用におけるnull許容型

  • Javaアノテーションがnull許容性の決定に使われる
  • アノテーションがない場合はプラットフォーム型になる
    • プラットフォーム型はnull許容と非許容どちらとしても扱える
    • コンパイラはnull許容性の情報を持っていないため実装者が責任を持つ
Java Kotlin
@Nullable + Type Type? (null許容型)
@NotNull + Type Type (null非許容型)
Type Type or Type? (プラットフォーム型)
  • KotlinでJavaAPIを扱う場合はnullを返すかどうか調べてチェックを追加する必要がある

Kotlinではnull許容型を安全で簡潔に呼び出す手段が豊富に提供されていることが分かりました。

特に、NullPointerExceptionを無くすことを目指しつつできるだけ開発者に冗長なnullチェックをさせないための配慮が随所に感じられました。

後半はプリミティブ型の扱いについてです。