1. スキャナーの概要

このクイックチュートリアルでは、 Java Scanner クラスを使用して、さまざまな区切り文字で入力を読み取り、パターンを検索してスキップする方法を説明します。

2. ファイルをスキャンする

まず、スキャナーを使用してファイルを読み取る方法を見てみましょう。

次の例では、「Helloworld」を含むファイルをトークンに読み込みます。

@Test
public void whenReadFileWithScanner_thenCorrect() throws IOException{
    Scanner scanner = new Scanner(new File("test.txt"));

    assertTrue(scanner.hasNext());
    assertEquals("Hello", scanner.next());
    assertEquals("world", scanner.next());

    scanner.close();
}

next()メソッドは、ここで次のStringトークンを返すことに注意してください。

また、使い終わったらスキャナーを閉じる方法にも注意してください。

3. InputStreamStringに変換します

次へ– スキャナーを使用して、InputStreamStringに変換する方法を見てみましょう。

@Test
public void whenConvertInputStreamToString_thenConverted()
  throws IOException {
    String expectedValue = "Hello world";
    FileInputStream inputStream 
      = new FileInputStream("test.txt");
    
    Scanner scanner = new Scanner(inputStream);
    scanner.useDelimiter("A");

    String result = scanner.next();
    assertEquals(expectedValue, result);

    scanner.close();
}

前の例と同様に、 Scanner を使用して、最初から次の正規表現「A」までのストリーム全体をトークン化しました。これは、完全な入力と一致します。

4. スキャナー対。 BufferedReader

次に、ScannerBufferedReaderの違いについて説明します–通常は次のものを使用します。

  • BufferedReader入力を行に読み込みたい場合
  • スキャナーは、入力をトークンに読み込みます

次の例では、BufferedReaderを使用してファイルを行に読み込んでいます。

@Test
public void whenReadUsingBufferedReader_thenCorrect() 
  throws IOException {
    String firstLine = "Hello world";
    String secondLine = "Hi, John";
    BufferedReader reader 
      = new BufferedReader(new FileReader("test.txt"));

    String result = reader.readLine();
    assertEquals(firstLine, result);

    result = reader.readLine();
    assertEquals(secondLine, result);

    reader.close();
}

次に、 Scanner を使用して、同じファイルをトークンに読み込みます。

@Test
public void whenReadUsingScanner_thenCorrect() 
  throws IOException {
    String firstLine = "Hello world";
    FileInputStream inputStream 
      = new FileInputStream("test.txt");
    Scanner scanner = new Scanner(inputStream);

    String result = scanner.nextLine();
    assertEquals(firstLine, result);

    scanner.useDelimiter(", ");
    assertEquals("Hi", scanner.next());
    assertEquals("John", scanner.next());

    scanner.close();
}

スキャナーnextLine() API –を使用して行全体を読み取る方法に注意してください

5. New Scanner(System.in)を使用してコンソールから入力をスキャンします

次へ– Scannerインスタンスを使用してコンソールから入力を読み取る方法を見てみましょう。

@Test
public void whenReadingInputFromConsole_thenCorrect() {
    String input = "Hello";
    InputStream stdin = System.in;
    System.setIn(new ByteArrayInputStream(input.getBytes()));

    Scanner scanner = new Scanner(System.in);

    String result = scanner.next();
    assertEquals(input, result);

    System.setIn(stdin);
    scanner.close();
}

System.setIn(…)を使用して、コンソールからの入力をシミュレートしたことに注意してください。

5.1.  nextLine() API

このメソッドは、現在の行の文字列を返すだけです。

scanner.nextLine();

これにより、現在の行の内容が読み取られ、最後の行区切り文字(この場合は改行文字)を除いて返されます。

コンテンツを読み取った後、スキャナーはその位置を次の行の先頭に設定します。 覚えておくべき重要な点は、 nextLine()APIが行区切り文字を使用し、スキャナーの位置を次の行に移動することです。

したがって、次回スキャナーを読み取ると、次の行の先頭から読み取ることになります。

5.2.  nextInt() API

このメソッドは、入力の次のトークンを int:としてスキャンします

scanner.nextInt();

APIは、次に使用可能な整数トークンを読み取ります。

この場合、次のトークンが整数で、整数の後に行区切り文字がある場合は、常に次のことを覚えておいてください。 nextInt()は行区切り文字を消費しません。 代わりに、スキャナーの位置はラインセパレーター自体になります

したがって、一連の操作がある場合、最初の操作は Scanner.nextInt()、次に scanner.nextLine()であり、整数を指定してを押すと入力として改行すると、両方の操作が実行されます。

nextInt() APIは整数を消費し、 nextLine() APIは行区切り文字を消費し、Scannerを次の行の先頭に配置します。

6. 入力を検証する

次に、スキャナーを使用して入力を検証する方法を見てみましょう。 次の例では、 ScannerメソッドhasNextInt()を使用して、入力が整数値であるかどうかを確認します。

@Test
public void whenValidateInputUsingScanner_thenValidated() 
  throws IOException {
    String input = "2000";
    InputStream stdin = System.in;
    System.setIn(new ByteArrayInputStream(input.getBytes()));

    Scanner scanner = new Scanner(System.in);

    boolean isIntInput = scanner.hasNextInt();
    assertTrue(isIntInput);

    System.setIn(stdin);
    scanner.close();
}

7. 文字列をスキャンします

次へ– Scannerを使用してStringをスキャンする方法を見てみましょう。

@Test
public void whenScanString_thenCorrect() 
  throws IOException {
    String input = "Hello 1 F 3.5";
    Scanner scanner = new Scanner(input);

    assertEquals("Hello", scanner.next());
    assertEquals(1, scanner.nextInt());
    assertEquals(15, scanner.nextInt(16));
    assertEquals(3.5, scanner.nextDouble(), 0.00000001);

    scanner.close();
}

注:メソッド nextInt(16)は、次のトークンを16進整数値として読み取ります。

8. パターンを検索

それでは、スキャナーを使用してパターンを見つける方法を見てみましょう。

次の例では、 findInLine()を使用して、入力全体で指定されたパターンに一致するトークンを検索します。

@Test
public void whenFindPatternUsingScanner_thenFound() throws IOException {
    String expectedValue = "world";
    FileInputStream inputStream = new FileInputStream("test.txt");
    Scanner scanner = new Scanner(inputStream);

    String result = scanner.findInLine("wo..d");
    assertEquals(expectedValue, result);

    scanner.close();
}

次の例のように、 findWithinHorizon()を使用して、特定のドメインでPatternを検索することもできます。

@Test
public void whenFindPatternInHorizon_thenFound() 
  throws IOException {
    String expectedValue = "world";
    FileInputStream inputStream = new FileInputStream("test.txt");
    Scanner scanner = new Scanner(inputStream);

    String result = scanner.findWithinHorizon("wo..d", 5);
    assertNull(result);

    result = scanner.findWithinHorizon("wo..d", 100);
    assertEquals(expectedValue, result);

    scanner.close();
}

検索範囲は、単に検索が実行される文字数であることに注意してください。

9. パターンをスキップ

次へ–スキャナーパターンをスキップする方法を見てみましょう。 Scanner を使用して入力を読み取るときに、特定のパターンに一致するトークンをスキップできます。

次の例では、 Scannerメソッドskip()を使用して「Hello」トークンをスキップします。

@Test
public void whenSkipPatternUsingScanner_thenSkipped() 
  throws IOException {
    FileInputStream inputStream = new FileInputStream("test.txt");
    Scanner scanner = new Scanner(inputStream);

    scanner.skip(".e.lo");

    assertEquals("world", scanner.next());

    scanner.close();
}

10. スキャナー区切り文字を変更します

最後に– Scanner区切り文字を変更する方法を見てみましょう。 次の例では、デフォルトのScanner区切り文字を「o」に変更します。

@Test
public void whenChangeScannerDelimiter_thenChanged() 
  throws IOException {
    String expectedValue = "Hello world";
    String[] splited = expectedValue.split("o");

    FileInputStream inputStream = new FileInputStream("test.txt");
    Scanner scanner = new Scanner(inputStream);
    scanner.useDelimiter("o");

    assertEquals(splited[0], scanner.next());
    assertEquals(splited[1], scanner.next());
    assertEquals(splited[2], scanner.next());

    scanner.close();
}

複数の区切り文字を使用することもできます。 次の例では、「 John、Adam-Tom 」を含むファイルをスキャンするための区切り文字として、コンマ「」とダッシュ「」の両方を使用しています。

@Test
public void whenReadWithScannerTwoDelimiters_thenCorrect() 
  throws IOException {
    Scanner scanner = new Scanner(new File("test.txt"));
    scanner.useDelimiter(",|-");

    assertEquals("John", scanner.next());
    assertEquals("Adam", scanner.next());
    assertEquals("Tom", scanner.next());

    scanner.close();
}

注:デフォルトのスキャナー区切り文字は空白です。

11. Handling NoSuchElementException

Before diving deep into the details, let’s try to understand what the exception really means.

According to the documentation, NoSuchElementException typically occurs when the requested element does not exist.

Basically, Scanner throws this exception when it fails to read data or fetch the next element.

11.1. Reproducing the Exception

Now that we know what causes Scanner to throw NoSuchElementException, let’s see how to reproduce it using a practical example.

To demonstrate a real-world use case, we’re going to read data from a FileInputStream twice using two different instances of the Scanner class:

public void givenClosingScanner_whenReading_thenThrowException() throws IOException {
    final FileInputStream inputStream = new FileInputStream("src/test/resources/test_read.in");

    final Scanner scanner = new Scanner(inputStream);
    scanner.next();
    scanner.close();

    final Scanner scanner2 = new Scanner(inputStream);
    scanner2.next();
    scanner2.close();
}

Next, let’s run our test case and see what happens. Indeed, looking at the logs, the test case fails with NoSuchElementException:

java.util.NoSuchElementException
    at java.util.Scanner.throwFor(Scanner.java:862)
    at java.util.Scanner.next(Scanner.java:1371)
    at com.baeldung.scanner.JavaScannerUnitTest.givenClosingScanner_whenReading_thenThrowException(JavaScannerUnitTest.java:195)
...

11.2. Investigating the Cause

Simply put, when a Scanner is closed, it will close its source, too, if the source implements the Closeable interface.

Basically, when we invoked scanner.close() in our test case, we actually closed the inputStream, too. So, the second Scanner failed to read the data because the source is not open.

11.3. Fixing the Exception

There’s only one way to fix the exception – to avoid using multiple Scanners to manipulate the same data source.

So, a good solution would be using a single Scanner instance to read data and close it at the very end.

12. 結論

このチュートリアルでは、Javaスキャナーの実際の使用例をいくつか紹介しました。

We learned how to read input from a file, console, or String using Scanner. We also learned how to find and skip a pattern using Scanner — as well as how to change the Scanner delimiter.

Finally, we explained how to handle NoSuchElementException exception.

これらの例の実装は、GitHubにあります。