NoSuchElementException とは、「取り出そうとした要素がもう無い(最初から無い)」ときに、Java の標準ライブラリが投げる例外です。 空の入力に対する Scanner の nextLine、最後まで進んだ Iterator の next、値の入っていない Optional の get、空のキューの remove など、発生元は多岐にわたります。直し方はどれも共通で、「取り出す前に、あるかどうかを確かめる」か「無いときに例外を投げないメソッドに替える」 のどちらかです。

この記事では、発生元になりやすい 26 の呼び出しを Java 21 で実際に動かし、エラーメッセージとスタックトレースの1行目から発生元を引ける表にまとめました。標準入力が空のまま実行したときに Scanner で出る例もパイプ実行で再現し、例外を投げるメソッドと投げない代替の対応表、そのまま貼って動くコードを載せています。

確認環境:Ubuntu 24.04、OpenJDK 21.0.10(java ファイル名.java の単一ファイル実行)。掲載した出力はすべてこの環境で実行したものです。

NoSuchElementException の早見表(メッセージから発生元を引く)

スタックトレースの1行目にある 「: 」より後ろのメッセージと、2行目の at に書かれたメソッド名の2つを見れば、発生元を絞り込めます。

1行目のメッセージ 2行目(at)に出るメソッド 発生元 例外を投げない書き方
No line found Scanner.nextLine 読む行が残っていない Scanner hasNextLine() で確かめてから読む
(メッセージなし) Scanner.throwFor 入力が尽きた状態の next()、nextInt() hasNext()、hasNextInt() で確かめる
No value present Optional.get 空の Optional の get()。Stream の max().get() や findFirst().get() もここ orElse、orElseGet、ifPresent
No value present Optional.orElseThrow 空の Optional の引数なし orElseThrow() 同上
No value present OptionalInt.getAsInt IntStream の max().getAsInt() など orElse(既定値)
(メッセージなし) ArrayList$Itr.next など 最後まで進んだ Iterator の next() hasNext() 1回につき next() 1回
(メッセージなし) ArrayDeque.removeFirst 空のキューの remove()、pop() poll()(空なら null)
(メッセージなし) ArrayDeque.getFirst 空のキューの element() peek()(空なら null)
(メッセージなし) ArrayList.getFirst、ArrayList.getLast 空のリストの getFirst()、getLast()(Java 21 で追加) isEmpty() を見てから呼ぶ
(メッセージなし) TreeMap.key 空の TreeSet の first()、TreeMap の firstKey() pollFirst()、firstEntry()(空なら null)
(メッセージなし) StringTokenizer.nextToken トークンが尽きた StringTokenizer hasMoreTokens() で確かめる
Vector Enumeration Vector$1.nextElement 要素が尽きた Enumeration hasMoreElements() で確かめる

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Java NoSuchElementExceptionの原因と対処法」

NoSuchElementException とはどんな例外か

Java SE 21 の API ドキュメントでは、NoSuchElementException は「要求された要素が存在しないことを示すために、さまざまなアクセサメソッドが投げる例外」と説明されています。押さえておきたい性質は次の4つです。

項目 内容
パッケージ java.util(使うときは import java.util.NoSuchElementException;)
継承関係 RuntimeException のサブクラス。非チェック例外なので、try-catch を書かなくてもコンパイルは通る
サブクラス InputMismatchException(Scanner で型が合わないときの例外)は NoSuchElementException の子
コンストラクタ 引数なし、メッセージ付き。Java 15 からは原因(cause)を渡せるものも追加

メッセージが付かない発生元が多いのが、この例外の特徴です。Exception in thread "main" java.util.NoSuchElementException とだけ表示されたときは、次の行の at を読むことが発生元を知る近道になります。

スタックトレースの読み方

次の出力を例に、どこを見るかを整理します。

Exception in thread "main" java.util.NoSuchElementException: No value present
    at java.base/java.util.Optional.get(Optional.java:143)
    at OptDemo.main(OptDemo.java:8)
見る場所 この例での内容 わかること
1行目の例外クラス名 java.util.NoSuchElementException 「要素が無い」種類の例外
1行目のメッセージ No value present Optional 系が空だった
java.base/ で始まる at 行 Optional.get 例外を投げた標準ライブラリのメソッド
自分のクラス名が出る最初の at 行 OptDemo.java:8 直す場所。自分のコードの8行目

直すのは、自分のクラス名が最初に出てくる行です。java.base/ で始まる行は標準ライブラリの中なので、読むだけで十分です。

26の呼び出しで発生元とメッセージを確かめる

発生元になりやすい呼び出しを1つのプログラムにまとめ、例外クラス・メッセージ・投げたメソッドを1行ずつ表示します。そのまま保存して java Repro.java で実行できます。

import java.util.*;
import java.util.stream.Stream;

public class Repro {
    interface Body { void run() throws Exception; }

    static void test(String label, Body body) {
        try {
            body.run();
            System.out.println(label + " => 例外なし");
        } catch (NoSuchElementException e) {
            StackTraceElement top = e.getStackTrace()[0];
            String cls = top.getClassName().replace("java.util.", "");
            System.out.println(label + " => " + e.getClass().getSimpleName()
                + ": " + e.getMessage() + " @ " + cls + "." + top.getMethodName());
        } catch (Exception e) {
            System.out.println(label + " => " + e);
        }
    }

    public static void main(String[] args) {
        List<String> empty = new ArrayList<>();
        test("Scanner.nextLine", () -> new Scanner("").nextLine());
        test("Scanner.nextInt(空)", () -> new Scanner("").nextInt());
        test("Scanner.nextInt(abc)", () -> new Scanner("abc").nextInt());
        test("Iterator.next(ArrayList)", () -> empty.iterator().next());
        test("Iterator.next(HashSet)", () -> new HashSet<String>().iterator().next());
        test("Iterator.next(LinkedList)", () -> new LinkedList<String>().iterator().next());
        test("Iterator.next(List.of)", () -> List.of().iterator().next());
        test("Optional.get", () -> Optional.empty().get());
        test("Optional.orElseThrow", () -> Optional.empty().orElseThrow());
        test("OptionalInt.getAsInt", () -> OptionalInt.empty().getAsInt());
        test("Queue.remove(ArrayDeque)", () -> new ArrayDeque<String>().remove());
        test("Queue.element(ArrayDeque)", () -> new ArrayDeque<String>().element());
        test("Deque.pop(ArrayDeque)", () -> new ArrayDeque<String>().pop());
        test("Queue.remove(LinkedList)", () -> new LinkedList<String>().remove());
        test("Queue.remove(PriorityQueue)", () -> new PriorityQueue<String>().remove());
        test("List.getFirst(ArrayList)", () -> empty.getFirst());
        test("List.getLast(ArrayList)", () -> empty.getLast());
        test("List.getFirst(List.of)", () -> List.of().getFirst());
        test("Stream.max.get", () -> Stream.<Integer>empty().max(Integer::compare).get());
        test("IntStream.max.getAsInt", () -> empty.stream().mapToInt(String::length).max().getAsInt());
        test("TreeSet.first", () -> new TreeSet<String>().first());
        test("TreeMap.firstKey", () -> new TreeMap<String, String>().firstKey());
        test("Collections.max", () -> Collections.max(empty));
        test("StringTokenizer.nextToken", () -> new StringTokenizer("").nextToken());
        test("Enumeration.nextElement", () -> new Vector<String>().elements().nextElement());
        test("List.get(0) 参考", () -> empty.get(0));
    }
}

実行結果です。null と出ている行は、メッセージが付かない発生元です。

Scanner.nextLine => NoSuchElementException: No line found @ Scanner.nextLine
Scanner.nextInt(空) => NoSuchElementException: null @ Scanner.throwFor
Scanner.nextInt(abc) => InputMismatchException: null @ Scanner.throwFor
Iterator.next(ArrayList) => NoSuchElementException: null @ ArrayList$Itr.next
Iterator.next(HashSet) => NoSuchElementException: null @ HashMap$HashIterator.nextNode
Iterator.next(LinkedList) => NoSuchElementException: null @ LinkedList$ListItr.next
Iterator.next(List.of) => NoSuchElementException: null @ ImmutableCollections$ListItr.next
Optional.get => NoSuchElementException: No value present @ Optional.get
Optional.orElseThrow => NoSuchElementException: No value present @ Optional.orElseThrow
OptionalInt.getAsInt => NoSuchElementException: No value present @ OptionalInt.getAsInt
Queue.remove(ArrayDeque) => NoSuchElementException: null @ ArrayDeque.removeFirst
Queue.element(ArrayDeque) => NoSuchElementException: null @ ArrayDeque.getFirst
Deque.pop(ArrayDeque) => NoSuchElementException: null @ ArrayDeque.removeFirst
Queue.remove(LinkedList) => NoSuchElementException: null @ LinkedList.removeFirst
Queue.remove(PriorityQueue) => NoSuchElementException: null @ AbstractQueue.remove
List.getFirst(ArrayList) => NoSuchElementException: null @ ArrayList.getFirst
List.getLast(ArrayList) => NoSuchElementException: null @ ArrayList.getLast
List.getFirst(List.of) => NoSuchElementException: null @ List.getFirst
Stream.max.get => NoSuchElementException: No value present @ Optional.get
IntStream.max.getAsInt => NoSuchElementException: No value present @ OptionalInt.getAsInt
TreeSet.first => NoSuchElementException: null @ TreeMap.key
TreeMap.firstKey => NoSuchElementException: null @ TreeMap.key
Collections.max => NoSuchElementException: null @ ArrayList$Itr.next
StringTokenizer.nextToken => NoSuchElementException: null @ StringTokenizer.nextToken
Enumeration.nextElement => NoSuchElementException: Vector Enumeration @ Vector$1.nextElement
List.get(0) 参考 => java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0

この結果から読み取れるポイントは3つあります。

  • メッセージが付くのは Scanner の nextLine(No line found)、Optional 系(No value present)、Vector の Enumeration(Vector Enumeration) で、ほかはメッセージなし(null)です。
  • Stream の max().get() は、発生元が Optional.get として表示されます。Stream のメソッド名は出てこないので、「No value present」を見たら Optional を返すメソッドの直後の get() を探します。
  • Scanner に数値以外が残っているときは InputMismatchException、list.get(0) は IndexOutOfBoundsException です。似た場面でも例外クラスが変わります。

Scanner で NoSuchElementException が出る原因と直し方

標準入力が空のまま実行すると「No line found」になる

Scanner の例外は、読もうとした入力がもう残っていないときに出ます。キーボードから入力する想定のプログラムを、入力なしで実行したときが代表例です。

import java.util.Scanner;

public class ScanLine {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String name = sc.nextLine();
        System.out.println("こんにちは、" + name + "さん");
    }
}

名前を渡して実行すると、想定どおりに動きます。

$ echo 山田 | java ScanLine.java
こんにちは、山田さん

入力を空にして実行すると、例外で止まります。空の入力は、/dev/null をつなぐ方法と、空文字をパイプで渡す方法のどちらでも再現できます。

$ java ScanLine.java < /dev/null
Exception in thread "main" java.util.NoSuchElementException: No line found
    at java.base/java.util.Scanner.nextLine(Scanner.java:1660)
    at ScanLine.main(ScanLine.java:6)
$ echo -n "" | java ScanLine.java
Exception in thread "main" java.util.NoSuchElementException: No line found
    at java.base/java.util.Scanner.nextLine(Scanner.java:1660)
    at ScanLine.main(ScanLine.java:6)

オンラインの実行環境や学習サイトで「入力欄が空のまま実行」したときに出る No line found も、同じ状態です。プログラムは正しく、入力が渡っていないだけ、ということがよくあります。まず入力欄に値を入れたかを確かめ、そのうえで入力が無い場合にも止まらない形にしておくと扱いやすくなります。

import java.util.Scanner;

public class ScanSafe {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        if (!sc.hasNextLine()) {
            System.out.println("入力がありません。名前を1行入力してください。");
            return;
        }
        String name = sc.nextLine();
        System.out.println("こんにちは、" + name + "さん");
    }
}
$ java ScanSafe.java < /dev/null
入力がありません。名前を1行入力してください。

nextInt で入力の個数が足りないとき

数値を2つ読むプログラムに、1つしか渡さなかった場合です。

import java.util.Scanner;

public class ScanTwo {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int a = sc.nextInt();
        int b = sc.nextInt();
        System.out.println(a + b);
    }
}
$ echo 5 | java ScanTwo.java
Exception in thread "main" java.util.NoSuchElementException
    at java.base/java.util.Scanner.throwFor(Scanner.java:945)
    at java.base/java.util.Scanner.next(Scanner.java:1602)
    at java.base/java.util.Scanner.nextInt(Scanner.java:2267)
    at java.base/java.util.Scanner.nextInt(Scanner.java:2221)
    at ScanTwo.main(ScanTwo.java:7)

メッセージは付かず、at の先頭に Scanner.throwFor が出ます。一方、2つ目が数値でないときは例外クラスが変わります。

$ echo "5 x" | java ScanTwo.java
Exception in thread "main" java.util.InputMismatchException
    at java.base/java.util.Scanner.throwFor(Scanner.java:947)
    at java.base/java.util.Scanner.next(Scanner.java:1602)
    at java.base/java.util.Scanner.nextInt(Scanner.java:2267)
    at java.base/java.util.Scanner.nextInt(Scanner.java:2221)
    at ScanTwo.main(ScanTwo.java:7)
入力の状態 例外 対処
入力が尽きている NoSuchElementException hasNextInt() や hasNext() で確かめる
入力は残っているが数値ではない InputMismatchException hasNextInt() で確かめ、合わないものは next() で読み飛ばす

InputMismatchException は NoSuchElementException のサブクラスなので、catch (NoSuchElementException e) と書くと両方を受け取ります。区別したいときは、InputMismatchException の catch を先に書きます。

Scanner をメソッドごとに作って閉じると、2回目で止まる

入力を2行渡しているのに No line found になる場合は、System.in を読む Scanner を複数回作って閉じていることが考えられます。

import java.util.Scanner;

public class ScanClose {
    static String ask() {
        try (Scanner sc = new Scanner(System.in)) {
            return sc.nextLine();
        }
    }
    public static void main(String[] args) {
        System.out.println("1回目: " + ask());
        System.out.println("2回目: " + ask());
    }
}
$ printf "a\nb\n" | java ScanClose.java
1回目: a
Exception in thread "main" java.util.NoSuchElementException: No line found
    at java.base/java.util.Scanner.nextLine(Scanner.java:1660)
    at ScanClose.ask(ScanClose.java:6)
    at ScanClose.main(ScanClose.java:11)

try-with-resources で Scanner を閉じると、つないでいる System.in も一緒に閉じられます。2回目に作った Scanner は閉じた入力を読むことになり、行が見つかりません。System.in を読む Scanner はプログラム全体で1つだけ作り、使い回すと解消します。

import java.util.Scanner;

public class ScanShare {
    // System.in を読む Scanner はプログラム全体で1つだけ作る
    private static final Scanner IN = new Scanner(System.in);

    static String ask() {
        return IN.hasNextLine() ? IN.nextLine() : "(入力なし)";
    }

    public static void main(String[] args) {
        System.out.println("1回目: " + ask());
        System.out.println("2回目: " + ask());
        System.out.println("3回目: " + ask());
    }
}
$ printf "a\nb\n" | java ScanShare.java
1回目: a
2回目: b
3回目: (入力なし)

Iterator の next で出る原因と直し方

Iterator では、hasNext() を1回確かめたのに next() を2回呼ぶ書き方が発生元になります。要素数が奇数のときだけ止まるため、気づきにくいパターンです。

import java.util.Iterator;
import java.util.List;

public class IterDemo {
    public static void main(String[] args) {
        List<String> names = List.of("佐藤", "鈴木", "高橋");
        Iterator<String> it = names.iterator();
        while (it.hasNext()) {
            // hasNext() 1回に対して next() を2回呼んでいる
            System.out.println(it.next() + " と " + it.next());
        }
    }
}
佐藤 と 鈴木
Exception in thread "main" java.util.NoSuchElementException
    at java.base/java.util.ImmutableCollections$ListItr.next(ImmutableCollections.java:382)
    at IterDemo.main(IterDemo.java:10)

1周目で2人分を取り出し、2周目は残り1人なのに2回目の next() を呼んで例外になりました。at に出るクラス名はコレクションの種類で変わり、ArrayList なら ArrayList$Itr.next、HashSet なら HashMap$HashIterator.nextNode です。

直し方は、next() の結果を一度変数に受けて使い回すことです。順番に処理するだけなら拡張 for 文に替えると、hasNext() と next() の対応を自分で管理しなくて済みます。

import java.util.Iterator;
import java.util.List;

public class IterFix {
    public static void main(String[] args) {
        List<String> names = List.of("佐藤", "鈴木", "高橋");

        // next() は1周につき1回。結果は変数に受ける
        Iterator<String> it = names.iterator();
        while (it.hasNext()) {
            String name = it.next();
            System.out.println(name + " さん(" + name.length() + "文字)");
        }

        // 順番に処理するだけなら拡張 for 文
        for (String name : names) {
            System.out.println(name);
        }
    }
}
佐藤 さん(2文字)
鈴木 さん(2文字)
高橋 さん(2文字)
佐藤
鈴木
高橋

Collections.max(空のリスト) も内部で Iterator の next() を呼ぶため、同じ例外になります(上の実行結果の Collections.max の行)。

Optional の get で「No value present」が出る原因と直し方

Optional は「値があるかもしれないし、無いかもしれない」を表す入れ物です。空の Optional に get() を呼ぶと No value present になります。

import java.util.Map;
import java.util.Optional;

public class OptDemo {
    public static void main(String[] args) {
        Map<String, Integer> stock = Map.of("ペン", 3);
        Optional<Integer> found = Optional.ofNullable(stock.get("ノート"));
        System.out.println(found.get());
    }
}
Exception in thread "main" java.util.NoSuchElementException: No value present
    at java.base/java.util.Optional.get(Optional.java:143)
    at OptDemo.main(OptDemo.java:8)

Map に「ノート」が無いため stock.get("ノート") は null を返し、Optional は空になります。直し方は、無いときにどうしたいかで選びます。

無いときにしたいこと 書き方
既定値を使う found.orElse(0)
既定値を作る処理が重いので、無いときだけ実行したい found.orElseGet(() -> 計算())
あるときだけ処理する found.ifPresent(v -> ...)
あるとき・無いときで処理を分ける found.ifPresentOrElse(v -> ..., () -> ...)
意味のあるメッセージを付けて例外にする found.orElseThrow(() -> new NoSuchElementException("..."))
値を変換してから既定値を使う found.map(v -> v * 2).orElse(0)

isPresent() で確かめてから get() を呼ぶ書き方でも動きますが、orElse 系にまとめると確認し忘れが起きません。引数なしの orElseThrow() は get() と同じく No value present を投げるので、無いことがありうる場面では既定値か、メッセージ付きの例外を選びます。

Queue・Deque の remove、element で出る原因と直し方

キューには、同じ働きをするメソッドが「空のとき例外を投げる版」と「null を返す版」の2系統あります。

import java.util.ArrayDeque;
import java.util.Queue;

public class QueueDemo {
    public static void main(String[] args) {
        Queue<String> tasks = new ArrayDeque<>();
        tasks.add("集計");
        System.out.println(tasks.remove());
        System.out.println(tasks.remove());
    }
}
集計
Exception in thread "main" java.util.NoSuchElementException
    at java.base/java.util.ArrayDeque.removeFirst(ArrayDeque.java:361)
    at java.base/java.util.ArrayDeque.remove(ArrayDeque.java:522)
    at QueueDemo.main(QueueDemo.java:9)
やりたいこと 空のとき例外 空のとき null
先頭を取り出す(削除する) remove()、removeFirst()、pop() poll()、pollFirst()
先頭を見る(削除しない) element()、getFirst() peek()、peekFirst()
末尾を取り出す removeLast() pollLast()
末尾を見る getLast() peekLast()

空になりうるキューでは poll() と peek() を使い、戻り値が null かどうかで分岐します。while ((task = tasks.poll()) != null) の形にすると、空になった時点でループを抜けます。

List の getFirst、getLast で出る原因と直し方(Java 21)

Java 21 では、順序のあるコレクションに共通のインターフェース SequencedCollection が加わり、List でも getFirst() と getLast() が使えるようになりました。API ドキュメントには、コレクションが空のとき NoSuchElementException を投げると書かれています。

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

public class FirstDemo {
    public static void main(String[] args) {
        List<String> items = new ArrayList<>();
        System.out.println(items.getFirst());
    }
}
Exception in thread "main" java.util.NoSuchElementException
    at java.base/java.util.ArrayList.getFirst(ArrayList.java:439)
    at FirstDemo.main(FirstDemo.java:7)

同じ空のリストでも、呼び方によって例外が変わる点に注意します。

呼び方 空のリストでの結果
list.getFirst()、list.getLast() NoSuchElementException
list.get(0) IndexOutOfBoundsException: Index 0 out of bounds for length 0
list.removeFirst()、list.removeLast() NoSuchElementException
list.stream().findFirst() 空の Optional(例外なし)

list.get(0) を getFirst() に書き換えると、catch している例外クラスが合わなくなることがあります。isEmpty() で確かめてから呼ぶか、stream().findFirst().orElse(既定値) にすると、どちらの例外も出ません。

Stream の max().get() で出る原因と直し方

max、min、findFirst、findAny、引数1つの reduce は、結果が無いことがあるため Optional を返します。空のリストに対して get() まで続けて書くと、No value present になります。

import java.util.List;

public class StreamDemo {
    public static void main(String[] args) {
        List<Integer> scores = List.of();
        int top = scores.stream().max(Integer::compare).get();
        System.out.println(top);
    }
}
Exception in thread "main" java.util.NoSuchElementException: No value present
    at java.base/java.util.Optional.get(Optional.java:143)
    at StreamDemo.main(StreamDemo.java:6)

スタックトレースには Stream のメソッド名が出ず、Optional.get とだけ表示されます。絞り込みの filter で1件も残らなかったときも同じ結果になるため、Stream の終端が Optional を返すなら get() ではなく orElse で受けるのを基本にします。

max().orElse(0) の形は、あとの「コピペ用コード」の中で実行結果つきで確かめています。

StringTokenizer の nextToken で出る原因と直し方

StringTokenizer は文字列を区切り文字で分けるクラスです。トークンの数を決め打ちで読むと、足りないときに例外になります。

import java.util.StringTokenizer;

public class TokenDemo {
    public static void main(String[] args) {
        StringTokenizer st = new StringTokenizer("A,B", ",");
        for (int i = 0; i < 3; i++) {
            System.out.println(st.nextToken());
        }
    }
}
A
B
Exception in thread "main" java.util.NoSuchElementException
    at java.base/java.util.StringTokenizer.nextToken(StringTokenizer.java:349)
    at TokenDemo.main(TokenDemo.java:7)

hasMoreTokens() で確かめながら読むと、個数が変わっても止まりません。区切って配列にしたいだけなら "A,B".split(",") でも同じことができます。

例外を投げるメソッドと、投げない代替の対応表

ここまでの内容を、1つの表にまとめます。左の列のメソッドを見つけたら、右の列に置き換えられないかを検討します。

対象 例外を投げる 事前に確かめる 例外を投げない代替
Scanner(行) nextLine() hasNextLine() ―
Scanner(数値) nextInt()、nextDouble() hasNextInt()、hasNextDouble() ―
Scanner(単語) next() hasNext() ―
Iterator next() hasNext() 拡張 for 文
ListIterator previous() hasPrevious() ―
Optional get()、orElseThrow() isPresent() orElse、orElseGet、ifPresent、ifPresentOrElse
OptionalInt など getAsInt()、getAsDouble() isPresent() orElse(既定値)
Queue(取り出す) remove() isEmpty() poll()
Queue(見る) element() isEmpty() peek()
Deque removeFirst()、removeLast()、pop()、getFirst()、getLast() isEmpty() pollFirst()、pollLast()、peekFirst()、peekLast()
List(Java 21) getFirst()、getLast()、removeFirst()、removeLast() isEmpty() stream().findFirst()
Stream max().get()、findFirst().get() ― max().orElse(既定値)
TreeSet first()、last() isEmpty() pollFirst()、pollLast()
TreeMap firstKey()、lastKey() isEmpty() firstEntry()、lastEntry()
StringTokenizer nextToken() hasMoreTokens() split()
Enumeration nextElement() hasMoreElements() ―

poll() や peek() は「空なら null」を返すため、戻り値をそのまま使うと今度は NullPointerException の原因になります。null を返す版に替えたら、null のときの処理も一緒に書くところまでがセットです。

コピペ用:例外を投げない書き方をまとめたコード

対応表の右側の書き方を、1つのプログラムで確かめます。

import java.util.*;
import java.util.stream.Stream;

public class Safe {
    public static void main(String[] args) {
        // 1. Scanner: hasNextLine / hasNextInt で確かめてから読む
        Scanner sc = new Scanner("");
        String line = sc.hasNextLine() ? sc.nextLine() : "(入力なし)";
        System.out.println("Scanner : " + line);

        // 2. Iterator: hasNext() 1回につき next() は1回
        Iterator<String> it = new ArrayList<String>().iterator();
        System.out.println("Iterator: " + (it.hasNext() ? it.next() : "(要素なし)"));

        // 3. Optional: orElse / orElseGet / ifPresentOrElse
        Optional<String> opt = Optional.empty();
        System.out.println("orElse  : " + opt.orElse("(既定値)"));
        System.out.println("orElseGet: " + opt.orElseGet(() -> "(あとから作る既定値)"));
        opt.ifPresentOrElse(
            v -> System.out.println("値あり: " + v),
            () -> System.out.println("ifPresentOrElse: 値なしの処理"));

        // 4. Queue / Deque: poll と peek は空なら null
        Queue<String> queue = new ArrayDeque<>();
        System.out.println("poll    : " + queue.poll());
        System.out.println("peek    : " + queue.peek());

        // 5. List: isEmpty() を見てから getFirst / getLast
        List<String> list = new ArrayList<>();
        String first = list.isEmpty() ? "(空のリスト)" : list.getFirst();
        System.out.println("getFirst: " + first);
        System.out.println("findFirst: " + list.stream().findFirst().orElse("(空のリスト)"));

        // 6. Stream: max / min / findFirst の結果は Optional のまま扱う
        int top = Stream.<Integer>empty().max(Integer::compare).orElse(0);
        System.out.println("max     : " + top);
        System.out.println("IntStream: " + list.stream().mapToInt(String::length).max().orElse(-1));

        // 7. TreeSet / TreeMap: pollFirst や firstEntry は空なら null
        TreeSet<String> set = new TreeSet<>();
        System.out.println("pollFirst: " + set.pollFirst());
        System.out.println("firstEntry: " + new TreeMap<String, String>().firstEntry());

        // 8. StringTokenizer: hasMoreTokens で確かめる
        StringTokenizer st = new StringTokenizer("A,B", ",");
        while (st.hasMoreTokens()) {
            System.out.print(st.nextToken() + " ");
        }
        System.out.println();
    }
}
Scanner : (入力なし)
Iterator: (要素なし)
orElse  : (既定値)
orElseGet: (あとから作る既定値)
ifPresentOrElse: 値なしの処理
poll    : null
peek    : null
getFirst: (空のリスト)
findFirst: (空のリスト)
max     : 0
IntStream: -1
pollFirst: null
firstEntry: null
A B 

すべて空の状態から呼んでいますが、例外は1つも出ていません。

try-catch で受けるか、事前に確かめるか

NoSuchElementException は非チェック例外なので、catch を書かなくてもコンパイルは通ります。どちらで対応するかは、次の基準で選べます。

状況 向いている対応
無いことが普通に起こる(検索結果が0件、キューが空) 事前に確かめる、または投げない代替を使う
無いのは想定外で、処理を続けられない 例外のまま上に伝える。メッセージを付けると調べやすい
外部からの入力を読む(標準入力、ファイル) hasNext 系で確かめ、足りないときは案内を表示する
ライブラリの呼び出し元で、利用者向けの文言に変えたい catch して、別の例外やメッセージに包み直す

想定外として例外にする場合は、自分でメッセージを付けて投げると、あとから読んだときに原因がすぐわかります。

import java.util.NoSuchElementException;
import java.util.Optional;

public class CatchDemo {
    public static void main(String[] args) {
        Optional<String> user = Optional.empty();
        try {
            // 自分で意味のあるメッセージを付けて投げる
            user.orElseThrow(() -> new NoSuchElementException("会員ID 42 が見つかりません"));
        } catch (NoSuchElementException e) {
            System.out.println("メッセージ: " + e.getMessage());
            System.out.println("発生元    : " + e.getStackTrace()[0]);
        }
    }
}
メッセージ: 会員ID 42 が見つかりません
発生元    : CatchDemo.lambda$main$0(CatchDemo.java:9)

標準ライブラリが投げるときはメッセージが空のことが多いのに対し、自分で投げるときは「何が見つからなかったか」を書けます。

似た例外との違い

「無いものを取り出そうとした」場面では、呼び方によって別の例外になります。

例外 出る場面 例
NoSuchElementException 順番に取り出す・先頭や末尾を取る操作で、要素が無い it.next()、opt.get()、queue.remove()
IndexOutOfBoundsException 番号を指定して取り出す操作で、番号が範囲外 list.get(0)
InputMismatchException Scanner で、入力はあるが型が合わない nextInt() に abc
NullPointerException null に対してメソッドを呼んだ queue.poll().length()(空のとき)

直す前に確かめるチェックリスト

確かめること 見る場所
1行目のメッセージは何か(No line found / No value present / なし) スタックトレース1行目
自分のクラス名が出る最初の at 行はどこか スタックトレース
その行で呼んでいるのは next、get、remove、element、getFirst のどれか ソースコード
対象(入力・リスト・キュー・Optional)が空になる場合はあるか 呼び出し元のデータ
hasNext() 1回に対して next() が2回以上になっていないか ループの中
System.in を読む Scanner を2つ以上作っていないか プログラム全体
Stream の終端のあとに get() を続けていないか max、min、findFirst の直後

よくある質問

Q. プログラムは合っているはずなのに、No line found が出ます。なぜですか? A. 標準入力が空のまま実行されている可能性があります。この記事では java ScanLine.java < /dev/null と空文字のパイプで同じ例外を再現しました。オンラインの実行環境なら入力欄に値を入れ、手元ならキーボードから入力するか、echo 値 | java ... の形で渡します。入力が無い場合にも動かしたいときは hasNextLine() で確かめます。

Q. Optional の get() と orElseThrow() はどう違いますか? A. 空のときの動きは同じで、どちらも No value present の NoSuchElementException を投げます(at に出るメソッド名だけが異なります)。引数にラムダ式を渡す orElseThrow(() -> new 例外(...)) なら、投げる例外とメッセージを自分で決められます。

Q. list.get(0) と list.getFirst() は、空のときに同じ例外になりますか? A. 別の例外です。get(0) は IndexOutOfBoundsException、getFirst() は NoSuchElementException になります。書き換えるときは、catch している例外クラスも一緒に見直します。

Q. NoSuchElementException を catch すれば、InputMismatchException も受け取れますか? A. 受け取れます。InputMismatchException は NoSuchElementException のサブクラスだからです。「入力が尽きた」と「型が合わない」で対応を分けたい場合は、InputMismatchException の catch を先に書きます。

まとめ

  • NoSuchElementException は「取り出そうとした要素が無い」ことを表す非チェック例外で、Scanner、Iterator、Optional、Queue、List の getFirst、Stream の結果など、発生元は多岐にわたります。
  • 1行目のメッセージと at のメソッド名で発生元を絞れます。No line found は Scanner、No value present は Optional 系、メッセージなしは Iterator・Queue・getFirst などです。
  • 標準入力が空のまま実行すると、Scanner の nextLine は No line found になります。hasNextLine() で確かめる形にすると止まりません。
  • hasNext() 1回につき next() は1回。結果は変数に受けて使い回します。
  • Optional は get() の代わりに orElse、orElseGet、ifPresent を使い、Stream の max() や findFirst() のあとも同じ形で受けます。
  • キューは remove、element の代わりに poll、peek。null が返る場合の処理もセットで書きます。
  • Java 21 の getFirst、getLast は空のリストで NoSuchElementException。isEmpty() で確かめてから呼びます。

参考・出典

  • Java SE 21 API ドキュメント:java.util.NoSuchElementException(クラスの説明、継承関係、コンストラクタ)
  • Java SE 21 API ドキュメント:java.util.SequencedCollection(getFirst、getLast、removeFirst、removeLast と空のときの例外)

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Java NoSuchElementExceptionの原因と対処法」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?