Java | Java 詳細・モダン文法:Optional – フィールドに Optional を持たない理由

Java Java
スポンサーリンク

そもそも Optional は「フィールド用」に設計されていない

まず大前提として押さえておきたいのは、Optional は「戻り値用のコンテナ」として設計されている、ということです。
「値があるかもしれないし、ないかもしれない」という状況を、null ではなく型で表現するための“返り値専用の箱”という立ち位置です。

一方で、エンティティや DTO の「フィールド」は、フレームワークとの連携、シリアライズ、DB マッピングなど、いろいろな世界と接続されます。
ここに Optional を持ち込むと、その“箱”の存在がかえって邪魔になりやすい。
だから「フィールドに Optional を持たない方がいい」と言われるわけです。


例:User に Optional フィールドを持たせてみると何が起きるか

一見よさそうに見えるコード

たとえば、こんなクラスを書きたくなるかもしれません。

class User {
    private Optional<Address> address; // 住所がないかもしれないから Optional にしておこう…
}
Java

null を使わないぞ」という気合いは感じるし、パッと見は“モダン”っぽいです。
でも、実際に使い始めると、すぐに違和感が出てきます。

フレームワークと噛み合わない

この User を JPA や Jackson などのフレームワークと一緒に使おうとすると、たいていこうなります。

Optional フィールドをうまく扱えない」
「よく分からない例外が出る」
「結局カスタムコンバータを書かされる」

フレームワーク側は「普通のフィールド(Addressnull)」を前提に設計されていることが多く、Optional を直接マッピング対象にすることは想定されていません。
結果として、「null を避けたい」という気持ちで入れた Optional が、逆に現場の摩擦を増やすことになります。


「Optional の中に Optional」が簡単に生まれてしまう

すぐに二重 Optional 地獄になる

フィールドに Optional を持たせると、メソッド側でこうなりがちです。

class User {
    private Optional<Address> address;

    Optional<Optional<Address>> getAddressOptional() {
        return Optional.ofNullable(address);
    }
}
Java

あるいは、呼び出し側でこうなります。

Optional<User> maybeUser = findUser();
Optional<Optional<Address>> nested =
        maybeUser.map(User::getAddress); // Optional<Optional<Address>>
Java

Optional<Optional<Address>> という型が出てきた瞬間に、頭が一気に重くなりますよね。
「中身があるかもしれない箱の中に、さらに中身があるかもしれない箱」が入っている状態です。

もちろん flatMap で平らにできますが、そもそも「フィールドに Optional を持たなければ、こんな二重構造は生まれない」のです。


「内部表現」と「外向き API」を分けた方がきれいになる

フィールドは素直に「値 or null」で持つ

現実的で筋の良い設計は、こうです。

class User {
    private Address address; // ここは null 許容かどうかをクラス内のルールで決める

    Optional<Address> getAddressOptional() {
        return Optional.ofNullable(address);
    }
}
Java

クラスの“内側”では、Address フィールドを普通に持つ。
null を許すかどうかは、そのクラスの中のルールで決める(コンストラクタで必須にする、セッターでチェックする、など)。

そして、“外向き”のメソッドで Optional に包んで返す。
こうすると、外から見ると「住所は Optional」として安全に扱えるし、内側ではフレームワークとも素直に連携できます。

「境界」でだけ Optional に変換するイメージ

大事なのは、

「内部表現(フィールド)はシンプルに」
「外向きの API で、“ないかもしれない”を Optional で表現する」

という役割分担です。

null を完全に消すのではなく、
null が存在してもいい場所」と「null を外に漏らさない場所」を分ける。
Optional は、その“境界”で使うのが一番きれいにハマります。


「Optional フィールド」は結局どこかで null を復活させがち

orElse(null) の誘惑

フィールドに Optional を持っていると、どこかでこう書きたくなります。

Address addressOrNull = user.getAddress().orElse(null);
Java

これをやった瞬間、「null を避けるための Optional」が、「null を生成する装置」に変わります。
そして、その addressOrNull がまた別の場所に渡され、null チェック地獄が復活する。

つまり、フィールドに Optional を持っても、設計として null を閉じ込められていないことが多いのです。
むしろ「Optional も null も両方出てくる世界」になってしまい、余計にややこしくなります。

「Optional を持っているから安全」とは限らない

Optional フィールドを見て安心してしまうと、逆に危険です。

「どうせ Optional だから大丈夫でしょ」と思ってコードを追うと、
どこかで orElse(null) されていたり、
フレームワークが null を突っ込んでいたりして、
結局 NPE が飛ぶ、ということが普通に起こります。

「フィールドに Optional を持たない」というルールにしておくと、
null があり得るフィールドはどれか」がはっきりし、
「外に出すときは必ず Optional に包む」という形で、null を閉じ込めやすくなります。


設計としての一番大きな理由:「責務の境界があいまいになる」

Optional は「呼び出し側に“ないかもしれない”ことを伝えるための契約」

Optional の本質は、「呼び出し側との契約」です。

「このメソッドは、値がない可能性がありますよ」
「だから、呼び出し側で“なかったときどうするか”を必ず決めてくださいね」

というメッセージを、型で表現するためのものです。

フィールドに Optional を持ち始めると、

「このクラスの内部で、“ある/ない”の責任を誰が持つのか」
「どこで Optional を終わらせて、“必ずある前提”に切り替えるのか」

があいまいになります。

「外向き API でだけ Optional を使う」と決めると境界がはっきりする

だからこそ、こう決めてしまうと設計が一気に楽になります。

「フィールドには Optional を持たない。
Optional は“外向きの戻り値”でだけ使う。」

こうすると、

クラスの外から見ると:「この値は Optional だから、ないかもしれない」
クラスの中から見ると:「フィールドは普通の値(or null)として扱えばいい」

という役割分担がはっきりします。

責務の境界が明確になる、というのが「フィールドに Optional を持たない」最大の理由です。


まとめ:フィールドに Optional を持たない理由を自分の言葉で言うなら

あなたの言葉でまとめると、こうなります。

「Optional は“戻り値で、ないかもしれない 1 件を表現するための箱”であって、
エンティティや DTO のフィールドに持ち込むと、
フレームワークとの相性が悪くなり、Optional の二重構造が生まれ、
結局どこかで orElse(null) して null が復活し、
“どこである/ないを判断するか”という責務の境界もあいまいになる。

だから、フィールドはシンプルに値(or null)で持ち、
外向きの API でだけ Optional に包んで返す方が、
設計としてずっとスッキリして、安全で、読みやすい。」

タイトルとURLをコピーしました