惯用示例:
以下是正确使用方法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()
但不要同时两者,这样这种挑剔的行为就不会绊倒你。我个人认为类型安全的方法比手动测试、解析和捕获错误要好得多。
不变性:
请注意,代码中没有使用可变变量,这对于学习如何做很重要,它消除了运行时错误和微妙错误的四个最主要来源。
No nulls
意味着不可能NullPointerExceptions
!
没有可变性意味着您不必担心方法参数的更改或其他任何更改。当您逐步调试时,您永远不必使用watch
查看哪些变量更改为哪些值(如果它们正在更改)。这使得你阅读它时逻辑具有 100% 确定性。
没有可变性意味着您的代码自动是线程安全的。
无副作用。如果没有什么可以改变,那么您不必担心某些边缘情况意外改变某些内容的微妙副作用!
如果您不明白如何应用该方法,请阅读本文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 分叉并发送拉取请求我将更新此问题并回答其他基本使用场景。