Java Streamでnull要素を除外するだけなら、`.filter(Objects::nonNull)`で対応できます。これは`x -> x != null`をメソッド参照で書いた分かりやすい形で、どちらか一方だけが特別に安全というわけではありません。注意したいのは「nullがどこにあるか」です。Stream内の要素がnullなのか、そもそもList自体がnullなのか、map後にnullが生まれるのかで対策は変わります。
まず結論:nullがある場所で対策を分ける
| nullの場所 | 主な対策 | ポイント |
|---|---|---|
| Streamの要素 | filter(Objects::nonNull) | 後続処理へnullを流さない |
| 元のList自体 | stream()前にnull確認 | filterへ到達する前にNPEになる |
| map()の戻り値 | map後にfilter(Objects::nonNull) | 変換後に生まれたnullを除外する |
| 単一のnullable値 | Stream.ofNullable(value) | nullなら空Streamにする |
| findFirst/findAnyの候補 | 先にnullを除外 | 選択された要素がnullだとNPEになる |
Oracleの`Objects.nonNull`は、`filter(Objects::nonNull)`のようにPredicateとして使うためのAPIです。まず「要素のnull」と「Streamを作る前のnull」を分けると、不要なNPE対策を増やさずに済みます。
Objects::nonNullでnull要素を除外する
Listの中にnull要素が混じっている場合は、処理の早い段階でfilterしておくと、後続のmapやmethod referenceへnullを渡さずに済みます。
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
List<String> raw = Arrays.asList("Alice", null, "Bob");
List<String> names = raw.stream()
.filter(Objects::nonNull)
.toList();
System.out.println(names); // [Alice, Bob]
`Objects::nonNull`は受け取った参照がnullでなければtrueを返します。filterはPredicateがtrueになった要素だけを残すため、null要素だけを除外できます。
Objects::nonNullとx -> x != nullはどう違う?
null除外という目的では、`.filter(Objects::nonNull)`と`.filter(x -> x != null)`は同じ判定を表します。メソッド参照の方が「nullを除外する」という意図を短く読めるため便利ですが、挙動が特別に強化されるわけではありません。
raw.stream()
.filter(Objects::nonNull)
.toList();
raw.stream()
.filter(x -> x != null)
.toList();
複合条件まで確認したい場合は、ラムダ式の方が読みやすいこともあります。書き方ではなく、どの段階でnullを許容し、どこから先では許容しないかを決める方が重要です。
List自体がnullならfilterでは防げない
`items`そのものがnullなら、`items.stream()`を呼んだ時点で例外になり、filterには到達しません。このケースはStream要素のnull対策とは分けて考えます。
import java.util.Collections;
import java.util.List;
import java.util.Objects;
List<String> safeItems = items == null
? Collections.emptyList()
: items;
List<String> result = safeItems.stream()
.filter(Objects::nonNull)
.toList();
境界でnullを受け取ること自体が想定外なら、空Listへ変換するより入力時点でfail-fastにする設計もあります。空として扱うか異常として扱うかは、業務上の意味に合わせて決めます。
mapの後にnullが生まれる場合は変換後にも確認する
元の要素が非nullでも、getterや変換処理の結果がnullになることがあります。この場合、最初のfilterだけでは後続処理のnullを防げません。
List<String> emails = users.stream()
.filter(Objects::nonNull)
.map(User::getEmail)
.filter(Objects::nonNull)
.toList();
ただし、map後のnullに業務上の意味があるなら、何でも除外すると情報が失われます。「メール未登録を捨ててよいのか」を確認し、必要ならOptionalや別の状態表現を検討します。
単一のnullable値ならStream.ofNullableも使える
Java 9以降の`Stream.ofNullable`は、値が非nullなら1要素のStream、nullなら空Streamを返します。単一値をStream処理へつなげたいときに、null判定を外側へ増やさず書けます。
import java.util.stream.Stream;
List<String> normalized = Stream.ofNullable(email)
.map(String::trim)
.filter(s -> !s.isEmpty())
.toList();
複数要素のListからnullを落とす用途は`filter(Objects::nonNull)`、単一のnullable値を0件または1件のStreamへ変換する用途は`Stream.ofNullable`と考えると使い分けやすくなります。
findFirst・findAnyの前にnullを残さない
OracleのStream APIでは、`findFirst()`と`findAny()`は選択された要素がnullの場合に`NullPointerException`を送出すると明記されています。nullが混じる可能性のあるStreamでは、検索前に除外します。
String first = raw.stream()
.filter(Objects::nonNull)
.findFirst()
.orElse("未設定");
「Optionalで返るからnullも安全に包まれる」とは限りません。Optionalを返す終端操作でも、APIごとのnull契約を確認することが大切です。
実務ではnullを消す前に意味を確認する
- null要素が単なる欠損で、処理対象外にしてよいならfilterで除外する
- List自体のnullはstream()前に扱う
- map後にnullが生まれるなら変換後の契約も確認する
- 単一値をStreamへつなぐならStream.ofNullableを検討する
- nullがエラー状態を表すなら、黙って除外せず入力検証や例外を使う
(Stream以外も含めたNullPointerExceptionの原因を整理したい場合については『Spring Boot NullPointerException完全対策|3大原因と実装例まとめ』をご参照ください)
nullを除外できることと、nullを除外してよいことは別です。データ欠損を無視してよい処理なのか、異常として検知すべき処理なのかを先に決めると、Streamのコードも読みやすくなります。
よくある質問
Objects::nonNullはラムダ式より安全ですか?
null判定そのものは同等です。`Objects::nonNull`は`filter`用Predicateとして意図が読み取りやすい書き方です。
filter(Objects::nonNull)を書けばNullPointerExceptionはすべて防げますか?
防げません。元のList自体がnullならstream()で例外になりますし、filter後のmap処理が別のnullを返す場合もあります。nullがどの段階にあるかを切り分けます。
Stream.ofNullableはいつ使いますか?
単一の値がnullかもしれず、それを0件または1件のStreamとして後続処理へつなぎたい場合に便利です。Java 9から利用できます。
まず現在のStream処理で、nullが「元のList」「要素」「map後の値」のどこに入り得るかを1つずつ確認してみてください。