Ответ 1
Идиоматический пример:
Ниже приведен пример правильного использования класса java.util.Scanner
для интерактивного чтения пользовательского ввода из System.in
правильно (иногда называемого stdin
, особенно на языках 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);
}
}
Примечания:
Это может выглядеть как много кода, но это иллюстрирует минимальный необходимо использовать класс
Scanner
правильно и не нужно иметь дело с тонкими ошибками и побочными эффектами, которые программирование и этот ужасно реализованный класс, называемыйjava.util.Scanner
. Он пытается проиллюстрировать, какой идиоматический код Java должен выглядеть и вести себя как.
Ниже приведены некоторые из тех вещей, о которых я думал, когда писал этот пример:
Версия JDK:
Я намеренно сохранил этот пример совместимым с JDK 6. Если какой-то сценарий действительно требует функции JDK 7/8, я или кто-то другой опубликует новый ответ со спецификой о том, как изменить это для этой версии JDK.
Большинство вопросов об этом классе поступают от студентов, и у них обычно есть ограничения на то, что они могут использовать для решения проблемы, поэтому я ограничил это настолько, насколько мог, чтобы показать, как делать общие вещи без каких-либо других зависимостей. В 22 года я работал с Java и консультировал большую часть того времени, когда я никогда не сталкивался с профессиональным использованием этого класса в 10-миллионном исходном коде, который я видел.
Команды обработки:
Наивный диспетчер
Логика отправки намеренно наивна, чтобы не усложнять решение для новых читателей. Диспетчер, основанный на шаблоне Strategy Pattern
или Chain Of Responsibility
, будет более подходящим для проблем реального мира, которые будут намного сложнее.
Обработка ошибок
Код был намеренно структурирован так, чтобы не требовать обработки Exception
, потому что нет сценария, когда некоторые данные могут быть неправильными.
.hasNext()
и .hasNextXxx()
Я редко вижу, что кто-либо использует .hasNext()
правильно, тестируя общий .hasNext()
для управления циклом событий, а затем с помощью idiom if(.hasNextXxx())
позволяет вам решить, как и что делать с вашим кодом, без необходимости беспокоиться о том, чтобы запросить int
, когда ни один из них не доступен, поэтому код обработки исключений отсутствует.
.nextXXX()
vs .nextLine()
Это то, что разбивает каждый код. Это тонкая деталь, с которой не приходится иметь дело, и имеет очень обфузированную ошибку, о которой трудно рассуждать, потому что она разбивает Принцип наименьшего удивления
Методы .nextXXX()
не потребляют окончания строки. .nextLine()
делает.
Это означает, что вызов .nextLine()
сразу после .nextXXX()
просто вернет конец строки. Вы должны вызвать его снова, чтобы на самом деле получить следующую строку.
Так много людей выступают за то, чтобы использовать ничего, кроме методов .nextXXX()
или только .nextLine()
, но не для обоих, чтобы это хулиганство не отключало вас.
Immutablity:
Обратите внимание, что в коде не используются изменяемые переменные, это важно для того, чтобы научиться делать это, оно устраняет четыре основных источника ошибок времени выполнения и тонкие ошибки.
-
Нет
nulls
означает отсутствие возможностиNullPointerExceptions
! -
Никакая изменчивость означает, что вам не нужно беспокоиться об изменении параметров метода или изменении чего-либо другого. Когда вы начинаете отладку, вам никогда не придется использовать
watch
, чтобы увидеть, какие переменные изменяются к каким значениям, если они меняются. Это делает логику 100% детерминированной, когда вы ее читаете. -
Никакая изменчивость означает, что ваш код автоматически потокобезопасен.
-
Никаких побочных эффектов. Если ничего не изменится, вам не нужно беспокоиться о каком-то тонком побочном эффекте какого-то случая с краем, что-то неожиданно!
Прочтите это, если вы не понимаете, как применить ключевое слово final
в своем собственном коде.
Использование набора вместо массивных switch
или if/elseif
блоков:
Обратите внимание, как я использую Set<String>
и использую .contains()
для классификации команд вместо массивного switch
или if/elseif
монстра, который раздувает ваш код и, что более важно, делает обслуживание кошмаром! Добавление новой перегруженной команды так же просто, как добавление нового String
в массив в конструкторе.
Это также очень хорошо работает с i18n
и i10n
и правильным ResourceBundles
.
A Map<Locale,Set<String>>
позволит вам иметь поддержку нескольких языков с очень небольшими накладными расходами!
@Nonnull
Я решил, что весь мой код должен явно объявить, если что-то есть @Nonnull
или @Nullable
. Это позволяет вашей IDE помочь предупредить вас о потенциальных опасностях NullPointerException
, и когда вам не нужно проверять.
Самое главное, он документирует ожидания будущих читателей, что ни один из этих параметров метода не должен быть null
.
Вызов .close()
На самом деле подумайте об этом, прежде чем делать это.
Как вы думаете, произойдет ли System.in
, если вы должны позвонить sis.close()
? См. Комментарии в приведенном выше списке.