Java | Java 標準ライブラリ:コレクションの null 許容

Java Java
スポンサーリンク
スポンサーリンク

「コレクションは null を入れていいのか?」という全体像

Java のコレクションで初心者が一番ハマるポイントの一つが、

「null を入れていいのか・ダメなのかがバラバラ」

ということです。

ArrayList には null を入れても動く
HashMap には null キーも null 値も入る
TreeMap や TreeSet の「キーや要素の null」はダメ

といったように、実装クラスごとにルールが違います。

まずは「どのコレクションが null を許すのか」という全体像を整理し、そのあとで「そもそも設計として null をどう扱うべきか」を掘り下げます。


List 系(ArrayList / LinkedList)の null 許容

List は null 要素を「普通に」持てる

List(特に ArrayList や LinkedList)は、基本的に null を要素として普通に扱えます。

import java.util.ArrayList;
import java.util.List;

public class ListNullSample {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        list.add("Alice");
        list.add(null);       // null を追加
        list.add("Bob");

        System.out.println(list.get(1));  // null
        System.out.println(list);         // [Alice, null, Bob]
    }
}
Java

コンパイルも実行も問題なく通ります。
get(1) の戻り値が null であることを、呼び出し側がちゃんと意識していれば OK です。

ただし「取り出してすぐメソッド呼び」は危険

こういうコードは危ないです。

String s = list.get(1);
System.out.println(s.length());  // s が null のとき NullPointerException
Java

List 自体は null を許しても、
「その後の処理が null を前提にしていない」と簡単に落ちます。

だから、

List に null を入れても技術的には問題ない
けれど、取り出す側で null をどう扱うかが常に問題になる

この点を強く意識しておく必要があります。


Set 系(HashSet / LinkedHashSet / TreeSet)の null 許容

HashSet / LinkedHashSet は null 要素を 1 つだけ持てる

HashSet や LinkedHashSet は、Set なので「同じ要素は 1 つだけ」でした。

null に対しても同じルールです。

import java.util.HashSet;
import java.util.Set;

public class SetNullSample {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>();

        set.add("Alice");
        set.add(null);
        set.add(null);   // 2回目の null(重複扱い)

        System.out.println(set);  // [null, Alice] など(null は1個だけ)
    }
}
Java

null の重複は許されないので、結果として

「Set の中に null が 0 個か 1 個だけ存在しうる」

という状態になります。

TreeSet は null をそもそも許さない

TreeSet は「順序付き Set」でした。内部的にはソート済みの木構造(たとえば赤黒木)を使っており、要素同士を順序比較(compareTo や Comparator)します。

null を入れようとすると、比較ができないため NullPointerException や ClassCastException につながります。そのため TreeSet は null 要素を禁止しています。

import java.util.Set;
import java.util.TreeSet;

public class TreeSetNullSample {
    public static void main(String[] args) {
        Set<String> set = new TreeSet<>();
        set.add("Alice");
        set.add(null);  // 実行時に例外になる可能性大
    }
}
Java

「順序付き」のコレクションは、基本的に null を嫌う、と覚えておくと整理しやすいです。


Map 系(HashMap / LinkedHashMap / TreeMap)の null 許容

HashMap / LinkedHashMap は「null キー 1 個」「null 値 複数」OK

HashMap や LinkedHashMap は、キーにも値にも null を許します。

import java.util.HashMap;
import java.util.Map;

public class HashMapNullSample {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();

        map.put(null, "NULL_KEY");     // null キー
        map.put("A", null);            // null 値
        map.put("B", "value");

        System.out.println(map.get(null)); // NULL_KEY
        System.out.println(map);           // {null=NULL_KEY, A=null, B=value} など
    }
}
Java

ただし、null キーは一意なので、二度目以降の map.put(null, ...) は上書きになります。
値の null は重複して何個あっても構いません。

TreeMap の null キーは禁止(値の null は実装によるが避けるべき)

TreeMap は「キーでソートされる Map」です。
TreeSet と同様、キー同士を比較して順序を決めるため、null キーは比較不能です。

そのため、TreeMap に null キーを入れると、多くの場合 NullPointerException が投げられます。

import java.util.Map;
import java.util.TreeMap;

public class TreeMapNullSample {
    public static void main(String[] args) {
        Map<String, String> map = new TreeMap<>();
        map.put(null, "NG");  // 実行時に例外
    }
}
Java

値に null を入れること自体は技術的には可能な場合もありますが、
キーに対する比較や範囲検索が主役の構造なので、実務的には「TreeMap に null は基本入れない」で覚えておく方が安全です。


「null を許すかどうか」の設計の話(ここが本当に重要)

技術的には「入る・入らない」がコレクションごとに決まっていますが、
実務レベルでは、

「そもそもコレクションに null を入れる設計にするか?」

を、ちゃんと自分で決める必要があります。

設計としては「できるだけ null を入れない」方が安全

例えば List<String> に null を入れると、

get した側は、毎回 null チェックを意識しなければいけない
stream() で扱うときに、map(s -> s.toUpperCase()) のような処理で簡単に落ちる

といった面倒が発生します。

だから、現場の設計方針としては、

  • 「コレクションの中身は原則 null 禁止」
  • 「null になりうるものは Optional や別のフラグで扱う」
  • 「そもそもその要素をコレクションに入れない(存在しないことを意味にする)」

などに寄せることが多いです。

たとえば、「まだ値が決まっていないユーザー」を表したいなら、

List<User> の中に null を入れるのではなく
User のフィールド(メールアドレスなど)を null 許容にする、あるいは Optional<String> にする

といった設計の方が扱いやすくなります。

Map の null キー・null 値は特に慎重に

HashMap は null キーも null 値も許しますが、次のような問題があります。

存在しないキーを get すると null
「キーはあるが、値が本当に null」の場合も null

ここが区別しづらいのが本当に厄介です。

そのため、

  • 「キーに null を使わない」
  • 「値に null を使うのも極力避ける(意味があるなら Optional などで表す)」

という方針にしておくと、後々のバグが減ります。


まとめ:コレクションと null を頭の中でこう整理する

最後に、初心者向けの覚え方として整理します。

  • List(ArrayList / LinkedList)は null 要素を持てる。ただし取り出し側の null チェックが必須。
  • HashSet / LinkedHashSet は null 要素を 1 個だけ持てる。TreeSet は null 要素を許さない。
  • HashMap / LinkedHashMap は「null キー 1 個」「null 値は複数 OK」。TreeMap は null キー禁止。
  • 順序付き(TreeSet / TreeMap)のコレクションは、比較の都合上 null を基本的に嫌う。
  • 設計としては「入るから入れる」ではなく、「そもそもコレクションに null を入れない方が安全か?」を最初に考える。

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