Il s'agit d'une question/réponse canonique qui peut être utilisée comme cible en double. Ces exigences sont basées sur les questions les plus fréquemment posées chaque jour et peuvent être ajoutées au besoin. Ils nécessitent tous la même structure de code de base pour accéder à chacun des scénarios et ils dépendent généralement les uns des autres.
Le scanner semble être une classe "simple" à utiliser, et c'est là que la première erreur est commise. Ce n'est pas simple, il a toutes sortes d'effets secondaires non évidents et des comportements aberrants qui brisent le Principe du moindre étonnement de manière très subtile.
Donc, cela peut sembler exagéré pour cette classe, mais les erreurs et les problèmes de pelage des oignons sont tous simple, mais pris ensemble, ils sont très complexes à cause de leurs interactions et effets secondaires. C'est pourquoi il y a tellement de questions à ce sujet sur Stack Overflow chaque jour.
La plupart des questions Scanner
incluent des tentatives infructueuses de plusieurs de ces choses.
Je souhaite que mon programme attende automatiquement la prochaine entrée après chaque entrée précédente.
Je veux savoir comment détecter une commande exit et terminer mon programme lorsque cette commande est entrée.
Je veux savoir comment faire correspondre plusieurs commandes pour la commande exit d'une manière qui ne respecte pas la casse.
Je veux pouvoir faire correspondre les modèles d'expression régulière ainsi que les primitives intégrées. Par exemple, comment faire correspondre ce qui semble être une date (2014/10/18
)?
Je veux savoir comment faire correspondre des choses qui pourraient ne pas être facilement mises en œuvre avec une correspondance d'expressions régulières - par exemple, une URL (http://google.com
).
Dans le monde Java, Scanner
est un cas particulier, c'est une classe extrêmement capricieuse que les enseignants ne devraient pas donner aux nouveaux élèves des instructions à utiliser. Dans la plupart des cas, les instructeurs ne savoir comment l’utiliser correctement. Il n’est pratiquement pas utilisé dans le code de la production professionnelle, donc sa valeur pour les étudiants est extrêmement discutable.
L'utilisation de Scanner
implique toutes les autres choses que cette question et réponse mentionne. Il ne s'agit jamais seulement de Scanner
, il s'agit de savoir comment résoudre ces problèmes courants avec Scanner
qui sont toujours des problèmes co-morbides dans presque toutes les questions qui font mal à Scanner
. Il ne s'agit jamais seulement de next()
vs nextLine()
, c'est juste un symptôme de la finesse de l'implémentation de la classe, il y a toujours d'autres problèmes dans le publication de code dans les questions concernant Scanner
.
La réponse montre une implémentation complète et idiomatique de 99% des cas où Scanner
est utilisé et interrogé sur StackOverflow.
Surtout dans le code débutant. Si vous pensez que cette réponse est trop complexe, alors plaignez-vous aux instructeurs qui disent aux nouveaux étudiants d'utiliser Scanner
avant d'expliquer les subtilités, les bizarreries, les effets secondaires non évidents et les particularités de son comportement.
Scanner
est un grand moment d'enseignement sur l'importance du Principe du moindre étonnement et sur la raison pour laquelle un comportement et une sémantique cohérents sont importants pour nommer les méthodes et les arguments de méthode.
Vous ne verrez probablement jamais réellement
Scanner
utilisé dans des applications professionnelles/commerciales car tout ce qu'il fait est mieux fait par autre chose. Les logiciels du monde réel doivent être plus résistants et maintenables queScanner
vous permet d'écrire du code. Les logiciels du monde réel utilisent des analyseurs de format de fichier normalisés et des formats de fichier documentés, pas les formats d'entrée adhoc qui vous sont donnés dans des affectations autonomes.
Voici comment utiliser correctement la classe Java.util.Scanner
Pour lire correctement les entrées utilisateur de System.in
(Parfois appelé stdin
, en particulier en C, C++ et dans d'autres langages également. comme sous Unix et Linux). Il illustre de manière idiomatique les tâches les plus courantes à effectuer.
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);
}
}
Cela peut ressembler à beaucoup de code, mais il illustre l'effort minimum nécessaire pour utiliser correctement la classe
Scanner
et ne pas avoir à faire face à de subtils bugs et effets secondaires qui affligent ceux qui débutent en programmation et cette classe terriblement implémentée appeléeJava.util.Scanner
. Il essaie d'illustrer à quoi devrait ressembler et se comporter le code idiomatique Java code).
Voici quelques-unes des choses auxquelles je pensais quand j'ai écrit cet exemple:
J'ai délibérément gardé cet exemple compatible avec JDK 6. Si un scénario exige vraiment une fonctionnalité de JDK 7/8, ou quelqu'un d'autre publiera une nouvelle réponse avec des détails sur la façon de modifier cela pour cette version JDK.
La majorité des questions sur cette classe viennent des étudiants et ils ont généralement des restrictions sur ce qu'ils peuvent utiliser pour résoudre un problème, j'ai donc restreint cela autant que possible pour montrer comment faire les choses courantes sans autres dépendances. Depuis plus de 22 ans, je travaille avec Java et consultant la majorité de ce temps, je n'ai jamais rencontré d'utilisation professionnelle de cette classe dans les 10 millions de dollars) de code source de lignes que j'ai vu.
Cela montre exactement comment idiomatiquement lire les commandes de l'utilisateur de manière interactive et distribuer ces commandes. La majorité des questions sur Java.util.Scanner
Sont de comment puis-je faire quitter mon programme lorsque j'entre dans une catégorie d'entrée spécifique . Cela le montre clairement.
La logique de répartition est intentionnellement naïve afin de ne pas compliquer la solution pour les nouveaux lecteurs. Un répartiteur basé sur un modèle Strategy Pattern
Ou Chain Of Responsibility
Serait plus approprié pour des problèmes du monde réel qui seraient beaucoup plus complexes.
Le code a été délibérément structuré de manière à ne nécessiter aucune manipulation de Exception
car il n'y a aucun scénario où certaines données pourraient ne pas être correctes.
.hasNext()
et .hasNextXxx()
Je vois rarement quelqu'un utiliser correctement la .hasNext()
, en testant la générique .hasNext()
pour contrôler la boucle d'événements, puis en utilisant l'idiome if(.hasNextXxx())
vous permet de décider comment et quoi faire avec votre code sans avoir à vous soucier de demander un int
quand aucun n'est disponible, donc pas de code de gestion des exceptions.
.nextXXX()
vs .nextLine()
C'est quelque chose qui brise le code de tout le monde. C'est un détail capricieux qui ne devrait pas avoir à être traité et qui a un bug très obscur qui est difficile à raisonner car il casse le Principal of Least Astonishment
Les méthodes .nextXXX()
ne consomment pas la fin de ligne. .nextLine()
fait.
Cela signifie que l'appel de .nextLine()
immédiatement après .nextXXX()
renverra simplement la fin de la ligne. Vous devez l'appeler à nouveau pour obtenir la ligne suivante.
C'est pourquoi de nombreuses personnes préconisent d'utiliser uniquement les méthodes .nextXXX()
ou seulement .nextLine()
mais pas les deux en même temps afin que ce comportement capricieux ne vous gêne pas. Personnellement, je pense que les méthodes sûres de type sont bien meilleures que de devoir ensuite tester et analyser et intercepter les erreurs manuellement.
Notez qu'aucune variable modifiable n'est utilisée dans le code, c'est important d'apprendre à le faire, cela élimine quatre des sources les plus importantes d'erreurs d'exécution et de bogues subtils.
Non nulls
signifie aucune possibilité de NullPointerExceptions
!
Aucune mutabilité signifie que vous n'avez pas à vous soucier du changement des arguments de méthode ou de tout autre changement. Lorsque vous effectuez un débogage, vous n'avez jamais besoin d'utiliser watch
pour voir quelles variables sont modifiées en quelles valeurs, si elles changent. Cela rend la logique 100% déterministe lorsque vous la lisez.
Aucune mutabilité signifie que votre code est automatiquement thread-safe.
Pas d'effets secondaires. Si rien ne change, vous n'avez pas à vous soucier des effets secondaires subtils de certains cas Edge changeant quelque chose de manière inattendue!
Lisez ceci si vous ne comprenez pas comment appliquer le mot clé final
dans votre propre code.
switch
ou if/elseif
Massifs:Remarquez comment j'utilise un Set<String>
Et j'utilise .contains()
pour classer les commandes au lieu d'une énorme monstruosité switch
ou if/elseif
Qui alourdirait votre code et plus important encore faire de l'entretien un cauchemar! L'ajout d'une nouvelle commande surchargée est aussi simple que l'ajout d'un nouveau String
au tableau dans le constructeur.
Cela fonctionnerait également très bien avec i18n
Et i10n
Et le bon ResourceBundles
. Un Map<Locale,Set<String>>
Vous permettrait d'avoir un support multilingue avec très peu de frais généraux!
J'ai décidé que tout mon code devrait explicitement déclarer si quelque chose est @Nonnull
Ou @Nullable
. Il permet à votre IDE aide de vous avertir des dangers potentiels de NullPointerException
et quand vous n'avez pas à vérifier.
Plus important encore, il documente l'attente des futurs lecteurs qu'aucun de ces paramètres de méthode ne devrait être null
.
Pensez vraiment à celui-ci avant de le faire.
À votre avis, que se passera-t-il System.in
Si vous appelez sis.close()
? Voir les commentaires dans la liste ci-dessus.