如何使用 java.util.Scanner 正确读取 System.in 中的用户输入并对其进行操作?

2023-12-14

这本来是一个规范问题/答案可以用作 重复目标。这些要求基于最常见的 每天都会发布问题,并可根据需要添加。他们都 需要相同的基本代码结构来实现每个场景 他们通常是相互依赖的。


扫描仪似乎是一个"simple"要使用的类,这就是犯第一个错误的地方。它并不简单,它有各种不明显的副作用和打破常规的异常行为。最小惊讶原则以非常微妙的方式。

所以这对于这门课来说似乎有些过分了,但是剥洋葱的错误和问题都是simple,但综合起来它们非常complex因为它们的相互作用和副作用。这就是为什么 Stack Overflow 上每天都有这么多关于它的问题。

常见扫描仪问题:

Most Scanner问题包括对其中一件以上事情的失败尝试。

  1. 我希望能够让我的程序在每次输入后自动等待下一个输入。

  2. 我想知道如何检测exit命令并在输入该命令时结束我的程序。

  3. 我想知道如何匹配多个命令exit以不区分大小写的方式执行命令。

  4. 我希望能够匹配正则表达式模式以及内置基元。例如,如何匹配看似日期的内容(2014/10/18 )?

  5. 我想知道如何匹配那些可能不容易用正则表达式匹配实现的东西 - 例如,一个 URL (http://google.com ).

动机:

在Java世界中,Scanner是一个特例,这是一门极其挑剔的课程,老师不应该给新学生指导使用。在大多数情况下,讲师甚至不知道如何正确使用它。它几乎从未在专业生产代码中使用过,因此它对学生的价值非常值得怀疑。

Using Scanner暗示了这个问题和答案提到的所有其他事情。这从来不仅仅是关于Scanner这是关于如何解决这些常见问题Scanner在几乎所有的问题中,这总是共存的问题Scanner错误的。这从来不仅仅是关于next() vs nextLine(),这只是类实现的挑剔的一个症状,在询问有关问题的代码发布中总是存在其他问题Scanner.

答案显示了 99% 的情况的完整、惯用的实现Scanner在 StackOverflow 上使用和询问。

特别是在初学者代码中。如果您认为这个答案太复杂,请向告诉新学生使用的讲师抱怨Scanner在解释其行为的复杂性、怪癖、不明显的副作用和特殊性之前。

Scanner这是一个伟大的教学时刻,说明了最小惊讶原则这就是为什么一致的行为和语义在命名方法和方法参数时很重要。

学生注意事项:

你可能永远不会真正看到Scanner用于 专业/商业业务线应用程序,因为它的一切 通过其他事情可以做得更好。现实世界的软件必须是 比更具弹性和可维护性Scanner允许你写 代码。现实世界的软件使用标准化的文件格式解析器和 记录的文件格式,而不是adhoc您的输入格式 在独立作业中给出。


惯用示例:

以下是正确使用方法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 并进行咨询,大部分时间我在我见过的数十百万行源代码中从未遇到过此类的专业使用。

处理命令:

这准确地展示了如何惯用地以交互方式读取用户的命令并调度这些命令。大多数问题是关于java.util.Scanner属于输入某些特定输入时如何让程序退出类别。这清楚地表明了这一点。

天真的调度员

调度逻辑故意很简单,以免使新读者的解决方案变得复杂。一个基于调度程序的Strategy Pattern or Chain Of Responsibility模式将更适合于复杂得多的现实世界问题。

错误处理

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

.hasNext() and .hasNextXxx()

我很少看到有人使用.hasNext()正确地,通过测试通用.hasNext()控制事件循环,然后使用if(.hasNextXxx())idiom 可让您决定如何以及如何继续处理您的代码,而不必担心询问int如果没有可用的,则没有异常处理代码。

.nextXXX() vs .nextLine()

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

The .nextXXX()方法不消耗行结尾。.nextLine() does.

这意味着调用.nextLine()之后立马.nextXXX()只会返回行结尾。您必须再次调用它才能真正获取下一行。

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

不变性:

请注意,代码中没有使用可变变量,这对于学习如何做很重要,它消除了运行时错误和微妙错误的四个最主要来源。

  1. No nulls意味着不可能NullPointerExceptions!

  2. 没有可变性意味着您不必担心方法参数的更改或其他任何更改。当您逐步调试时,您永远不必使用watch查看哪些变量更改为哪些值(如果它们正在更改)。这使得你阅读它时逻辑具有 100% 确定性。

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

  4. 无副作用。如果没有什么可以改变,那么您不必担心某些边缘情况意外改变某些内容的微妙副作用!

如果您不明白如何应用该方法,请阅读本文final您自己的代码中的关键字。

使用集合而不是大量switch or if/elseif blocks:

注意我如何使用Set<String>并使用.contains()对命令进行分类而不是大量switch or if/elseif怪物会让你的代码变得臃肿,更重要的是让维护成为一场噩梦!添加新的重载命令就像添加新的一样简单String到构造函数中的数组。

这也可以很好地配合i18n and i10n和适当的ResourceBundles. A Map<Locale,Set<String>>可以让您以很少的开销获得多种语言支持!

@Nonnull

我决定我所有的代码都应该明确地声明是否有某事@Nonnull or @Nullable。它可以让您的 IDE 帮助您警告潜在的风险NullPointerException出现危险时不必检查。

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

调用.close()

在你做这件事之前,请认真考虑一下这一点。

你认为会发生什么System.in如果你打电话sis.close()?请参阅上面列表中的评论。

Please 分叉并发送拉取请求我将更新此问题并回答其他基本使用场景。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用 java.util.Scanner 正确读取 System.in 中的用户输入并对其进行操作? 的相关文章

  • Android 2.2 SDK - Droid X 相机活动无法正常完成

    我注意到我在 Droid X 上调用的默认相机活动与我的 Droid 和 Nexus One 上的默认相机活动看起来不同 在 Droid 和 Nexus One 上选择 确定 后 活动将完成 Droid X 有一个 完成 按钮 它将带您返回
  • 添加动态数量的监听器(Spring JMS)

    我需要添加多个侦听器 如中所述application properties文件 就像下面这样 InTopics Sample QUT4 Sample T05 Sample T01 Sample JT7 注意 这个数字可以多一些 也可以少一些
  • 如果使用的 JVM 是 x86 或 x64,则以不同的方式解决 Maven 依赖关系?

    我设置了一个 Maven 存储库来托管一些 dll 但我需要我的 Maven 项目根据使用的 JVM 是 x86 还是 x64 下载不同的 dll 例如 在运行 x86 版本 JVM 的计算机上 我需要从存储库下载 ABC dll 作为依赖
  • 在java中实现你自己的阻塞队列

    我知道这个问题之前已经被问过并回答过很多次了 但我只是无法根据互联网上找到的示例找出窍门 例如this http tutorials jenkov com java concurrency blocking queues html or t
  • 如何获取 WebElement 的父级[重复]

    这个问题在这里已经有答案了 我试过了 private WebElement getParent final WebElement webElement return webElement findElement By xpath 但我得到
  • 列表应该如何转换为具体的实现?

    假设我正在使用一个我不知道源代码的库 它有一个返回列表的方法 如下所示 public List
  • Git 无法识别重命名和修改的包文件

    我有一个名为的java文件package old myfile java 我已经通过 git 提交了这个文件 然后我将我的包重命名为new所以我的文件在package new myfile java 我现在想将此文件重命名 和内容更改 提交
  • Java 数组的最大维数

    出于好奇 在 Java 中数组可以有多少维 爪哇language不限制维数 但是JavaVM规范将维度数限制为 255 例如 以下代码将无法编译 class Main public static void main String args
  • 计算日期之间的天数差异

    在我的代码中 日期之间的差异是错误的 因为它应该是 38 天而不是 8 天 我该如何修复 package random04diferencadata import java text ParseException import java t
  • 如何记录来自 Akka (Java) 的所有传入消息

    在 Scala 中 您可以使用 LoggingReceive 包装接收函数 如何通过 Java API 实现相同的目标 def receive LoggingReceive case x do something Scala API 有Lo
  • Dispatcher-servlet 无法映射到 websocket 请求

    我正在开发一个以Spring为主要框架的Java web应用程序 特别使用Spring core Spring mvc Spring security Spring data Spring websocket 像这样在 Spring 上下文
  • Espresso 和 Proguard 的 Java.lang.NoClassDefFoundError

    我对 Espresso 不太有经验 但我终于成功地运行了它 我有一个应用程序需要通过 Proguard 缩小才能处于 56K 方法之下 该应用程序以 3 秒的动画开始 因此我需要等到该动画结束才能继续 这就是我尝试用该方法做的事情waitF
  • 如何在 Eclipse Java 动态 Web 项目中使用 .properties 文件?

    我正在 Eclipse 中开发动态 Web 项目 我创建了一个 properties 文件来存储数据库详细信息 用户名 密码等 我通过右键单击项目和 New gt File 添加它 我使用了Java util包Properties类 但它不
  • 为什么java中的for-each循环中需要声明变量

    for 每个循环的通常形式是这样的 for Foo bar bars bar doThings 但如果我想保留 bar 直到循环结束 我可以not使用 foreach 循环 Foo bar null Syntax error on toke
  • 无法捕获 Spring Batch 的 ItemWriter 中的异常

    我正在编写一个 Spring Batch 流程来将数据集从一个系统迁移到另一个系统 在这种情况下 这就像使用RowMapper实现在传递给查询之前从查询构建对象ItemWriter The ItemWriter称为save我的 DAO 上的
  • Android - 9 补丁

    我正在尝试使用 9 块图片创建一个新的微调器背景 我尝试了很多方法来获得完美的图像 但都失败了 s Here is my 9 patch 当我用Draw 9 patch模拟时 内容看起来不错 但是带有箭头的部分没有显示 或者当它显示时 这部
  • Java &= 运算符应用 & 或 && 吗?

    Assuming boolean a false 我想知道是否这样做 a b 相当于 a a b logical AND a is false hence b is not evaluated 或者另一方面 这意味着 a a b Bitwi
  • 嵌入式 Jetty - 以编程方式添加基于表单的身份验证

    有没有一种方法可以按如下方式以编程方式添加基于表单的身份验证 我用的是我自己的LdapLoginModule 最初我使用基本身份验证并且工作正常 但现在我想在登录页面上进行更多控制 例如显示徽标等 有没有好的样品 我正在使用嵌入式 jett
  • JAXB - 列表<可序列化>?

    我使用 xjc 制作了一些课程 public class MyType XmlElementRefs XmlElementRef name MyInnerType type JAXBElement class required false
  • 在哪里存储 Java 的 .properties 文件?

    The Java教程 http download oracle com javase tutorial essential environment properties htmlon using Properties 讨论如何使用 Prop

随机推荐

  • NumPy odeint 输出额外变量

    在仿真过程中保存中间变量的最简单方法是什么odeint在 Numpy 中 例如 def dy y t x np rand 3 1 return y x sum sim odeint dy 0 np arange 0 1 0 1 保存存储在中
  • R:找出工作日的差异

    计算工作日中的日期差异时遇到问题 即排除周末 如 Excel 中的网络日函数 这是我的数据 e lt structure list date pr structure c 15909 15933 16517 15961 15974 1597
  • Google Drive API 限制导出下载 10 mb 的策略

    我们一直在开发一个解决方案 负责生成嵌入大量图像的 PPT 然后通过 Google Slides 和 Google Drive API 在浏览器中下载 因此 我们在项目进行到一半时发现 从 Google Drive API 导出文件存在限制
  • MvcContrib TestHelper Fluent 路由测试和特定 HttpVerbs 的问题

    我正在尝试使用 MvcContrib TestHelper 流畅的路由测试 API 但我看到了奇怪的行为 WithMethod HttpVerb 扩展方法似乎没有按预期执行 这是我的控制器 显示了接受不同 HttpVerbs 的 2 个操作
  • 即使长度为 1,也返回正确的 .length

    如果我做一个 services Get Service services length 我得到126服务 但如果我指定一项服务 services Get Service Name vds services length 我什么也没得到 这搞
  • 避免鼠标移动的 QGraphicsItem 形状发生碰撞

    引发了一场有趣的讨论here关于防止 QGraphicsScene 中由 QGraphicsEllipseItems 组成的圆发生碰撞 问题将范围缩小到两个相互冲突的项目 但更大的目标仍然存在 对于任意数量的碰撞怎么办 这是期望的行为 当一
  • 如何用 BufferedImage 中的另一种颜色替换颜色

    所以我有一个图像文件 上面有一座火山 其他都是 0xFFFF00FF 不透明洋红色 我想用 0 透明 替换包含该颜色的每个像素 到目前为止我的方法如下所示 public static BufferedImage replace Buffer
  • “参数列表太长”限制是否适用于 shell 内置函数?

    我浏览过很多postsStack Overflow 以及一些相关社区argument list too long主题 我似乎不清楚长度限制是否适用于 shell 内置函数 假设我想通过标准输入将一个很长的字符串传递给命令 string a
  • 404 未找到资源:具有 Google Directory API 的域

    我按照快速入门进行操作 并尝试使用 google api ruby client 创建用户 我已经在 google api 控制台中设置了访问权限 我可以使用 API 浏览器让它工作 但是当我尝试使用 ruby 客户端时 我收到一个资源未找
  • 如何使用 postdata preSigned Url 调用 Amazon S3 存储桶以使用 Karate 上传文件

    我有一个 Amazon S3 的 postdata 预签名 URL 我想在空手道功能文件中使用它来上传文件 例如 pdf 这是我需要使用空手道 POST 请求执行的示例 Curl 请求 curl location request POST
  • 使用 REST api 创建新的团队项目

    整个星期我一直在努力反对这个问题 使用 REST api 创建新的团队项目 无论我看哪里 响应都是相同的 并且总是涉及使用命令行和 xml But why 在 Visual Studio 在线页面上可以找到 https www visual
  • 如何为新的训练模型初始化coef_init和intercept_init?

    正如这里所指定的 https stackoverflow com a 35662770 5757129 我存储了第一个模型的系数和截距 后来 我将它们作为初始化程序传递给我的第二个 fit 如下所示 以便在旧模型之上学习新数据 from s
  • 我想当用户单击 android 中的后退按钮时结束整个应用程序

    我想当用户单击 android Currently 中的后退按钮时结束整个应用程序 当前它再次转到以前打开的活动 我也尝试重写 onBackPressed 但它不起作用 public boolean onKeyDown int keyCod
  • 如何在控制器创建上添加外键值

    我有一个简单的数据库 用户可以在其中创建任务 我希望索引页显示哪些用户创建了哪些任务 任务和用户之间的关系是1个用户对应多个任务 该任务有一个 user id 外键 用户只有通过 Devise 登录后才能创建任务 我的创建控制器如下 但我不
  • 如何更改 EditText 选择手柄/锚点的颜色/外观?

    所以我改变了 Holo 主题的风格全息颜色生成器 and 操作栏样式生成器到我自己的颜色 但是当我在编辑文本中选择文本时 所选位置的 标记 仍然是蓝色的 我怎样才能改变它 这里最糟糕的部分是找到该项目的 名称 以及它在主题内的调用方式 所以
  • 如何配置 @PreAuthorize 来识别我登录用户的 ID?

    我正在尝试创建一个 Spring Boot 2 1 应用程序 我创建了以下休息控制器 RestController RequestMapping api users public class UserController PutMappin
  • MySql、Postgres、Oracle 和 SQLServer 忽略 IS NOT NULL 过滤器

    当我准备回答 SO 上的一位同事时 我遇到了一种奇怪的情况 至少对我来说是这样 原来的问题在这里 数据透视表忽略具有空值的行 我已经修改了查询以使用max代替group concat为了显示所有数据库中的 问题 SELECT id max
  • 调用 SendMessage (P/Invoke) 不断崩溃

    我必须编写一个与第三方程序通信的应用程序 AOL 对不起 做了很多研究 我发现了一些方法来做到这一点P Invoke 并且在很大程度上它works好的 但它会在后续试验中崩溃 特别是SendMessage 我在下面概述了崩溃的代码 所有这些
  • 动态设置复选框的初始值

    我有一个带有 CheckboxSelectMutliple 小部件的 MultipleChoiceField weight training days forms MultipleChoiceField help text u Requir
  • 如何使用 java.util.Scanner 正确读取 System.in 中的用户输入并对其进行操作?

    这本来是一个规范问题 答案可以用作 重复目标 这些要求基于最常见的 每天都会发布问题 并可根据需要添加 他们都 需要相同的基本代码结构来实现每个场景 他们通常是相互依赖的 扫描仪似乎是一个 simple 要使用的类 这就是犯第一个错误的地方