如何使用java.utilite.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:

在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 中正确交互地读取用户输入(有时称为 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并咨询了大多数人,在我所看到的10百万行代码中,从未遇到过此类的专业用途。

处理命令。

这恰好显示了如何以交互方式惯用地从用户读取命令并分派这些命令。有关 java.util.Scanner 的大多数问题是输入某些特定输入类别时如何使程序退出。这清楚地表明了这一点。

天真的调度员

调度逻辑是天真的幼稚,以免使新读者的解决方案复杂化。基于 Strategy PatternChain Of Responsibility 模式的调度员将更适合于现实世界中更为复杂的问题。

错误处理

该代码经过精心设计,不需要任何 Exception 处理,因为在某些情况下某些数据可能不正确。

.hasNext().hasNextXxx()

我很少看到使用任何 .hasNext() 正常,通过测试通用 .hasNext() 来控制事件循环,然后使用 if(.hasNextXxx()) 成语让你决定如何,哪些与您的代码进行无需担心在没有可用的 int 时询问int的情况,因此无需任何异常处理代码。

.nextXXX().nextLine()

这会破坏每个人的代码。这是一个很挑剔的细节,不应该处理,并且有一个非常难以理解的错误,因为它破坏了“最小惊讶 ” 原则,所以很难推理。

.nextXXX() 方法不消耗行结束。 .nextLine() 可以。

这意味着,调用 .nextLine() 后立即 .nextXXX() 将只返回结束行。您必须再次调用它才能实际获得下一行。

这就是为什么许多人主张要么只使用 .nextXXX() 方法,要么只使用 .nextLine() ,但不要同时使用这两种方法,以便这种挑剔的行为不会使您绊倒。我个人认为类型安全方法比必须手动测试和解析并捕获错误要好得多。

Immutablity:

注意代码中没有使用可突变变量,这一点很重要,要学会做,它消除了运行时错误和细微BUG中最主要的四个来源。

  1. 没有 nulls 意味着没有 NullPointerExceptions 的可能性 !

  2. 没有任何可变性意味着您不必担心方法参数更改或其他任何更改。当您逐步调试时,如果变量正在更改,则不必 watch 将哪些变量更改为什么值。当您阅读时,这使逻辑具有100%的确定性。

  3. 没有突变性意味着你的代码是自动的线程安全。

  4. 没有副作用。如果没有什么可以改变的,你就不用担心一些微妙的副作用,因为一些边缘的情况下,会有一些微妙的副作用,会有一些意想不到的变化!

如果您不了解如何在自己的代码中应用 final 关键字,请阅读此文章。

使用Set代替大量 switchif/elseif 块:

请注意,我如何使用 Set<String> 并使用 .contains() 对命令进行分类,而不是使用大量 switchif/elseif 怪异行为,这会使您的代码膨胀,更重要的是使维护成为噩梦!添加新的重载命令就像将新的 String 添加到构造函数中的数组一样简单。

这对于 i18ni10n 以及适当的 ResourceBundles 也可以很好地工作。一个 Map<Locale,Set<String>> 将让您拥有多语言支持非常小的开销!

@Nonnull

我已经决定所有代码都应明确声明 @Nonnull@Nullable 。它可以让您的IDE帮助您警告潜在的 NullPointerException 危险以及何时无需检查。

最重要的是,它记录了对未来读者的期望,即这些方法参数都不应该为 null

调用.close()

真的要想好了再去做这件事。

如果您调用 sis.close() ,您认为 System.in 会发生什么?请参阅上面清单中的评论。

分叉并发送请求请求,我将针对其他基本用法方案更新此问题和解答。