如何使用 java.util.Scanner 从 System.in 正确读取用户输入并对其采取行动?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/26446599/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to use java.util.Scanner to correctly read user input from System.in and act on it?
提问by
This is meant to be a canonical question/answerthat can be used as a duplicate target. These requirements are based on the most common questions posted every day and may be added to as needed. They all require the same basic code structure to get to each of the scenarios and they are generally dependent on one another.
这是一个可用作重复目标的规范问题/答案。这些要求基于每天发布的最常见问题,并可根据需要添加。它们都需要相同的基本代码结构才能到达每个场景,并且它们通常相互依赖。
Scanner seems like a "simple"class to use, and that is where the first mistake is made. It is not simple, it has all kinds of non-obvious side effect and aberrant behaviors that break the Principle of Least Astonishmentin very subtle ways.
Scanner 似乎是一个“简单”的使用类,这就是犯第一个错误的地方。它并不简单,它具有各种不明显的副作用和异常行为,以非常微妙的方式打破了最小惊讶原则。
So this might seem to be overkill for this class, but the peeling the onions errors and problems are all simple, but taken together they are very complexbecause of their interactions and side effects. This is why there are so many questions about it on Stack Overflow every day.
所以这对于这个类来说似乎有点矫枉过正,但是剥洋葱的错误和问题都很简单,但是由于它们的相互作用和副作用,它们加在一起非常复杂。这就是为什么每天在 Stack Overflow 上都有很多关于它的问题。
Common Scanner questions:
扫描仪常见问题:
Most Scanner
questions include failed attempts at more than one of these things.
大多数Scanner
问题都包括在不止一项这些事情上的失败尝试。
I want to be able to have my program automatically wait for the next input after each previous input as well.
I want to know how to detect an exitcommand and end my program when that command is entered.
I want to know how to match multiple commands for the exitcommand in a case-insensitive way.
I want to be able to match regular expression patterns as well as the built-in primitives. For example, how to match what appears to be a date (
2014/10/18
)?I want to know how to match things that might not easily be implemented with regular expression matching - for example, an URL (
http://google.com
).
我希望能够让我的程序在每次之前的输入之后自动等待下一个输入。
我想知道如何检测退出命令并在输入该命令时结束我的程序。
我想知道如何以不区分大小写的方式为exit命令匹配多个命令。
我希望能够匹配正则表达式模式以及内置原语。例如,如何匹配似乎是日期 (
2014/10/18
) 的内容?我想知道如何匹配使用正则表达式匹配可能不容易实现的东西 - 例如,一个 URL (
http://google.com
)。
Motivation:
动机:
In the Java world, Scanner
is a special case, it is an extremely finicky class that teachers should not give new students instructions to use. In most cases the instructors do not even know how to use it correctly. It is hardly if ever used in professional production code so its value to students is extremely questionable.
在Java世界中,Scanner
是一个特例,它是一个非常挑剔的类,老师不应该给新生使用说明。在大多数情况下,教师甚至不知道如何正确使用它。它几乎没有用于专业生产代码,因此它对学生的价值非常值得怀疑。
Using Scanner
implies all the other things this question and answer mentions. It is never just about Scanner
it is about how to solve these common problems with Scanner
that are always co morbid problems in almost all the question that get Scanner
wrong. It is never just about next()
vs nextLine()
, that is just a symptom of the finickiness of the implementation of the class, there are always other issues in the code posting in questions asking about Scanner
.
使用Scanner
意味着这个问题和答案提到的所有其他事情。它永远不仅仅是关于Scanner
如何解决这些常见问题,Scanner
而在几乎所有Scanner
出错的问题中,这些问题都是共病问题。它绝不仅仅是关于next()
vsnextLine()
,这只是类实现的挑剔的一个症状,在关于Scanner
.
The answer shows a complete, idiomatic implementation of 99% of cases where Scanner
is used and asked about on StackOverflow.
答案显示了Scanner
在 StackOverflow 上使用和询问的 99% 情况的完整、惯用的实现。
Especially in beginner code. If you think this answer is too complex then complain to the instructors that tell new students to use Scanner
before explaining the intricacies, quirks, non-obvious side effects and peculiarities of its behavior.
特别是在初学者代码中。如果你认为这个答案太复杂,那么Scanner
在解释其行为的复杂性、怪癖、不明显的副作用和特殊性之前,向告诉新学生使用的教师抱怨。
Scanner
is the a great teaching moment about how important the Principle of least astonishmentis and why consistent behavior and semantics are important in naming methods and method arguments.
Scanner
是关于最小惊讶原则的重要性以及为什么一致的行为和语义在命名方法和方法参数中很重要的一个很好的教学时刻。
Note to students:
学生须知:
You will probably never actually see
Scanner
used in professional/commercial line of business apps because everything it does is done better by something else. Real world software has to be more resilient and maintainable thanScanner
allows you to write code. Real world software uses standardized file format parsers and documented file formats, not the adhocinput formats that you are given in stand alone assignments.
您可能永远不会真正看到
Scanner
在专业/商业业务线应用程序中使用,因为它所做的一切都被其他东西做得更好。现实世界的软件必须比Scanner
允许您编写代码更具弹性和可维护性。真实世界的软件使用标准化的文件格式解析器和文档化的文件格式,而不是您在独立作业中给出的即席输入格式。
采纳答案by
Idiomatic Example:
惯用语示例:
The following is how to properly use the java.util.Scanner
class to interactively read user input from System.in
correctly( sometimes referred to as stdin
, especially in C, C++ and other languages as well as in Unix and Linux). It idiomatically demonstrates the most common things that are requested to be done.
以下是如何正确使用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:
笔记:
This may look like a lot of code, but it illustrates the minimum effort needed to use the
Scanner
class correctly and not have to deal with subtle bugs and side effects that plague those new to programming and this terribly implemented class calledjava.util.Scanner
. It tries to illustrate what idiomatic Java code should look like and behave like.
这可能看起来像很多代码,但它说明了
Scanner
正确使用类所需的最小努力,而不必处理困扰编程新手和这个可怕实现的类的微妙错误和副作用java.util.Scanner
。它试图说明惯用的 Java 代码的外观和行为。
Below are some of the things I was thinking about when I wrote this example:
下面是我在写这个例子时考虑的一些事情:
JDK Version:
JDK版本:
I purposely kept this example compatible with JDK 6. If some scenario really demands a feature of JDK 7/8 I or someone else will post a new answer with specifics about how to modify this for that version JDK.
我特意让这个例子与 JDK 6 兼容。如果某些场景真的需要 JDK 7/8 的一个特性,我或其他人会发布一个新的答案,具体说明如何为那个版本的 JDK 修改它。
The majority of questions about this class come from students and they usually have restrictions on what they can use to solve a problem so I restricted this as much as I could to show how to do the common things without any other dependencies. In the 22+ years I have been working with Java and consulting the majority of that time I have never encountered professional use of this class in the 10's of millions of lines source code I have seen.
关于这门课的大部分问题来自学生,他们通常对解决问题的方法有限制,所以我尽可能地限制了这一点,以展示如何在没有任何其他依赖的情况下做常见的事情。在 22 年多的时间里,我一直在使用 Java 并在大部分时间进行咨询,我从未在我见过的数百万行源代码中遇到过此类的专业使用。
Processing commands:
处理命令:
This shows exactly how to idiomaticallyread commands from the user interactively and dispatch those commands. The majority of questions about java.util.Scanner
are of the how can I get my program to quit when I enter some specific inputcategory. This shows that clearly.
这确切地显示了如何以交互方式惯用地从用户读取命令并分发这些命令。大多数的问题,您java.util.Scanner
是的我如何才能让我的程序退出时,我进入一些特定的输入类别。这清楚地表明了这一点。
Naive Dispatcher
天真的调度员
The dispatch logic is intentionally naive so as to not complicate the solution for new readers. A dispatcher based on a Strategy Pattern
or Chain Of Responsibility
pattern would be more appropriate for real world problems that would be much more complex.
调度逻辑是故意幼稚的,以免使新读者的解决方案复杂化。基于Strategy Pattern
或Chain Of Responsibility
模式的调度程序更适合于更复杂的现实世界问题。
Error Handling
错误处理
The code was deliberately structured as to require no Exception
handling because there is no scenario where some data might not be correct.
代码被故意构建为不需要Exception
处理,因为不存在某些数据可能不正确的情况。
.hasNext()
and .hasNextXxx()
.hasNext()
和 .hasNextXxx()
I rarely see anyone using the .hasNext()
properly, by testing for the generic .hasNext()
to control the event loop, and then using the if(.hasNextXxx())
idiom lets you decide how and what to proceed with your code without having to worry about asking for an int
when none is available, thus no exception handling code.
我很少看到有人.hasNext()
正确使用它,通过测试泛型.hasNext()
来控制事件循环,然后使用if(.hasNextXxx())
习语让您决定如何以及如何处理您的代码,而不必担心int
在没有可用时询问,因此没有异常处理代码。
.nextXXX()
vs .nextLine()
.nextXXX()
对比 .nextLine()
This is something that breaks everyone's code. It is a finicky detailthat should not have to be dealt with and has a very obfusated bug that is hard to reason about because of it breaks the Principal of Least Astonishment
这会破坏每个人的代码。这是一个挑剔的细节,不应该被处理,并且有一个非常模糊的错误,很难推理,因为它打破了最小惊讶原则
The .nextXXX()
methods do not consume the line ending. .nextLine()
does.
这些.nextXXX()
方法不消耗行尾。.nextLine()
做。
That means that calling .nextLine()
immediately after .nextXXX()
will just return the line ending. You have to call it again to actually get the next line.
这意味着.nextLine()
在之后立即调用.nextXXX()
只会返回行尾。您必须再次调用它才能真正获得下一行。
This is why many people advocate either use nothing but the .nextXXX()
methods or only .nextLine()
but not both at the same time so that this finicky behavior does not trip you up. Personally I think the type safe methods are much better than having to then test and parse and catch errors manually.
这就是为什么许多人主张要么只使用.nextXXX()
方法,要么只使用两种方法.nextLine()
但不要同时使用,这样这种挑剔的行为就不会绊倒你。我个人认为类型安全的方法比必须手动测试、解析和捕获错误要好得多。
Immutablity:
不变性:
Notice that there are no mutable variables used in the code, this is important to learn how to do, it eliminates four of the most major sources of runtime errors and subtle bugs.
请注意,代码中没有使用可变变量,这对于学习如何做很重要,它消除了四个主要的运行时错误和细微错误来源。
No
nulls
means no possibility of aNullPointerExceptions
!No mutability means that you don't have to worry about method arguments changing or anything else changing. When you step debug through you never have to use
watch
to see what variables are change to what values, if they are changing. This makes the logic 100% deterministic when you read it.No mutability means your code is automatically thread-safe.
No side effects. If nothing can change, the you don't have to worry about some subtle side effect of some edge case changing something unexpectedly!
不
nulls
意味着没有可能性NullPointerExceptions
!无可变性意味着您不必担心方法参数更改或其他任何更改。当您逐步调试时,您永远不必使用
watch
查看哪些变量更改为哪些值(如果它们正在更改)。这使得逻辑在您阅读时 100% 具有确定性。无可变性意味着您的代码是自动线程安全的。
没有副作用。如果没有什么可以改变,你就不必担心某些边缘情况会意外改变某些东西的一些微妙的副作用!
Read this if you don't understand how to apply the final
keyword in your own code.
如果您不了解如何final
在自己的代码中应用关键字,请阅读本文。
Using a Set instead of massive switch
or if/elseif
blocks:
使用 Set 而不是 massswitch
或if/elseif
blocks:
Notice how I use a Set<String>
and use .contains()
to classify the commands instead of a massive switch
or if/elseif
monstrosity that would bloat your code and more importantly make maintenance a nightmare! Adding a new overloaded command is as simple as adding a new String
to the array in the constructor.
请注意我如何使用 aSet<String>
和 use.contains()
来对命令进行分类,而不是使用大量switch
或if/elseif
怪物来分类命令,这会使您的代码膨胀,更重要的是使维护成为一场噩梦!添加新的重载命令就像String
在构造函数中向数组添加新命令一样简单。
This also would work very well with i18n
and i10n
and the proper ResourceBundles
.
A Map<Locale,Set<String>>
would let you have multiple language support with very little overhead!
这也适用于i18n
andi10n
和适当的ResourceBundles
. AMap<Locale,Set<String>>
可以让您以很少的开销获得多语言支持!
@Nonnull
@非空
I have decided that all my code should explicitlydeclare if something is @Nonnull
or @Nullable
. It lets your IDE help warn you about potential NullPointerException
hazards and when you do not have to check.
我决定我所有的代码都应该明确声明是否有东西是@Nonnull
或@Nullable
。它可以让您的 IDE 帮助您警告潜在NullPointerException
危险以及何时您不必检查。
Most importantly it documents the expectation for future readers that none of these method parameters should be null
.
最重要的是,它记录了未来读者的期望,即这些方法参数都不应该是null
。
Calling .close()
调用 .close()
Really think about this one before you do it.
在你做这件事之前真的想一想。
What do you think will happen System.in
if you were to call sis.close()
? See the comments in the listing above.
System.in
如果你打电话,你认为会发生sis.close()
什么?请参阅上面列表中的注释。