28
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

KotlinのNull安全をJavaのOptionalと比較して理解する

28
Posted at

はじめに

Javaエンジニアなら一度は経験したことがあるはずです。

Exception in thread "main" java.lang.NullPointerException

NullPointerException(NPE)は、Javaの生みの親であるトニー・ホーアが「10億ドルの失敗」と呼んだほど、長年エンジニアを悩ませてきた問題です。

Java 8では Optional が導入されてnullを扱いやすくなりましたが、Kotlinはさらに一歩進んで言語レベルでnullを安全に扱う仕組みを提供しています。

この記事では、JavaのOptionalとKotlinのNull安全を比較しながら、Kotlinがnullをどのように解決しているかを解説します。


Javaのnull問題

まず、Javaでnullがどれほど厄介かを確認します。

public String getUserName(int userId) {
    User user = userRepository.findById(userId);
    return user.getName(); // userがnullだとNPE!
}

このコードは userRepository.findById()null を返す可能性があります。しかしコンパイルは通るため、実行時になって初めてNPEが発生します

nullチェックを追加すると…

public String getUserName(int userId) {
    User user = userRepository.findById(userId);
    if (user == null) {
        return "不明";
    }
    return user.getName();
}

シンプルな処理なのにnullチェックが増えてコードが膨らみます。さらにネストが深くなると…

// nullチェックの地獄
public String getCityName(int userId) {
    User user = userRepository.findById(userId);
    if (user != null) {
        Address address = user.getAddress();
        if (address != null) {
            City city = address.getCity();
            if (city != null) {
                return city.getName();
            }
        }
    }
    return "不明";
}

いわゆるネストの地獄です。


JavaのOptionalによる解決

Java 8で導入された Optional<T> は「値が存在するかもしれないし、しないかもしれない」を表すコンテナです。

// Optional を使った例
public Optional<User> findUser(int userId) {
    return Optional.ofNullable(userRepository.findById(userId));
}

// 呼び出し側
String name = findUser(1)
    .map(User::getName)
    .orElse("不明");

Optionalの主なメソッド

Optional<String> opt = Optional.ofNullable(getValue());

// 値が存在するか確認
opt.isPresent();                    // true / false
opt.isEmpty();                      // isPresent()の逆(Java 11以降)

// 値を取り出す
opt.get();                          // 値を取得(空だとNoSuchElementException)
opt.orElse("デフォルト");           // 空なら代替値を返す
opt.orElseGet(() -> "デフォルト"); // 空なら Supplier を実行
opt.orElseThrow();                  // 空なら例外をスロー

// 変換・フィルタ
opt.map(String::toUpperCase);       // 値を変換
opt.filter(s -> s.length() > 3);   // 条件でフィルタ
opt.flatMap(s -> Optional.of(s));   // Optional を返す変換

Optionalのアンチパターン

Optional は便利ですが、誤った使い方も多く見られます。

// ❌ isPresent() + get() はnullチェックと変わらない
if (opt.isPresent()) {
    String value = opt.get();
}

// ✅ orElse / map を使う
String value = opt.orElse("デフォルト");

// ❌ フィールドに Optional を使う
public class User {
    private Optional<String> email; // NG(シリアライズなどで問題が起きる)
}

// ❌ メソッドの引数に Optional を使う
public void sendEmail(Optional<String> email) { } // NG(呼び出し元が面倒になる)

KotlinのNull安全

Kotlinはnullの問題を型システムに組み込むことで解決しています。

Nullable型と Non-Nullable型

Kotlinでは、型宣言で nullを許可するか を明示します。

var name: String = "田中"   // Non-Nullable(nullを代入できない)
var name: String? = null    // Nullable(nullを代入できる)
var name: String = "田中"
name = null  // コンパイルエラー!

var nullableName: String? = "田中"
nullableName = null  // OK

nullを代入できない型とできる型をコンパイル時に区別するため、うっかりnullを代入するミスを防げます。

Nullable型へのアクセスはコンパイルエラー

var name: String? = "田中"
println(name.length)  // コンパイルエラー!nullかもしれないから

Kotlinは「この変数はnullかもしれないよ」とコンパイル時に教えてくれます。アクセスするには適切な処理が必要です。


Kotlinのnull安全演算子

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

nullの場合はnullを返し、非nullの場合だけメソッドを呼び出します。

val name: String? = null
println(name?.length)  // null(例外は発生しない)

val name2: String? = "田中"
println(name2?.length) // 2

Javaのnullチェックと比較すると…

// Java
String name = null;
int length = (name != null) ? name.length() : 0;
// Kotlin
val name: String? = null
val length = name?.length ?: 0

?:(エルビス演算子)

nullの場合に使うデフォルト値を指定します。

val name: String? = null
val displayName = name ?: "名無し"
println(displayName) // 名無し

val name2: String? = "田中"
val displayName2 = name2 ?: "名無し"
println(displayName2) // 田中

JavaのOptionalと比較すると…

// Java
String name = null;
String displayName = Optional.ofNullable(name).orElse("名無し");
// Kotlin(はるかに簡潔)
val name: String? = null
val displayName = name ?: "名無し"

ネストしたnullチェックもすっきり

先ほどのJavaのネスト地獄をKotlinで書き直すと…

// Kotlin
fun getCityName(userId: Int): String {
    return userRepository.findById(userId)
        ?.address
        ?.city
        ?.name
        ?: "不明"
}

?. を連鎖させるだけで、どこかが null でも安全に処理できます。

!!(非null断言演算子)

「この変数は絶対nullでない」と断言する演算子です。nullだった場合は NullPointerException が発生します。

val name: String? = "田中"
println(name!!.length) // 2

val name2: String? = null
println(name2!!.length) // NullPointerException!

⚠️ !!最終手段です。乱用するとKotlinのNull安全の恩恵がなくなります。本当にnullでないことが保証できる場合のみ使いましょう。

スマートキャスト

nullチェックをすると、Kotlinは自動的に型を Non-Nullable として扱います(スマートキャスト)。

val name: String? = getName()

if (name != null) {
    // このブロック内では name は String(Non-Nullable)として扱われる
    println(name.length) // ?.不要!
}

JavaのOptional vs KotlinのNull安全 比較

比較項目 Java Optional Kotlin Null安全
仕組み ライブラリ(ラッパークラス) 言語の型システム
強制力 任意(使わなくてもコンパイル通る) コンパイル時に強制される
記述量 やや多い 少ない(?. ?: で簡潔)
nullを返すメソッド Optional で明示できる ? で型レベルで明示
パフォーマンス ラッパーオブジェクトのオーバーヘッドあり 型情報のみなのでオーバーヘッドなし
null断言 opt.get()(空でも例外) !!(明示的)

最大の違いは強制力です。Javaの Optional はあくまでコーディング規約で使うものであり、返り値を Optional にしないメソッドが混在してもコンパイルは通ります。KotlinのNull安全は型システムに組み込まれているため、コンパイラが強制します


JavaのコードをKotlinに変換してみる

// Java
public String getDisplayName(User user) {
    if (user == null) {
        return "名無し";
    }
    String name = user.getName();
    if (name == null || name.isEmpty()) {
        return "名無し";
    }
    return name.toUpperCase();
}
// Kotlin
fun getDisplayName(user: User?): String {
    return user?.name?.takeIf { it.isNotEmpty() }?.uppercase() ?: "名無し"
}

Javaと連携するときの注意点

KotlinはJavaと相互運用できますが、Javaのコードから来る値はプラットフォーム型と呼ばれ、nullかどうかをKotlinが判断できません。

// Javaの String(@Nullable/@NonNull がない場合)は
// Kotlin では String! という「プラットフォーム型」になる
val javaString = javaObject.getString() // String!(nullかもしれない)

// 安全に扱うには明示的にNullable型として受け取る
val safeString: String? = javaObject.getString()

Java側で @Nullable / @NonNull アノテーションをつけると、Kotlin側で適切な型として認識されます。


まとめ

演算子 意味 使いどころ
?(型宣言) nullを許容する型 nullが入る可能性がある変数
?. nullなら処理をスキップ Nullable型へのアクセス
?: nullならデフォルト値を使う nullのときの代替値指定
!! nullでないと断言 nullでないことが確実な場合のみ
  • KotlinのNull安全は型システムに組み込まれているため、コンパイル時にnullの危険を検出できる
  • JavaのOptionalより記述量が少なく、強制力が強い
  • !! の乱用はNPEの温床になるため避ける
  • JavaコードとKotlinを連携させる際はプラットフォーム型に注意する

JavaのNPEに悩まされてきた方なら、KotlinのNull安全の快適さをきっと実感できます。

28
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?