Scannerとは、キーボード入力(標準入力)やファイル・文字列から、文字列や数値を1つずつ取り出すためのクラスです。 new Scanner(System.in) で作り、1行まるごと読むなら nextLine()、整数を1つ読むなら nextInt()、空白で区切られた単語を1つ読むなら next() を使います。

Scannerでいちばん多い質問は、「nextInt() のあとに nextLine() を呼ぶと、入力を待たずに空文字が返ってくる」というものです。この記事では、その現象をOpenJDK 21.0.10で再現し、3通りの対処を実行結果つきで比べます。あわせて、hasNextInt を使った入力チェック、InputMismatchException の実際のスタックトレース、複数行・空白区切りの入力パターン集、BufferedReader との速度比較、System.in を閉じるときの注意まで、1本でたどれるようにしました。Java 25での差分は公式APIドキュメントで確認して注記しています。

Scannerの早見表

やりたいこと メソッド 読む範囲 読んだあとの位置
1行まるごと読む nextLine() 行末の改行の手前まで(空白も含む) 次の行の先頭(改行も消費する)
単語を1つ読む next() 次の空白・改行の手前まで 単語の直後(改行は残る)
整数を読む nextInt() 整数1つ 数字の直後(改行は残る)
大きな整数を読む nextLong() 整数1つ(long の範囲) 数字の直後
小数を読む nextDouble() 小数1つ 数字の直後
true/falseを読む nextBoolean() true か false 直後
次が整数か確かめる hasNextInt() 読まない(確認だけ) 変わらない
次のトークンがあるか hasNext() 読まない 変わらない
次の行があるか hasNextLine() 読まない 変わらない
1文字読む next().charAt(0) 単語の1文字目 単語の直後
区切り文字を変える useDelimiter(",") ― ―
よくある症状 原因 対処
nextInt() の次の nextLine() が空文字になる 数字のあとの改行が残っている nextLine() を1回はさんで読み捨てる
InputMismatchException 整数でない入力を nextInt() で読んだ hasNextInt() で先に確かめる、または行で読んで Integer.parseInt
NoSuchElementException 入力が尽きた、または System.in が閉じられている hasNext() で確かめる。Scannerは1つを使い回す

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

(記載例)出典:株式会社RJC「Java Scannerの使い方|nextLineとnextIntの違いと改行残り」

まず動かす:名前と年齢を読む基本コード

Scannerを使うには java.util.Scanner をimportし、new Scanner(System.in) で標準入力につなぎます。 下のコードを Hello.java として保存し、java Hello.java で実行します。

import java.util.Scanner;

public class Hello {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.print("名前: ");
        String name = sc.nextLine();   // 1行まるごと
        System.out.print("年齢: ");
        int age = sc.nextInt();        // 整数1つ
        System.out.println();
        System.out.println(name + "さんは" + age + "歳です");
    }
}

実行結果(OpenJDK 21.0.10。入力は printf '山田 太郎\n28\n' | java Hello.java で渡しています):

名前: 年齢: 
山田 太郎さんは28歳です

入力をパイプで渡したため、打ち込んだ文字は画面に出ていません。ターミナルで手入力した場合は、名前: のあとに自分で打った文字が表示され、Enterを押すと次へ進みます。この記事の実行結果は、すべて同じようにパイプやファイルで入力を渡して取得したものです。

  • nextLine() は空白を含めて1行を読むので、「山田 太郎」がそのまま取れます。 next() で読むと「山田」までになります。
  • System.in は「標準入力」を表します。 キーボードだけでなく、java Hello.java < input.txt のようにファイルをつないでも同じコードで読めます。
  • 日本語の入力が文字化けするときは、new Scanner(System.in, "UTF-8") のように、第2引数でターミナル側の文字コード名を指定します(Java 18以降は、指定しない場合の既定がUTF-8です)。

nextLine・next・nextIntの違い

Scannerは入力を「トークン(空白や改行で区切られたかたまり)」に分けて読みます。 next() と nextInt() などの nextXxx はトークン単位、nextLine() だけが行単位で読む、というのが違いの中心です。

次の入力を、いろいろなメソッドで順に読んでみます。

red blue green
42 3.14
9999999999 true 0.10
ff tail
last line
import java.util.Scanner;

public class Tokens {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("next()      = [" + sc.next() + "]");
        System.out.println("nextLine()  = [" + sc.nextLine() + "]");
        System.out.println("nextInt()   = " + sc.nextInt());
        System.out.println("nextDouble()= " + sc.nextDouble());
        System.out.println("nextLong()  = " + sc.nextLong());
        System.out.println("nextBoolean = " + sc.nextBoolean());
        System.out.println("nextBigDecimal = " + sc.nextBigDecimal());
        System.out.println("nextInt(16) = " + sc.nextInt(16));
        System.out.println("nextLine()  = [" + sc.nextLine() + "]");
        System.out.println("nextLine()  = [" + sc.nextLine() + "]");
        System.out.println("hasNext()   = " + sc.hasNext());
        System.out.println("hasNextLine()= " + sc.hasNextLine());
    }
}

実行結果(OpenJDK 21.0.10):

next()      = [red]
nextLine()  = [ blue green]
nextInt()   = 42
nextDouble()= 3.14
nextLong()  = 9999999999
nextBoolean = true
nextBigDecimal = 0.10
nextInt(16) = 255
nextLine()  = [ tail]
nextLine()  = [last line]
hasNext()   = false
hasNextLine()= false
メソッド 読む単位 前にある空白・改行 後ろの改行 戻り値
next() トークン 飛ばす 残す String
nextInt() / nextLong() トークン 飛ばす 残す int / long
nextDouble() トークン 飛ばす 残す double
nextBoolean() トークン 飛ばす 残す boolean
nextBigDecimal() トークン 飛ばす 残す BigDecimal
nextLine() 行 飛ばさない(そのまま含める) 消費する(戻り値には含めない) String

実行結果の2行目に注目してください。next() で red を読んだあとの nextLine() は、同じ行の残り blue green(先頭に空白つき)を返しています。 nextLine() は「次の行」を読むメソッドではなく、「今いる位置から行末まで」を読むメソッドです。これが、次の節で扱う改行残りの正体です。

なお、nextChar() というメソッドはありません。1文字だけ読みたいときは sc.next().charAt(0) と書きます。

nextIntのあとのnextLineが飛ばされる現象を再現する

nextInt() のあとに nextLine() を呼ぶと、入力を待たずに空文字が返ります。 年齢を読んでから名前を読むプログラムで再現します。

import java.util.Scanner;

public class Skip {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int age = sc.nextInt();
        String name = sc.nextLine();   // ここが空文字になる
        System.out.println("age=" + age);
        System.out.println("name=[" + name + "] 長さ=" + name.length());
        System.out.println("次の行=[" + sc.nextLine() + "]");
    }
}

実行結果(OpenJDK 21.0.10。入力は 28 と 山田 太郎 の2行):

age=28
name=[] 長さ=0
次の行=[山田 太郎]

name が長さ0の空文字になり、本来読みたかった「山田 太郎」は、その次の nextLine() で読まれています。

なぜ空文字になるのか

入力は、Scannerから見ると改行(\n)を含んだ1本の文字の並びです。

2 8 \n 山 田   太 郎 \n
手順 呼び出し 読んだ部分 残り
1 nextInt() 28 \n山田 太郎\n
2 nextLine() 位置から行末まで=空文字(\n を消費) 山田 太郎\n
3 nextLine() 山田 太郎(\n を消費) なし

nextInt() は数字だけを読み、Enterで入った改行を残します。 続く nextLine() は「今の位置から行末まで」を返すので、残っていた改行だけを消費して空文字を返します。Scannerが入力を読み飛ばしたのではなく、仕様どおりに動いた結果です。next()・nextDouble()・nextLong() など、トークン単位で読むメソッドのあとでも同じことが起きます。

対処1:nextLineを1回はさんで読み捨てる

int age = sc.nextInt();
sc.nextLine();                 // 残った改行を読み捨てる
String name = sc.nextLine();
System.out.println("age=" + age + " name=[" + name + "]");
age=28 name=[山田 太郎]

対処2:すべてnextLineで読み、数値は変換する

int age = Integer.parseInt(sc.nextLine().trim());  // 行で読んでから変換
String name = sc.nextLine();
System.out.println("age=" + age + " name=[" + name + "]");
age=28 name=[山田 太郎]

対処3:あとの読み取りもnextにそろえる

int age = sc.nextInt();
String name = sc.next();       // 空白・改行を飛ばして次の単語を読む
System.out.println("age=" + age + " name=[" + name + "]");
age=28 name=[山田]

(3つとも Skip.java の main の中身を差し替え、同じ2行の入力で実行した結果です。)

3通りの比較と選び方

対処 書き方 空白を含む文字列 向いている場面
1. 読み捨て sc.nextInt(); sc.nextLine(); 読める 既存のコードを最小の変更で直す
2. 行で統一 Integer.parseInt(sc.nextLine().trim()) 読める 対話型の入力。1行に1つの値を入れてもらう
3. next で統一 sc.nextInt(); sc.next(); 読めない(最初の単語まで) 空白区切りのデータ。文字列に空白が入らない

対話型のプログラムでは、対処2の「全部 nextLine() で読む」がいちばん安定します。 改行の位置を気にする必要がなくなり、数値でない入力があったときも、その行をまるごと捨ててやり直せるからです。対処3は、実行結果のとおり「山田 太郎」が「山田」までになる点に注意します。

nextIntに数字以外を入れるとInputMismatchException

nextInt() に整数として読めない入力が来ると、InputMismatchException が発生します。

import java.util.Scanner;

public class Mismatch {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        System.out.println(n);
    }
}

abc を入力したときの実行結果(OpenJDK 21.0.10):

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 Mismatch.main(Mismatch.java:6)

入力を変えて、同じプログラムを実行した結果をまとめます。

入力 結果(1行目) 理由
abc java.util.InputMismatchException 整数の形ではない
3.5 java.util.InputMismatchException 小数は nextInt() では読めない(nextDouble() なら読める)
3000000000 java.util.InputMismatchException: For input string: "3000000000" int の上限(2147483647)を超えている。nextLong() なら読める
(入力なし) java.util.NoSuchElementException 読むものがない

例外を捕まえるだけだと同じ入力を読み続ける

InputMismatchException が出たとき、読めなかったトークンは入力に残ったままです。 try-catch で囲んでループするだけでは、同じトークンを何度も読むことになります。

import java.util.InputMismatchException;
import java.util.Scanner;

public class Loop {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int count = 0;
        while (count < 3) {
            try {
                int n = sc.nextInt();
                System.out.println("OK: " + n);
                break;
            } catch (InputMismatchException e) {
                count++;
                System.out.println(count + "回目: InputMismatchException(トークンは残ったまま)");
                // sc.next(); を入れないと同じ "abc" を読み続ける
            }
        }
    }
}

abc を1回だけ入力したときの実行結果(OpenJDK 21.0.10):

1回目: InputMismatchException(トークンは残ったまま)
2回目: InputMismatchException(トークンは残ったまま)
3回目: InputMismatchException(トークンは残ったまま)

入力は1回なのに、例外が3回出ています。回数の上限(count < 3)を付けていなければ、止まらずに回り続けます。catch の中で sc.next() または sc.nextLine() を呼び、読めなかった入力を捨ててから次へ進むのが直し方です。

hasNextIntで入力をチェックする

hasNextInt() は「次のトークンが整数として読めるか」を、読み進めずに確かめるメソッドです。 例外に頼らずに、数値とそれ以外を振り分けられます。

import java.util.Scanner;

public class HasNextInt {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int sum = 0;
        while (sc.hasNext()) {
            if (sc.hasNextInt()) {
                int v = sc.nextInt();
                sum += v;
                System.out.println("整数: " + v);
            } else {
                String bad = sc.next();          // 読み捨てないと同じトークンで止まり続ける
                System.out.println("読み飛ばし: " + bad);
            }
        }
        System.out.println("合計: " + sum);
    }
}

実行結果(OpenJDK 21.0.10。入力は 10 abc 20 と 3.5 30 の2行):

整数: 10
読み飛ばし: abc
整数: 20
読み飛ばし: 3.5
整数: 30
合計: 60
メソッド true になる条件
hasNext() 次のトークンがある
hasNextInt() 次のトークンが int として読める
hasNextLong() 次のトークンが long として読める
hasNextDouble() 次のトークンが double として読める
hasNextLine() まだ読んでいない行がある

hasNextXxx はキーボード入力の場合、次の入力が来るまで待ちます。ファイルやパイプでは、入力の終わりに達すると false を返します。

正しい値が入るまで聞き直す(コピペ用の完成形)

対話型で「1〜5の整数」を入力してもらうときの、行単位で読む書き方です。数値以外・空行・範囲外のどれが来ても、その行を捨てて聞き直します。

import java.util.Scanner;

public class Retry {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n;
        while (true) {
            System.out.print("1〜5の整数を入力: ");
            if (!sc.hasNextLine()) {            // 入力が尽きた(Ctrl+D など)
                System.out.println("\n入力が終わりました");
                return;
            }
            String line = sc.nextLine().trim();
            try {
                n = Integer.parseInt(line);
            } catch (NumberFormatException e) {
                System.out.println("\n  → [" + line + "] は整数ではありません");
                continue;
            }
            if (n < 1 || n > 5) {
                System.out.println("\n  → " + n + " は範囲外です");
                continue;
            }
            break;
        }
        System.out.println("\n入力値: " + n);
    }
}

実行結果(OpenJDK 21.0.10。入力は abc、空行、9、3 の4行。パイプ入力で見やすくするため、メッセージの前に改行を入れています):

1〜5の整数を入力: 
  → [abc] は整数ではありません
1〜5の整数を入力: 
  → [] は整数ではありません
1〜5の整数を入力: 
  → 9 は範囲外です
1〜5の整数を入力: 
入力値: 3
方法 書き方 特徴
行で読んで変換 Integer.parseInt(sc.nextLine().trim()) を try-catch 行ごとやり直せる。改行残りが起きない。対話型向き
先に確かめる if (sc.hasNextInt()) sc.nextInt(); else sc.next(); 例外を使わない。空白区切りのデータ向き
例外を捕まえる catch (InputMismatchException e) { sc.next(); } 読み捨てを忘れると同じ入力を読み続ける

入力パターン集:複数行・空白区切り・カンマ区切り

空白区切りや複数行のデータは、nextInt() と next() を並べるだけで読めます。 これらは空白も改行も同じ「区切り」として飛ばすので、1行に並んでいても複数行に分かれていても同じコードで読めます。

次の入力(patterns.txt)を、6つのパターンで順に読みます。

3
10 20 30
2 3
1 2 3
4 5 6
2
ペン 40
ノート 12
備品 管理 メモ
A,B,,D
1行目

2行目
import java.util.*;

public class Patterns {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        // (A) 1行目に件数N、2行目にN個の整数(空白区切り)
        int n = sc.nextInt();
        int[] a = new int[n];
        for (int i = 0; i < n; i++) a[i] = sc.nextInt();
        System.out.println("A: " + Arrays.toString(a));

        // (B) H行W列の表
        int h = sc.nextInt(), w = sc.nextInt();
        int[][] g = new int[h][w];
        for (int i = 0; i < h; i++)
            for (int j = 0; j < w; j++) g[i][j] = sc.nextInt();
        System.out.println("B: " + Arrays.deepToString(g));

        // (C) 名前と数値が1行に並ぶ行がM行
        int m = sc.nextInt();
        Map<String, Integer> stock = new LinkedHashMap<>();
        for (int i = 0; i < m; i++) stock.put(sc.next(), sc.nextInt());
        System.out.println("C: " + stock);

        // (D) 空白を含む1行(直前が nextInt なので読み捨てが必要)
        sc.nextLine();
        String title = sc.nextLine();
        System.out.println("D: [" + title + "]");

        // (E) 1行をカンマで分ける
        String[] cols = sc.nextLine().split(",");
        System.out.println("E: " + Arrays.toString(cols) + " 個数=" + cols.length);

        // (F) 個数がわからない行を最後まで
        List<String> rest = new ArrayList<>();
        while (sc.hasNextLine()) {
            String line = sc.nextLine();
            if (line.isBlank()) continue;
            rest.add(line);
        }
        System.out.println("F: " + rest);
    }
}

実行結果(OpenJDK 21.0.10。java Patterns.java < patterns.txt):

A: [10, 20, 30]
B: [[1, 2, 3], [4, 5, 6]]
C: {ペン=40, ノート=12}
D: [備品 管理 メモ]
E: [A, B, , D] 個数=4
F: [1行目, 2行目]
入力の形 読み方
整数が1つ int n = sc.nextInt();
1行に整数が2つ(空白区切り) int a = sc.nextInt(), b = sc.nextInt();
N個の整数(1行でも複数行でも) for (int i = 0; i < n; i++) a[i] = sc.nextInt();
H行W列の表 二重の for で sc.nextInt()
単語と数値の組 sc.next() と sc.nextInt() を続けて呼ぶ
空白を含む1行 sc.nextLine()(直前がトークン読みなら1回読み捨てる)
カンマ区切りの1行 sc.nextLine().split(",")
行数がわからない while (sc.hasNextLine()) { ... sc.nextLine() ... }
値の個数がわからない while (sc.hasNextInt()) { ... sc.nextInt() ... }

(D) のように、トークン読みから行読みに切り替えるところだけ、sc.nextLine() の読み捨てが1回必要です。 切り替えの回数が多い入力は、最初から行で読んで split するほうが見通しがよくなります。

区切り文字を変える・文字列やファイルから読む

Scannerは System.in 以外も読めます。useDelimiter で区切りを正規表現で指定でき、文字列を渡せばその文字列を入力として読みます。

Scanner sc = new Scanner("ペン,120,40\nノート,300,12\n").useDelimiter("[,\\n]");
while (sc.hasNext()) {
    String name = sc.next();
    int price = sc.nextInt();
    int stock = sc.nextInt();
    System.out.println(name + " 価格=" + price + " 在庫=" + stock);
}
ペン 価格=120 在庫=40
ノート 価格=300 在庫=12

ファイルを読むときは new Scanner(Path.of("memo.txt"), "UTF-8") と書きます。ファイルは使い終わったら閉じたいので、try (Scanner f = new Scanner(Path.of("memo.txt"), "UTF-8")) { ... } の形にします(hasNextLine() と nextLine() で2行のファイルを読めることを確認しました)。

数値の読み取りはロケールの影響を受ける

nextInt() や nextDouble() は、桁区切りや小数点の記号をロケール(地域設定)に合わせて解釈します。 検証環境(ロケール en_US)で確認した結果です。

入力 呼び出し 結果
1,234 nextInt() 1234(カンマを桁区切りとして読む)
123(全角数字) nextInt() 123
2147483648 hasNextInt() / hasNextLong() false / true
3.14 useLocale(Locale.GERMANY) のあと hasNextDouble() false
3,14 useLocale(Locale.GERMANY) のあと nextDouble() 3.14

実行する環境によって小数の読み取り結果が変わるのを避けたいときは、sc.useLocale(Locale.ROOT) のようにロケールを固定するか、行で読んで Double.parseDouble で変換します。

Scannerを閉じるとSystem.inも閉じる

Scanner を close() すると、つないでいる System.in も一緒に閉じられ、そのプログラムの中では二度と標準入力を読めなくなります。 ファイルと同じ感覚で、メソッドごとに try-with-resources で囲むと起きます。

import java.util.Scanner;

public class Close {
    static int readOne() {
        try (Scanner sc = new Scanner(System.in)) {   // メソッドを抜けると System.in も閉じる
            return sc.nextInt();
        }
    }
    public static void main(String[] args) {
        System.out.println("1回目: " + readOne());
        System.out.println("2回目: " + readOne());
    }
}

実行結果(OpenJDK 21.0.10。入力は 1 と 2 の2行):

1回目: 1
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 Close.readOne(Close.java:6)
    at Close.main(Close.java:11)

入力には 2 が残っているのに、2回目の readOne() は NoSuchElementException になりました。閉じたあとの状態も確認しました。

Scanner sc = new Scanner(System.in);
sc.close();
try { sc.nextInt(); } catch (IllegalStateException e) { System.out.println("閉じたScanner: " + e); }
try { System.out.println("System.in.read() = " + System.in.read()); }
catch (java.io.IOException e) { System.out.println("System.in: " + e); }
閉じたScanner: java.lang.IllegalStateException: Scanner closed
System.in: java.io.IOException: Stream closed

公式ドキュメントにも、Scannerを閉じると、入力元が Closeable を実装している場合はその入力元も閉じる、と書かれています。

直し方:Scannerは1つ作って使い回す

import java.util.Scanner;

public class CloseOk {
    private static final Scanner IN = new Scanner(System.in);   // 1つを使い回す

    static int readOne() { return IN.nextInt(); }

    public static void main(String[] args) {
        System.out.println("1回目: " + readOne());
        System.out.println("2回目: " + readOne());
    }
}
1回目: 1
2回目: 2

閉じなくても、System.in につないだScannerを複数作ること自体が入力の取りこぼしにつながります。 Scannerは入力を先読みして内部にためるためです。

Scanner a = new Scanner(System.in);
System.out.println("a: " + a.nextInt());
Scanner b = new Scanner(System.in);     // 2つ目を作る
System.out.println("b.hasNextInt(): " + b.hasNextInt());
a: 1
b.hasNextInt(): false

入力は 1 と 2 の2行ですが、2 は1つ目のScannerがすでに先読みしているので、2つ目からは見えません。

入力元 閉じ方
System.in プログラム全体で1つだけ作り、閉じずに使い回す(閉じるならプログラムの最後に1回だけ)
ファイル(Path / File) try-with-resources で閉じる
文字列 閉じなくてよい

IDEが「リソースが閉じられていません」という警告を出すことがありますが、System.in はJVMが用意した共有の入力なので、上の表の扱いで問題ありません。

ScannerとBufferedReaderの速度を比べる

大量の入力を読むときは、BufferedReader のほうが速く読めます。 1行に1つ整数が入ったファイルを用意し、全部を足し合わせる時間を測りました(事前に javac でコンパイルし、java -cp cls クラス名 < nums.txt で各2回実行)。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class ReadBuffered {
    public static void main(String[] args) throws IOException {
        long t0 = System.nanoTime();
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine().trim());
        long sum = 0;
        for (int i = 0; i < n; i++) sum += Integer.parseInt(br.readLine().trim());
        System.out.printf("BufferedReader: sum=%d %d ms%n", sum, (System.nanoTime() - t0) / 1_000_000);
    }
}

Scanner側は、同じ処理を sc.nextInt() で読む版と、Integer.parseInt(sc.nextLine()) で読む版の2つを用意しました。

実行結果(OpenJDK 21.0.10、2コアの検証用コンテナ。JVMの起動後、main の先頭からの経過時間):

読み方 10万行(1回目 / 2回目) 100万行(1回目 / 2回目)
Scanner の nextInt() 567 ms / 527 ms 1433 ms / 1298 ms
Scanner の nextLine() + parseInt 499 ms / 380 ms 855 ms / 833 ms
BufferedReader の readLine() + parseInt 102 ms / 134 ms 180 ms / 181 ms

合計値は3つとも一致しました(100万行で 500161506242)。数値は環境で変わりますが、100万行では BufferedReader が Scanner.nextInt() のおよそ7分の1の時間で読み終えています。Scannerは正規表現でトークンを切り出し、ロケールに応じた数値の解釈もしているため、その分の時間がかかります。

BufferedReaderで空白区切りを読む書き方

BufferedReader は行を読むだけなので、空白区切りの値は自分で分けます。

import java.io.*;
import java.util.*;

public class BrSplit {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine().trim());
        StringTokenizer st = new StringTokenizer(br.readLine());
        int[] a = new int[n];
        for (int i = 0; i < n; i++) a[i] = Integer.parseInt(st.nextToken());
        System.out.println(Arrays.toString(a));
        String line;
        while ((line = br.readLine()) != null) {      // 入力の終わりは null
            System.out.println("行: " + line);
        }
    }
}

実行結果(OpenJDK 21.0.10。入力は 3、10 20 30、あ、い の4行):

[10, 20, 30]
行: あ
行: い
比べる点 Scanner BufferedReader
数値を直接読む nextInt() などがある ない(Integer.parseInt で変換)
空白区切り 自動で分ける split や StringTokenizer で分ける
入力の終わり hasNext() が false readLine() が null
例外 非チェック例外(InputMismatchException など) IOException(throws か try-catch が必要)
速さ 手軽さを優先 速い
向いている場面 学習、対話型の入力、数千行までのデータ 数十万行以上、処理時間に制限がある課題

まずはScannerで書き、入力が数十万行を超えて時間が気になったら BufferedReader に切り替える、という順で選ぶと無理がありません。

Java 25での差分(公式ドキュメントで確認)

この記事のコードはすべてJava 21.0.10で実行しました。Java 25については手元で動かしておらず、Java SE 25の公式APIドキュメントで次の点を確認しています。

  • Scanner クラスには、Java 22〜25で追加されたコンストラクタ・メソッドはありません。 この記事の内容はJava 25でもそのまま当てはまります。
  • Java 25では java.lang.IO クラスが追加され、IO.readln()・IO.readln(String prompt)・IO.println(Object)・IO.print(Object) が使えます。 readln は1行を読んで改行を除いた文字列を返し、入力の終わりでは null を返します。文字コードは stdin.encoding プロパティに従います。
  • IO.readln は行単位の読み取りだけなので、空白区切りの数値を読む、hasNextInt で確かめる、といった用途では引き続きScannerが使えます。
// Java 25以降(公式ドキュメントの記載に基づく書き方。この記事では未実行)
String name = IO.readln("名前: ");
int age = Integer.parseInt(IO.readln("年齢: ").trim());
IO.println(name + "さんは" + age + "歳です");

よくある質問

Q. nextLine() と next() はどう使い分けますか? A. 空白を含む文字列(氏名、文章、タイトル)を読むなら nextLine()、空白で区切られた単語を1つずつ読むなら next() です。next() は前にある空白や改行を飛ばしてくれますが、空白の手前で止まります。

Q. nextInt() のあとに nextLine() を2回書くのは正しい書き方ですか? A. はい、1回目で残った改行を読み捨て、2回目で本来の行を読む、という意味のある書き方です。コメントで // 改行を読み捨てる と添えておくと意図が伝わります。数値も Integer.parseInt(sc.nextLine().trim()) で読む形にそろえると、読み捨て自体が要らなくなります。

Q. 入力の終わり(EOF)はどうやって判定しますか? A. while (sc.hasNext()) や while (sc.hasNextLine()) でループします。ファイルやパイプなら入力が尽きた時点で false になります。キーボード入力では、macOS・LinuxはCtrl+D、WindowsはCtrl+Zのあと Enter で入力の終わりを伝えられます。確かめずに読むと、この記事で再現した NoSuchElementException(nextLine() の場合は No line found)になります。

Q. Scannerは複数のスレッドから使えますか? A. 公式ドキュメントに「外部で同期しないかぎり、マルチスレッドでの使用は安全ではない」と書かれています。入力を読むスレッドを1つに決めて、読んだ値をほかのスレッドへ渡す形にするとよいでしょう。

まとめ

  • nextLine() は行単位、next() と nextInt() などはトークン単位で読む。 nextLine() だけが改行を消費する。
  • nextInt() のあとの nextLine() が空文字になるのは、数字のあとの改行が残っているため。 nextLine() を1回はさむ、全部 nextLine() で読んで parseInt する、next() にそろえる、の3通りで直せる。
  • 整数でない入力は InputMismatchException。 読めなかったトークンは残るので、catch の中で sc.next() か sc.nextLine() で捨てる。
  • hasNextInt() で先に確かめると、例外なしで振り分けられる。 対話型は「行で読んで parseInt、合わなければ聞き直す」が安定する。
  • 空白区切り・複数行のデータは nextInt() を並べるだけで読める。 行読みに切り替える箇所だけ読み捨てが必要。
  • System.in につないだScannerは1つだけ作って使い回す。 close() や try-with-resources で閉じると System.in も閉じ、以後は NoSuchElementException になる。
  • 大量の入力は BufferedReader が速い。 100万行の計測で Scanner.nextInt() のおよそ7分の1の時間だった。Java 25でもScannerのAPIは同じで、行読み用に IO.readln が加わっている。

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

(記載例)出典:株式会社RJC「Java Scannerの使い方|nextLineとnextIntの違いと改行残り」

株式会社RJCでは、Scannerによる標準入力の扱い方のようなJavaの基礎を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社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で一緒に開発しながら、 成長を楽しみませんか?