java.util.Scanner를 사용하여 System.in에서 사용자 입력을 올바르게 읽고 조치하는 방법

java java.util.scanner system.in


이는 중복 대상으로 사용할 수 있는 정식 질문 / 답변 입니다. 이러한 요구 사항은 매일 게시되는 가장 일반적인 질문을 기반으로하며 필요에 따라 추가 될 수 있습니다. 각 시나리오에 도달하려면 동일한 기본 코드 구조가 필요하며 일반적으로 서로 의존합니다.


스캐너는 사용하기에 "간단한" 클래스 처럼 보이며 , 이것이 첫 번째 실수입니다. 그것은 휴식이 아닌 명백한 부작용과 비정상적인 행동의 모든 종류가, 간단하지 않다 최소 깜짝 원칙을 매우 미묘한 방식으로합니다.

따라서 이것은이 클래스에서 과도하게 보일 수 있지만 양파 오류와 문제를 벗기는 것은 간단 하지만 상호 작용과 부작용으로 인해 매우 복잡 합니다. 이것이 매일 Stack Overflow에 대해 많은 질문이있는 이유입니다.

일반적인 스캐너 질문 :

대부분의 Scanner 질문에는 이러한 문제 중 하나 이상에 대한 실패한 시도가 포함됩니다.

  1. 프로그램이 이전의 각 입력 후 다음 입력을 자동으로 기다리도록하고 싶습니다.

  2. 종료 명령 을 감지하고 해당 명령을 입력하면 프로그램을 종료 하는 방법을 알고 싶습니다 .

  3. 종료 명령에 대해 여러 명령을 대소 문자를 구분하지 않는 방법 으로 알고 싶습니다 .

  4. 내장 프리미티브뿐만 아니라 정규 표현식 패턴을 일치시킬 수 있기를 원합니다. 예를 들어, 날짜 ( 2014/10/18 ) 와 일치하는 방법은 무엇 입니까?

  5. URL ( http://google.com ) 과 같이 정규 표현식 일치로 쉽게 구현할 수없는 항목을 일치시키는 방법을 알고 싶습니다 .

Motivation:

자바 세계에서 Scanner 는 특별한 경우이며, 교사가 새로운 학생들에게 사용 지침을 제공해서는 안되는 매우 까다로운 수업입니다. 대부분의 경우 강사는 올바르게 사용하는 방법조차 모릅니다. 전문 생산 코드에서 사용되는 경우는 거의 없으므로 학생들에게 가치가 매우 의심 스럽습니다.

Scanner 사용하면 이 질문과 답변에 언급 된 다른 모든 내용이 포함됩니다. 그것은 단지 Scanner 에 관한 것이 아닙니다 . Scanner 잘못된 문제를 일으키는 거의 모든 질문에서 항상 병적 인 문제인 Scanner 일반적인 문제를 해결하는 방법에 관한 것입니다. 클래스 구현의 신념의 증상 일뿐 인 next() nextLine() 에 대해서는 절대로 절대 코드에 Scanner 에 관한 질문에 다른 문제가 있습니다.

이 답변은 Scanner 가 사용되고 StackOverflow에 대해 질문 된 경우의 99 %를 완전하고 관용적으로 구현 한 것을 보여줍니다 .

특히 초보자 코드에서. 이 답변이 너무 복잡하다고 생각되면 신입생에게 Scanner 를 사용하도록 지시하여 강사에게 복잡한 점, 특이한 점, 명백하지 않은 부작용 및 동작의 특성을 설명하기 전에 불만을 제기하십시오 .

Scanner가장 놀라운 원리가 얼마나 중요한지, 방법과 방법론의 이름을 짓는데 일관성있는 행동과 의미가 중요한 이유 에 대한 훌륭한 교육 순간 입니다.

학생들을위한 참고 사항 :

전문 / 상업용 비즈니스 응용 프로그램에서 Scanner 실제로 사용되는 것을 볼 수는 없습니다. 실제 소프트웨어는 Scanner 코드를 작성 하는 것보다 복원력이 뛰어나고 유지 관리가 쉬워야 합니다. 실제 소프트웨어는 독립형 할당으로 제공되는 임시 입력 형식이 아니라 표준화 된 파일 형식 파서 및 문서화 된 파일 형식을 사용 합니다.




Answer 1 27 revsuser177800


관용적 예 :

다음은 java.util.Scanner 클래스 를 올바르게 사용하여 System.in 에서 사용자 입력을 올바르게 대화식으로 읽는 방법입니다 ( 특히 stdin , 특히 C, C ++ 및 기타 언어, Unix 및 Linux). 그것은 요청 된 가장 일반적인 것들을 관용적으로 보여줍니다.

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로 작업하고 그 시간의 대부분을 컨설팅 해 왔으며, 내가 본 1000 만 줄의 소스 코드 에서이 클래스를 전문적으로 사용하지 못했습니다.

처리 명령 :

이것은 사용자로부터 대화 형으로 명령 을 관용적으로 읽고 그 명령을 전달하는 방법을 정확하게 보여줍니다 . java.util.Scanner 에 대한 대부분의 질문은 특정 입력 카테고리를 입력 할 때 프로그램을 종료 하는 방법 에 관한 것 입니다. 이것은 명확하게 보여줍니다.

순진한 발송자

디스패치 로직은 새로운 독자를위한 솔루션을 복잡하게 만들지 않기 위해 의도적으로 순진합니다. Strategy Pattern 또는 Chain Of Responsibility 패턴을 기반으로하는 디스패처 는 훨씬 복잡한 실제 문제에 더 적합합니다.

오류 처리

코드는 일부 데이터가 올바르지 않은 시나리오가 없기 때문에 Exception 처리 가 필요하지 않도록 의도적으로 구조화되었습니다 .

.hasNext().hasNextXxx()

이벤트 루프를 제어 하기 위해 일반 .hasNext() 를 테스트 한 다음 if(.hasNextXxx()) 관용구를 사용하여 .hasNext() 올바르게 사용하는 사람은 거의 없습니다. 사용할 수없는 경우 int 를 요구하지 않아도 되므로 예외 처리 코드가 없습니다.

.nextXXX().nextLine()

이것은 모든 사람의 코드를 어기는 것입니다. 그것은이다 까다로운 세부 처리 할 필요가 없습니다과 나누기 그 때문에에 대한 이유에 어려운 매우 obfusated 버그가 최소 깜짝 교장을

.nextXXX() 메서드는 선 결말을 소모하지 않습니다. .nextLine() 은 않습니다.

즉, .nextLine() 바로 다음에 .nextXXX() 을 호출 하면 줄 끝이 반환됩니다. 실제로 다음 줄을 얻으려면 다시 호출해야합니다.

이것이 많은 사람들이 .nextXXX() 메소드만을 사용하거나 .nextLine() 만을 사용 하지만 동시에 두 가지 모두를 사용하지 않는 이유는이 까다로운 행동이 당신을 방해 하지 않기 때문입니다. 개인적으로 타입 안전 메소드는 수동으로 오류를 테스트하고 구문 분석하고 포착하는 것보다 훨씬 낫습니다.

Immutablity:

코드에 사용되는 변경 가능한 변수가 없으므로 수행 방법을 배우는 것이 중요하며, 가장 큰 런타임 오류 소스 4 개와 미묘한 버그를 제거합니다.

  1. nulls 없음 은 NullPointerExceptions 가능성이 없음을 의미합니다 !

  2. 변경이 불가능하다는 것은 메소드 인수 변경이나 다른 변경에 대해 걱정할 필요가 없음을 의미합니다. 단계별 디버그를 수행 할 때 watch 가 변수를 변경하는 경우 어떤 변수가 어떤 값으로 변경되는지 확인할 필요가 없습니다. 이를 통해 읽을 때 논리를 100 % 결정 론적으로 만듭니다.

  3. 변경 불가능은 코드가 자동으로 스레드로부터 안전하다는 것을 의미합니다.

  4. 부작용이 없습니다. 아무것도 바꿀 수 없다면, 예기치 않은 무언가를 바꾸는 가장자리 케이스의 미묘한 부작용에 대해 걱정할 필요가 없습니다!

자신의 코드에 final 키워드 를 적용하는 방법을 이해하지 못하는 경우이 내용을 읽으십시오 .

대규모 switch 또는 if/elseif 블록 대신 Set 사용 :

Set<String> 을 사용하고 .contains() 을 사용 하여 대규모 switch 또는 if/elseif 괴물 대신 명령을 분류 하여 코드를 부풀리고 유지 관리를 악몽으로 만드는 방법에 주목 하십시오! 오버로드 된 새 명령을 추가하는 것은 생성자의 배열에 새 String 을 추가하는 것만 큼 간단 합니다.

이것은 또한 i18ni10n 및 적절한 ResourceBundles 와 매우 잘 작동 합니다. Map<Locale,Set<String>> 당신이 오버 헤드가 거의 여러 언어 지원을 할 것입니다!

@Nonnull

@Nonnull 또는 @Nullable 인 경우 모든 코드에서 명시 적으로 선언 해야한다고 결정했습니다 . IDE가 잠재적 NullPointerException 위험과 점검 할 필요가없는 경우 에 대해 경고합니다 .

가장 중요한 것은 미래의 독자들에게 이러한 메소드 매개 변수가 null 이 아니 어야한다는 기대를 문서화 한 것입니다 .

.close () 호출

실제로이 일을 생각하기 전에 생각하십시오.

sis.close() 를 호출하면 System.in 이 어떻게 될 것이라고 생각 하십니까? 위 목록의 의견을 참조하십시오.

제발 포크와 전송 풀 요청 하고 본인은 기본적인 사용 시나리오에 대한이 질문과 답을 업데이트합니다.