java.util.Scanner を使用して System.in からユーザー入力を正しく読み取り、それに応じて操作するにはどうすればよいでしょうか? 質問する

java.util.Scanner を使用して System.in からユーザー入力を正しく読み取り、それに応じて操作するにはどうすればよいでしょうか? 質問する

これは標準的な質問/回答重複ターゲットとして使用できます。これらの要件は、毎日投稿される最も一般的な質問に基づいており、必要に応じて追加される可能性があります。すべてのシナリオに到達するには、同じ基本的なコード構造が必要であり、通常は相互に依存しています。


スキャナーは"単純"クラスを使用するのが最初の間違いです。これは単純ではなく、さまざまな副作用や異常な動作があり、驚き最小の原則非常に微妙な方法で。

これはこのクラスではやり過ぎのように思えるかもしれませんが、タマネギの皮をむく間違いや問題はすべて単純、しかし、それらを合わせると非常に複雑な相互作用と副作用のためです。これが、Stack Overflow に毎日これに関する質問が多数寄せられる理由です。

スキャナーに関するよくある質問:

ほとんどのScanner質問には、これらのうちの 1 つ以上の失敗した試みが含まれています。

  1. プログラムが、前の入力の後に次の入力を自動的に待機できるようにしたいと考えています。

  2. 検出方法を知りたい出口コマンドを実行し、そのコマンドが入力されるとプログラムを終了します。

  3. 複数のコマンドを一致させる方法を知りたい出口大文字と小文字を区別せずにコマンドを実行します。

  4. 組み込みプリミティブだけでなく、正規表現パターンも一致させられるようにしたいです。たとえば、日付 ( 2014/10/18) と思われるものをどのように一致させるのでしょうか?

  5. 正規表現マッチングでは簡単に実装できない可能性のあるもの、たとえば URL ( http://google.com) をマッチングさせる方法を知りたいです。

モチベーション:

Java の世界では、Scannerこれは特別なケースで、教師が新しい生徒に使用方法を指示すべきではない、非常に扱いにくいクラスです。ほとんどの場合、教師はそれを正しく使用する方法さえ知りません。プロの製品コードで使用されることはほとんどないため、学生にとっての価値は非常に疑問です。

使用は、Scannerこの質問と回答で言及されている他のすべてのことを意味します。それは決してただについてではなく、ほとんどすべての質問で常に併存する問題であるScannerこれらの一般的な問題を解決する方法についてです。それは決してただについてではありませんScannerScannernext()nextLine()、これはクラスの実装の細かさの単なる症状ですが、 について尋ねる質問を投稿すると、コードには常に他の問題が存在しますScanner

Scannerこの回答は、StackOverflow で使用され質問されるケースの 99% を完全に慣用的に実装したものです。

特に初心者のコードではそうです。この答えが複雑すぎると思うなら、その動作の複雑さ、癖、わかりにくい副作用や特殊性を説明する前に、新しい学生に使用するように指示するインストラクターに文句を言ってくださいScanner

Scannerこれは、驚き最小の原則メソッドとメソッド引数の命名において一貫した動作とセマンティクスが重要である理由について説明します。

学生への注意:

Scannerおそらく、プロフェッショナル/商用の業務アプリケーションで実際に使用されているのを見ることはないでしょう。なぜなら、それが行うことはすべて、他の方法でより良く行われるからScannerです。現実世界のソフトウェアは、コードを記述できるよりも、より回復力があり、保守性が高くなければなりません。現実世界のソフトウェアは、標準化されたファイル形式パーサーと文書化されたファイル形式を使用します。このためにスタンドアロンの課題で指定された入力形式。

ベストアンサー1

慣用的な例:

以下は、クラスを適切に使用して、 (特に C、C++ などの言語、および Unix や Linux では と呼ばれることもあります)java.util.Scannerからユーザー入力を対話的に正しく読み取る方法です。これは、実行が要求される最も一般的なことを慣用的に示しています。System.instdin

package com.stackoverflow.scanner;

import javax.annotation.Nonnull;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.regex.Pattern;

import static java.lang.String.format;

public class ScannerExample
{
    private static final Set<String> EXIT_COMMANDS;
    private static final Set<String> HELP_COMMANDS;
    private static final Pattern DATE_PATTERN;
    private static final String HELP_MESSAGE;

    static
    {
        final SortedSet<String> ecmds = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
        ecmds.addAll(Arrays.asList("exit", "done", "quit", "end", "fino"));
        EXIT_COMMANDS = Collections.unmodifiableSortedSet(ecmds);
        final SortedSet<String> hcmds = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
        hcmds.addAll(Arrays.asList("help", "helpi", "?"));
        HELP_COMMANDS = Collections.unmodifiableSet(hcmds);
        DATE_PATTERN = Pattern.compile("\\d{4}([-\\/])\\d{2}\\1\\d{2}"); // http://regex101.com/r/xB8dR3/1
        HELP_MESSAGE = format("Please enter some data or enter one of the following commands to exit %s", EXIT_COMMANDS);
    }

    /**
     * Using exceptions to control execution flow is always bad.
     * That is why this is encapsulated in a method, this is done this
     * way specifically so as not to introduce any external libraries
     * so that this is a completely self contained example.
     * @param s possible url
     * @return true if s represents a valid url, false otherwise
     */
    private static boolean isValidURL(@Nonnull final String s)
    {
        try { new URL(s); return true; }
        catch (final MalformedURLException e) { return false; }
    }

    private static void output(@Nonnull final String format, @Nonnull final Object... args)
    {
        System.out.println(format(format, args));
    }

    public static void main(final String[] args)
    {
        final Scanner sis = new Scanner(System.in);
        output(HELP_MESSAGE);
        while (sis.hasNext())
        {
            if (sis.hasNextInt())
            {
                final int next = sis.nextInt();
                output("You entered an Integer = %d", next);
            }
            else if (sis.hasNextLong())
            {
                final long next = sis.nextLong();
                output("You entered a Long = %d", next);
            }
            else if (sis.hasNextDouble())
            {
                final double next = sis.nextDouble();
                output("You entered a Double = %f", next);
            }
            else if (sis.hasNext("\\d+"))
            {
                final BigInteger next = sis.nextBigInteger();
                output("You entered a BigInteger = %s", next);
            }
            else if (sis.hasNextBoolean())
            {
                final boolean next = sis.nextBoolean();
                output("You entered a Boolean representation = %s", next);
            }
            else if (sis.hasNext(DATE_PATTERN))
            {
                final String next = sis.next(DATE_PATTERN);
                output("You entered a Date representation = %s", next);
            }
            else // unclassified
            {
                final String next = sis.next();
                if (isValidURL(next))
                {
                    output("You entered a valid URL = %s", next);
                }
                else
                {
                    if (EXIT_COMMANDS.contains(next))
                    {
                        output("Exit command %s issued, exiting!", next);
                        break;
                    }
                    else if (HELP_COMMANDS.contains(next)) { output(HELP_MESSAGE); }
                    else { output("You entered an unclassified String = %s", next); }
                }
            }
        }
        /*
           This will close the underlying InputStream, in this case System.in, and free those resources.
           WARNING: You will not be able to read from System.in anymore after you call .close().
           If you wanted to use System.in for something else, then don't close the Scanner.
        */
        sis.close();
        System.exit(0);
    }
}

ノート:

これは大量のコードに見えるかもしれませんが、Scannerクラスを正しく使用するために必要な最小限の労力を示しており、プログラミング初心者や、このひどく実装された というクラスを悩ませる微妙なバグや副作用に対処する必要がありませんjava.util.Scanner。これは、慣用的な Java コードがどのように見えるか、どのように動作するかを説明しようとしています。

以下は、この例を書いたときに私が考えていたいくつかのことです。

JDK バージョン:

この例は意図的に JDK 6 と互換性を持たせました。シナリオによっては JDK 7/8 の機能が本当に必要な場合、私または他の誰かが、そのバージョンの JDK 用にこれを変更する方法の詳細を記載した新しい回答を投稿します。

このクラスに関する質問の大部分は学生から寄せられ、通常、問題を解決するために使用できるものには制限があるため、他の依存関係なしで一般的なことを実行する方法を示すために、これを可能な限り制限しました。私は 22 年以上にわたり Java に携わり、そのほとんどの時間をコンサルティングに費やしてきましたが、これまでに見た数千万行のソース コードの中で、このクラスが専門的に使用されているのに出会ったことはありません。

処理コマンド:

これはまさに慣用的にユーザーからのコマンドを対話的に読み取り、それらのコマンドをディスパッチします。ほとんどの質問java.util.Scanner特定の入力をしたときにプログラムを終了させるにはどうすればよいですかカテゴリー。これはそれをはっきりと示しています。

ナイーブディスパッチャ

ディスパッチ ロジックは、新しい読者にとってソリューションが複雑にならないように、意図的に単純化されています。Strategy PatternまたはChain Of Responsibilityパターンに基づくディスパッチャは、はるかに複雑な現実世界の問題に適しています。

エラー処理

Exception一部のデータが正しくない可能性があるシナリオは存在しないため、コードは意図的に処理を必要としない構造になっています。

.hasNext()そして.hasNextXxx()

を適切に使用している人をほとんど見かけませんが.hasNext()、ジェネリックをテストして.hasNext()イベント ループを制御し、if(.hasNextXxx())イディオムを使用すると、 が利用できない場合に を要求する心配をせずに、コードをどのように、何を進めるかを決定できるためint、例外処理コードが不要になります。

.nextXXX().nextLine()

これは、誰もが知っているコードを破壊するものです。細かい点対処する必要のない、非常に難解なバグがあり、それが破壊されるため、推論するのが難しい。最も驚かなかった校長

メソッド.nextXXX()は行末を消費しません。.nextLine()消費します。

つまり、.nextLine()直後に呼び出す.nextXXX()と、行末が返されるだけです。次の行を実際に取得するには、もう一度呼び出す必要があります。

このため、多くの人が、この厄介な動作に悩まされないように.nextXXX()、メソッドのみを使用するか、メソッドのみを.nextLine()使用して両方を同時に使用しないことを推奨しています。個人的には、手動でエラーをテストして解析し、キャッチするよりも、型安全なメソッドの方がはるかに優れていると思います。

不変性:

コードでは可変変数が使用されていないことに注意してください。これを実行する方法を学ぶことは重要であり、これにより、実行時エラーと微妙なバグの最も大きな原因の 4 つが排除されます。

  1. いいえnullsは可能性がないことを意味しますNullPointerExceptions

  2. 可変性がないということは、メソッドの引数の変更やその他の変更について心配する必要がないことを意味します。ステップ デバッグを実行するときに、watchどの変数がどの値に変更されるか、変更されているかどうかを確認するために を使用する必要はありません。これにより、ロジックは読み取り時に 100% 決定論的になります。

  3. 可変性がない場合は、コードが自動的にスレッドセーフになります。

  4. 副作用はありません。何も変更できない場合は、何らかのエッジケースによって予期せず何かが変更されるという微妙な副作用を心配する必要はありません。

final独自のコードでキーワードを適用する方法がわからない場合は、これをお読みください。

switch大規模なブロックの代わりにセットを使用するif/elseif:

コードを肥大化させ、さらに重要なことにメンテナンスを悪夢にする巨大なやの代わりに、 と を使用しSet<String>.contains()コマンドを分類していることに注目してください。オーバーロードされた新しいコマンドを追加するのは、コンストラクターの配列にnew を追加するのと同じくらい簡単です。switchif/elseifString

i18nこれは、および とi10n適切な との組み合わせでも非常にうまく機能しますResourceBundlesMap<Locale,Set<String>>を使用すると、オーバーヘッドをほとんどかけずに複数の言語をサポートできます。

@ヌルではない

私はすべてのコードを明示的に何かが@Nonnullまたはであるかどうかを宣言します@Nullable。これにより、IDE は潜在的なNullPointerException危険性について警告し、チェックする必要がない場合に警告を表示できるようになります。

最も重要なことは、将来の読者に対して、これらのメソッド パラメータはいずれも であってはならないという期待を文書化していることですnull

.close() を呼び出す

これを実行する前に、よく考えてください。

System.inに電話すると何が起こると思いますかsis.close()? 上記のリストのコメントを参照してください。

お願いしますフォークしてプルリクエストを送信する他の基本的な使用シナリオについてもこの質問と回答を更新します。

おすすめ記事