【Java】nullとOptionalの違い・使い方を初心者向けに解説
Javaを勉強していると、null という値がよく出てきます。
null は「値が存在しない」ことを表す特別な値です。
例えば、以下のように String 型の変数に null を入れることができます。
String name = null;
ただし、null のままメソッドを呼び出すと、NullPointerException が発生します。
String name = null;
System.out.println(name.length());
このコードでは、name が null なのに length メソッドを呼び出しているため、エラーになります。
Javaでは、この null の扱いを誤ると、実行時エラーの原因になります。
また、Java 8以降では Optional という仕組みも登場しました。
Optional は、値が存在するかもしれないし、存在しないかもしれない、という状態を表すための型です。
Javaを学び始めたばかりの頃は、以下のような点で迷いやすいです。
- null とは何か
- NullPointerException はなぜ発生するのか
- int と Integer の null の扱いは何が違うのか
- 画面入力の未入力は null で扱ってよいのか
- Optional はどのような場面で使うのか
- Optional を引数やフィールドに使ってよいのか
この記事では、Javaにおける null と Optional について、基本的な考え方から実務での使い分けまでまとめます。
- 1. nullとは何か
- 2. 基本型はnullにならない
- 3. intとIntegerの使い分け
- 4. 画面入力の未入力はnullで扱ってよいのか
- 5. 未入力の場合の扱いは仕様で決める
- 6. null安全の基本的な考え方
- 7. nullを返さない設計も重要
- 8. 文字列比較では定数を左側に置く
- 9. Optionalとは何か
- 10. Optionalの作り方
- 11. Optionalの基本的な使い方
- 12. Optionalは主に戻り値で使う
- 13. Optionalを引数に渡し続けない
- 14. Optionalは境界で解決する
- 15. Optionalをフィールドに使うのは避ける
- 16. Optional.getをいきなり使わない
- 17. Optionalとnullチェックの使い分け
- 18. 実務でのおすすめ方針
- 19. まとめ
nullとは何か
null とは、参照型の変数に値が入っていない状態を表す特別な値です。
例えば、以下のようなコードです。
String name = null;
Integer age = null;
User user = null;
この場合、変数自体は存在していますが、参照先のオブジェクトが存在していません。
そのため、null の状態でメソッドを呼び出すと、NullPointerException が発生します。
String name = null;
System.out.println(name.length());
name は String 型ですが、実際には String オブジェクトを参照していません。
そのため、length メソッドを呼び出す対象が存在せず、NullPointerException になります。
基本型はnullにならない
Javaには、大きく分けて基本型と参照型があります。
- 基本型・・・int、long、double、boolean など
- 参照型・・・String、Integer、Long、List、独自クラスなど
基本型は null を持てません。
int count = 0;
boolean active = false;
double price = 0.0;
以下のように、int に null を入れることはできません。
int count = null;
一方で、Integer などのラッパークラスは参照型なので null を持てます。
Integer count = null;
Boolean active = null;
Double price = null;
つまり、未入力や値なしを表現したい場合は、int ではなく Integer を使う必要があります。
intとIntegerの使い分け
int と Integer の大きな違いは、null を扱えるかどうかです。
- int・・・nullにできない
- Integer・・・nullにできる
例えば、画面から数値を受け取るケースを考えます。
年齢が未入力になる可能性がある場合、int で受け取ると未入力を表現できません。
private int age;
int は null にできないため、未入力なのか、0 が入力されたのかを区別しにくくなります。
未入力と 0 を区別したい場合は、Integer を使います。
private Integer age;
この場合、未入力であれば null として扱えます。
if (age == null) {
System.out.println("年齢は未入力です");
}
画面入力の未入力はnullで扱ってよいのか
画面から渡される数値項目で、未入力の可能性がある場合は、Integer で受け取るのが自然です。
例えば、Spring Boot のフォームクラスやリクエストDTOでは、以下のように定義します。
public class UserForm {
private Integer age;
private Integer quantity;
public Integer getAge() {
return age;
}
public Integer getQuantity() {
return quantity;
}
}
未入力の場合、age や quantity は null になる可能性があります。
そのため、計算や比較をする前には null チェックが必要です。
Integer quantity = form.getQuantity();
if (quantity != null) {
int total = quantity * 100;
System.out.println(total);
}
もし quantity が null のまま計算すると、NullPointerException が発生する可能性があります。
Integer quantity = null;
int total = quantity * 100;
Integer は参照型ですが、計算時には int に変換されます。
このとき、Integer が null だと変換できないため、NullPointerException になります。
未入力の場合の扱いは仕様で決める
画面入力の未入力を null で受け取ること自体は問題ありません。
ただし、その null をどのように扱うかは、業務仕様として決める必要があります。
- 必須項目ならエラーにする
- 任意項目なら処理しない
- 未入力を 0 として扱う
- 未入力をデフォルト値に置き換える
例えば、数量が必須項目なら、null のまま処理を進めるべきではありません。
if (form.getQuantity() == null) {
throw new IllegalArgumentException("数量は必須です");
}
一方で、割引率のような任意項目であれば、未入力の場合は処理しない、という設計もあります。
Integer discountRate = form.getDiscountRate();
if (discountRate != null) {
price = price - price * discountRate / 100;
}
また、未入力を 0 として扱ってよい場合は、null を 0 に変換してから処理する方法もあります。
int discountRate = form.getDiscountRate() == null ? 0 : form.getDiscountRate();
ただし、何でも 0 にすればよいわけではありません。
例えば、年齢が未入力の場合に 0 歳として扱うと、業務的に不自然なデータになる可能性があります。
未入力と 0 を同じ意味として扱ってよいかは、項目ごとに判断する必要があります。
null安全の基本的な考え方
null安全とは、NullPointerException が発生しにくいように設計・実装する考え方です。
単に null チェックを増やすことだけが null安全ではありません。
重要なのは、null が入る可能性のある場所を限定し、どこで null を処理するかを明確にすることです。
- 画面入力では null の可能性を受け入れる
- 入力チェックで必須項目の null を弾く
- 業務処理に入る前に、必要な値が揃っている状態にする
- 処理の奥深くまで null を引き回さない
例えば、以下のように処理の最初で必須チェックを行うと、その後の処理がシンプルになります。
public void register(UserForm form) {
if (form.getName() == null) {
throw new IllegalArgumentException("名前は必須です");
}
if (form.getAge() == null) {
throw new IllegalArgumentException("年齢は必須です");
}
createUser(form.getName(), form.getAge());
}
このように、入口でチェックしておくことで、後続処理では値が存在する前提で扱いやすくなります。
nullを返さない設計も重要
null安全を考えるうえでは、できるだけ null を返さない設計も重要です。
例えば、一覧取得メソッドでデータが0件の場合、null を返すよりも空のリストを返す方が安全です。
あまりよくない例です。
public List<String> getNames() {
return null;
}
この場合、呼び出し側で毎回 null チェックが必要になります。
List<String> names = getNames();
if (names != null) {
System.out.println(names.size());
}
よい例です。
public List<String> getNames() {
return new ArrayList<>();
}
空のリストを返せば、呼び出し側はそのまま size メソッドを呼び出せます。
List<String> names = getNames();
System.out.println(names.size());
この場合、件数は 0 になりますが、NullPointerException は発生しません。
文字列比較では定数を左側に置く
Javaで文字列を比較するときにも、null に注意が必要です。
例えば、以下のコードは name が null の場合に NullPointerException になります。
if (name.equals("admin")) {
System.out.println("管理者です");
}
安全に書くなら、定数を左側に置きます。
if ("admin".equals(name)) {
System.out.println("管理者です");
}
この書き方であれば、name が null でも NullPointerException は発生しません。
Optionalとは何か
Optional は、値が存在するかもしれないし、存在しないかもしれない、という状態を表すための型です。
通常、値がないことを表すために null を使うことがあります。
User user = findUserById(userId);
この findUserById メソッドが、ユーザーが見つからない場合に null を返す設計だったとします。
呼び出し側が null の可能性を忘れてしまうと、NullPointerException の原因になります。
User user = findUserById(userId);
System.out.println(user.getName());
Optional を使うと、値がない可能性を戻り値の型として表現できます。
public Optional<User> findUserById(Long userId) {
User user = userRepository.findById(userId);
return Optional.ofNullable(user);
}
このメソッドの戻り値を見ると、呼び出し側は「ユーザーが存在しない可能性がある」と分かります。
Optionalの作り方
Optional を作る方法はいくつかあります。
値が必ず null ではない場合は、Optional.of を使います。
Optional<String> name = Optional.of("田中");
ただし、Optional.of に null を渡すと NullPointerException になります。
Optional<String> name = Optional.of(null);
null の可能性がある値を Optional にする場合は、Optional.ofNullable を使います。
String inputName = null;
Optional<String> name = Optional.ofNullable(inputName);
inputName が null の場合、Optional.empty になります。
明示的に空の Optional を作る場合は、Optional.empty を使います。
Optional<String> name = Optional.empty();
Optionalの基本的な使い方
Optional の値を扱う方法をいくつか見ていきます。
値がある場合だけ処理する
値がある場合だけ処理したい場合は、ifPresent を使います。
Optional<String> name = Optional.ofNullable(getName());
name.ifPresent(value -> {
System.out.println(value.length());
});
name に値があれば処理されます。
値がなければ、何も実行されません。
値がない場合にデフォルト値を使う
値がない場合に別の値を使いたい場合は、orElse を使います。
String name = Optional.ofNullable(getName())
.orElse("未設定");
getName の結果が null の場合、name には「未設定」が入ります。
値がない場合に例外を投げる
値がない場合に処理を続けてはいけない場合は、orElseThrow を使います。
User user = userRepository.findUserById(userId)
.orElseThrow(() -> new IllegalArgumentException("ユーザーが存在しません"));
このように書くと、ユーザーが見つからない場合は例外になります。
一方で、ユーザーが存在する場合は、Optional ではなく User として後続処理に進めます。
値を変換する
Optional の中の値を変換したい場合は、map を使います。
Optional<Integer> length = Optional.ofNullable(getName())
.map(String::length);
getName の結果が null でなければ、文字数に変換されます。
getName の結果が null であれば、Optional.empty になります。
条件に合う値だけ残す
Optional の中の値が条件に合う場合だけ残したい場合は、filter を使います。
Optional<Integer> adultAge = Optional.ofNullable(getAge())
.filter(age -> age >= 20);
getAge の結果が 20 以上であれば、値が残ります。
null または 20 未満であれば、Optional.empty になります。
Optionalは主に戻り値で使う
Optional は、主にメソッドの戻り値として使うのが一般的です。
特に、検索メソッドのように「見つからない可能性がある」処理では Optional が向いています。
例えば、ユーザーIDからユーザーを検索するメソッドです。
public Optional<User> findUserById(Long userId) {
User user = jdbcTemplate.queryForObject(...);
return Optional.ofNullable(user);
}
このように書くことで、呼び出し側に「ユーザーが見つからない可能性がある」と伝えられます。
呼び出し側では、存在しない場合の処理を明確に書きます。
Optional<User> userOpt = userRepository.findUserById(userId);
if (userOpt.isEmpty()) {
return;
}
User user = userOpt.get();
sendMail(user);
updateHistory(user);
この例では、ユーザーが存在しない場合は return しています。
その後は User が存在する前提で処理できます。
Optionalを引数に渡し続けない
Optional は戻り値としては便利ですが、引数として使うと処理が分かりづらくなることがあります。
例えば、以下のように Optional を複数のメソッドに渡すコードを考えます。
public void process(Long userId) {
Optional<User> userOpt = userRepository.findUserById(userId);
validateUser(userOpt);
sendMail(userOpt);
updateHistory(userOpt);
}
この書き方だと、各メソッドの中で毎回 Optional を確認する必要があります。
private void sendMail(Optional<User> userOpt) {
userOpt.ifPresent(user -> {
System.out.println(user.getEmail());
});
}
このようなコードは、一見安全そうに見えます。
しかし、sendMail メソッドの責務が分かりにくくなります。
- メール送信だけをするメソッドなのか
- ユーザーが存在しない場合の判定もするメソッドなのか
- ユーザーが存在しない場合は何もしない仕様なのか
このように、Optional を引数としてあちこちに渡すと、処理の責務が曖昧になりやすいです。
Optionalは境界で解決する
おすすめは、Optional を受け取った場所で、できるだけ早めに扱いを決めることです。
例えば、ユーザーが存在しない場合にエラーにするなら、orElseThrow で User に戻します。
public void process(Long userId) {
User user = userRepository.findUserById(userId)
.orElseThrow(() -> new IllegalArgumentException("ユーザーが存在しません"));
validateUser(user);
sendMail(user);
updateHistory(user);
}
この形であれば、validateUser、sendMail、updateHistory は、User が存在する前提で実装できます。
private void sendMail(User user) {
System.out.println(user.getEmail());
}
存在しない場合は何もしない仕様なら、ifPresent を使う方法もあります。
public void process(Long userId) {
userRepository.findUserById(userId)
.ifPresent(user -> {
validateUser(user);
sendMail(user);
updateHistory(user);
});
}
この場合も、各メソッドには Optional ではなく User を渡しています。
Optional は「値があるかないかを判断する境界」で使い、その後の処理では通常の型に戻すと分かりやすくなります。
Optionalをフィールドに使うのは避ける
Optional は、基本的にはフィールドには使わない方がよいです。
例えば、以下のようなフォームクラスはあまりおすすめしません。
public class UserForm {
private Optional<Integer> age;
}
画面入力DTOやEntityでは、通常の型を使うことが多いです。
public class UserForm {
private Integer age;
}
そして、処理の中で必要に応じて Optional に包みます。
Optional.ofNullable(form.getAge())
.ifPresent(age -> {
System.out.println(age);
});
Optional は「フィールドとして保持するもの」ではなく、「値がない可能性を戻り値として表現するもの」と考えると分かりやすいです。
Optional.getをいきなり使わない
Optional を使うときに注意したいのが、get メソッドです。
以下のコードは危険です。
Optional<String> name = Optional.empty();
String value = name.get();
Optional が空の場合、get を呼び出すと NoSuchElementException が発生します。
get を使う場合は、値が存在することを事前に確認する必要があります。
if (name.isPresent()) {
String value = name.get();
System.out.println(value);
}
ただし、isPresent と get の組み合わせばかり使うと、通常の null チェックとあまり変わらなくなります。
できれば、ifPresent、orElse、orElseThrow、map などを使う方が Optional らしい書き方です。
Optionalとnullチェックの使い分け
Optional を使えば、すべての null チェックが不要になるわけではありません。
画面入力、外部API、DBの値など、null が入る可能性がある場所では、null チェックが必要になることもあります。
一方で、検索メソッドの戻り値のように「値がない可能性」を呼び出し側に明示したい場合は Optional が有効です。
- 画面入力DTOの項目・・・String、Integer などで持つ
- 必須項目・・・バリデーションや null チェックで弾く
- 任意項目・・・null の場合の扱いを決める
- 検索メソッドの戻り値・・・Optional を検討する
- 後続メソッドの引数・・・Optional ではなく中身の型を渡す
つまり、Optional は null チェックの完全な代わりではありません。
値が存在しない可能性を、型として分かりやすく表現するための道具です。
実務でのおすすめ方針
実務では、次のように考えると分かりやすいです。
- 未入力がありえる画面項目は Integer や String で受け取る
- 必須項目は入力チェックで null を弾く
- 任意項目は null の場合の仕様を明確にする
- 一覧取得では null ではなく空リストを返す
- 単一検索で見つからない可能性がある場合は Optional を戻り値にする
- Optional を受け取ったら、呼び出し元で早めに処理方針を決める
- Optional を引数やフィールドとして広げすぎない
例えば、単一検索では Optional を使います。
public Optional<User> findUserById(Long userId) {
User user = userRepository.findById(userId);
return Optional.ofNullable(user);
}
呼び出し元では、存在しない場合の扱いを決めます。
User user = userRepository.findUserById(userId)
.orElseThrow(() -> new IllegalArgumentException("ユーザーが存在しません"));
sendMail(user);
このように、Optional は戻り値で使い、後続処理では通常の型に戻すと、コードが読みやすくなります。
まとめ
Javaの null と Optional についてまとめると、以下のようになります。
- null は値が存在しないことを表す
- null のままメソッドを呼ぶと NullPointerException になる
- 基本型は null にできない
- Integer や String などの参照型は null になる可能性がある
- 画面入力の未入力を扱う場合は Integer を使うことが多い
- null の場合にエラーにするのか、処理しないのか、デフォルト値にするのかを仕様で決める
- Optional は値がない可能性を表現するための型
- Optional は主に検索メソッドなどの戻り値に使う
- Optional を引数やフィールドとして使いすぎると分かりづらくなる
- Optional を受け取ったら、呼び出し元で早めに処理方針を決める
null安全で大切なのは、すべての場所で機械的に null チェックを増やすことではありません。
どこで null を許可するのか、どこで null をチェックするのか、どこから先は値が存在する前提にするのかを明確にすることです。
Optional も同じで、何でも Optional にすれば安全になるわけではありません。
検索メソッドの戻り値のように、値が存在しない可能性を明示したい場面で使うと効果的です。
まずは、画面入力では null の可能性を考慮し、検索メソッドでは Optional を戻り値に使い、後続処理では通常の型に戻して扱う、という方針を覚えておくと実務で迷いにくくなります。



