java.util.Scannerを使用してSystem.inからユーザ入力を正しく読み取り、それに対応する方法

java java.util.scanner system.in


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


スキャナーは使用する「単純な」クラスのように見え、そこで最初の間違いが行われます。それは単純ではありません、それは非常に微妙な方法で最小驚き原則を破るあらゆる種類の非自明な副作用と異常な行動を持っています。

したがって、これはこのクラスにはやり過ぎに見えるかもしれませんが、玉ねぎの皮むきのエラーと問題はすべて単純ですが、まとめると、相互作用と副作用のために非常に複雑になります。これが、Stack Overflowで毎日非常に多くの質問がある理由です。

スキャナーの一般的な質問

ほとんどの Scanner 質問には、これらの複数の試行が失敗したことが含まれます。

  1. 前の入力の後にも次の入力を自動的に待つようにしたいです。

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

  3. 大文字と小文字を区別しない方法でexitコマンドの複数のコマンドを照合する方法を知りたいです。

  4. 組み込みのプリミティブだけでなく、正規表現のパターンにも一致できるようにしたいと考えています。たとえば、日付( 2014/10/18 )のように見えるものを照合するにはどうすればよいですか?

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

Motivation:

Javaの世界では、 Scanner は特殊なケースであり、教師が新入生に使用方法を教えてはならない、非常に骨の折れるクラスです。ほとんどの場合、講師はそれを正しく使用する方法すら知りません。プロのプロダクションコードで使用することはほとんどないため、学生にとってのその価値は非常に疑わしいものです。

Scanner 使用は、この質問と回答が言及する他のすべてのものを意味します。それはちょうど約決して Scanner それはこれらの一般的な問題解決の方法についてである Scanner 常に得るほぼすべての問題の共同病的な問題となっている Scanner が間違っています。 next()nextLine() だけではありません。これは、クラスの実装の細かさの兆候にすぎません。 Scanner に関する質問のコード投稿には、常に他の問題があります。

答えは、StackOverflow で Scanner が使用され、質問された場合の99%のケースの完全で慣用的な実装を示しています。

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

Scanner は、最小の驚きの原則がどれほど重要であるか、一貫した動作とセマンティクスがメソッドとメソッド引数の命名において重要である理由についての素晴らしい教育の瞬間です。

学生への注意事項。

Scanner が専門的/商用の基幹業務アプリで使用されているのを見ることはないでしょう。実際のソフトウェアは、 Scanner コードを記述できるよりも、回復力と保守性が高くなければなりません。実際のソフトウェアでは、スタンドアロンの割り当てで指定されたアドホック入力形式ではなく、標準化されたファイル形式パーサーと文書化されたファイル形式を使用します。




Answer 1 27 revsuser177800


慣用句の例。

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

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);
    }
}

Notes:

これは多くのコードのように見えるかもしれませんが、 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() をテストしてイベントループを制御し、 if(.hasNextXxx()) イディオムを使用することで、 .hasNext() を適切に使用している人がほとんどいないので、コードの処理方法と処理を決定できます何も利用できない場合に int を要求することを心配する必要がないため、例外処理コードがありません。

.nextXXX().nextLine()

これはみんなのコードを壊すものです。これは対処する必要のない細かいディテールであり、非常に難読化されたバグであり、最小驚きのプリンシパルを破壊するため、推論するのが困難です

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

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

このため、多くの人が .nextXXX() メソッドのみを使用するか、.nextLine()のみを .nextLine() し、両方を同時に使用しないことを支持しているため、この厄介な動作が邪魔になりません。個人的には、タイプセーフな方法の方が、手動でテストして解析してエラーをキャッチするよりもはるかに優れていると思います。

Immutablity:

これは、実行時エラーや微妙なバグの4つの主要な原因を排除します。

  1. いいえ nulls の可能性を意味しない NullPointerExceptions がします!

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

  3. 変異性がないということは、あなたのコードが自動的にスレッドセーフであることを意味します。

  4. 副作用はありません。何も変えることができないのであれば、どこかのエッジケースの微妙な副作用で何かが不意に変わってしまうことを心配する必要はありません!

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

大規模な switch または if/elseif ブロックの代わりにSetを使用する:

Set<String>.contains() を使用してコマンドを分類する方法に注目してください。大規模な switch や、コードを肥大化させ、さらに重要なことにメンテナンスを悪夢のようにする if/elseif の怪物です。新しいオーバーロードされたコマンドの追加は、コンストラクターの配列に新しい String を追加するのと同じくらい簡単です。

これは、 i18n および i10n と適切な ResourceBundles でも非常にうまく機能します。A Map<Locale,Set<String>> あなたが非常に少ないオーバーヘッドで複数の言語をサポートしてみようと思います!

@Nonnull

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

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

.close()の呼び出し

本当によく考えてからやれよ。

sis.close() を呼び出した場合、 System.in はどうなると思いますか?上記のリストのコメントを参照してください。

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