Comment interagir avec les utilisateurs en Java?

Java inclut de nombreuses classes pour interagir avec l’utilisateur. Par exemple, vous pourriez vouloir écrire une application qui demande à un utilisateur de se connecter et affiche ensuite un message de succès. Cette section contient de nombreuses techniques pour gérer et répondre aux entrées utilisateur.

Afficher des Données à l’Utilisateur

Java inclut deux instances de PrintStream pour fournir des informations à l’utilisateur: System.out et System.err. Alors que System.out devrait vous être familier, System.err pourrait être nouveau pour vous. La syntaxe pour appeler et utiliser System.err est la même que System.out mais est utilisée pour signaler des erreurs à l’utilisateur dans un I/O stream séparé des informations de sortie régulières.

try (var in = new FileInputStream("zoo.txt")) {
    System.out.println("Fichier trouvé!");
} catch (FileNotFoundException e) {
    System.err.println("Fichier introuvable!");
}

Comment diffèrent-ils en pratique? En partie, cela dépend de ce qui exécute le programme. Par exemple, si vous exécutez à partir d’une invite de commande, ils afficheront probablement du texte dans le même format. D’un autre côté, si vous travaillez dans un environnement de développement intégré (IDE), ils pourraient afficher le texte System.err dans une couleur différente. Enfin, si le code est exécuté sur un serveur, le stream System.err pourrait écrire dans un fichier journal différent.

Scénario du Monde Réel

Utilisation des API de Journalisation

Bien que System.out et System.err soient incroyablement utiles pour déboguer des applications autonomes ou simples, ils sont rarement utilisés dans le développement de logiciels professionnels. La plupart des applications s’appuient sur un service ou une API de journalisation.

Bien que de nombreuses API de journalisation soient disponibles, elles ont tendance à partager un certain nombre d’attributs similaires. D’abord, vous créez un objet de journalisation static dans chaque classe. Ensuite, vous journalisez un message avec un niveau de journalisation approprié: debug(), info(), warn(), ou error(). Les méthodes debug() et info() sont utiles car elles permettent aux développeurs de journaliser des choses qui ne sont pas des erreurs mais peuvent être utiles.

Lire l’Entrée comme un I/O Stream

Le System.in renvoie un InputStream et est utilisé pour récupérer l’entrée de texte de l’utilisateur. Il est généralement enveloppé avec un BufferedReader via un InputStreamReader pour utiliser la méthode readLine().

var lecteur = new BufferedReader(new InputStreamReader(System.in));
String entreeUtilisateur = lecteur.readLine();
System.out.println("Vous avez saisi: " + entreeUtilisateur);

Lorsqu’elle est exécutée, cette application récupère d’abord le texte de l’utilisateur jusqu’à ce que l’utilisateur appuie sur la touche Entrée. Elle affiche ensuite le texte que l’utilisateur a saisi à l’écran.

Fermeture des Streams System

Vous avez peut-être remarqué que nous n’avons jamais créé ou fermé System.out, System.err et System.in lorsque nous les avons utilisés. En fait, ce sont les seuls I/O streams de tout le chapitre pour lesquels nous n’avons pas utilisé de bloc try-with-resources!

Comme ce sont des objets static, les streams System sont partagés par toute l’application. La JVM les crée et les ouvre pour nous. Ils peuvent être utilisés dans une instruction try-with-resources ou en appelant close(), bien que leur fermeture ne soit pas recommandée. La fermeture des streams System les rend définitivement indisponibles pour tous les threads dans le reste du programme.

Que pensez-vous que le fragment de code suivant affiche?

try (var out = System.out) {}
System.out.println("Bonjour");

Rien. Il n’affiche rien. Les méthodes de PrintStream ne lancent aucune exception vérifiée et s’appuient sur checkError() pour signaler des erreurs, elles échouent donc silencieusement.

Et cet exemple?

try (var err = System.err) {}
System.err.println("Bonjour");

Celui-ci n’affiche rien non plus. Comme System.out, System.err est un PrintStream. Même s’il lançait une exception, nous aurions du mal à la voir puisque notre I/O stream pour signaler les erreurs est fermé! Fermer System.err est une particulièrement mauvaise idée, car les traces de pile de toutes les exceptions seront masquées.

Enfin, que pensez-vous que ce fragment de code fait?

var lecteur = new BufferedReader(new InputStreamReader(System.in));
try (lecteur) {}
String donnees = lecteur.readLine(); // IOException

Il affiche une exception à l’exécution. Contrairement à la classe PrintStream, la plupart des implémentations d’InputStream lanceront une exception si vous essayez d’opérer sur un I/O stream fermé.

Acquisition d’Entrée avec Console

La classe java.io.Console est spécifiquement conçue pour gérer les interactions utilisateur. Après tout, System.in et System.out ne sont que des streams bruts, tandis que Console est une classe avec de nombreuses méthodes centrées sur l’entrée utilisateur.

La classe Console est un singleton car elle n’est accessible que par une méthode d’usine et une seule instance est créée par la JVM. Par exemple, si vous rencontrez du code comme le suivant, il ne compile pas, car les constructeurs sont tous privés:

Console c = new Console();  // NE COMPILE PAS

Le fragment suivant montre comment obtenir une Console et l’utiliser pour récupérer l’entrée utilisateur:

Console console = System.console();
if (console != null) {
    String entreeUtilisateur = console.readLine();
    console.writer().println("Vous avez saisi: " + entreeUtilisateur);
} else {
    System.err.println("Console non disponible");
}

L’objet Console peut ne pas être disponible, selon l’endroit où le code est appelé. S’il n’est pas disponible, System.console() renvoie null. Il est impératif de vérifier la valeur null avant de tenter d’utiliser un objet Console!

Ce programme récupère d’abord une instance de la Console et vérifie qu’elle est disponible, en affichant un message à System.err si ce n’est pas le cas. Si elle est disponible, le programme récupère une ligne d’entrée de l’utilisateur et affiche le résultat. Comme vous l’avez peut-être remarqué, cet exemple est équivalent à notre exemple précédent de lecture d’entrée utilisateur avec System.in et System.out.

Obtention des I/O Streams Sous-jacents

La classe Console inclut l’accès à deux streams pour lire et écrire des données.

public Reader reader()
public PrintWriter writer()

L’accès à ces classes est analogue à l’appel direct de System.in et System.out, bien qu’ils utilisent des character streams plutôt que des byte streams. De cette manière, ils sont plus appropriés pour manipuler des données textuelles.

Formatage des Données de la Console

Dans le chapitre 4, vous avez appris la méthode format() sur String; et dans le chapitre 11, “Exceptions et Localisation”, vous avez travaillé avec le formatage en utilisant des locales. Commodément, chaque classe de print stream inclut une méthode format(), qui comprend une version surchargée qui prend une Locale pour combiner ces deux:

// PrintStream
public PrintStream format(String format, Object... args)
public PrintStream format(Locale loc, String format, Object... args)

// PrintWriter
public PrintWriter format(String format, Object... args)
public PrintWriter format(Locale loc, String format, Object... args)

Pour la commodité (ainsi que pour faire sentir les développeurs C plus à l’aise), Java inclut des méthodes printf(), qui fonctionnent de manière identique aux méthodes format(). La seule chose que vous devez savoir sur ces méthodes est qu’elles sont interchangeables avec format().

Voyons comment utiliser plusieurs méthodes pour afficher des informations pour l’utilisateur:

Console console = System.console();
if (console == null) {
    throw new RuntimeException("Console non disponible");
} else {
    console.writer().println("Bienvenue dans Notre Zoo!");
    console.format("Il possède %d animaux et emploie %d personnes", 391, 25);
    console.writer().println();
    console.printf("Le zoo s'étend sur %5.1f acres", 128.91);
}

En supposant que la Console soit disponible à l’exécution, elle affiche ce qui suit:

Bienvenue dans Notre Zoo!
Il possède 391 animaux et emploie 25 personnes
Le zoo s'étend sur 128.9 acres.

Utilisation de Console avec une Locale

Contrairement aux classes de print stream, Console n’inclut pas de méthode format() surchargée qui prend une instance Locale. Au lieu de cela, Console s’appuie sur la locale du système. Bien sûr, vous pourriez toujours utiliser une Locale spécifique en récupérant l’objet Writer et en passant votre propre instance Locale, comme dans l’exemple suivant:

Console console = System.console();
console.writer().format(new Locale("fr", "CA"), "Bonjour le Monde");

Lecture des Données de la Console

La classe Console inclut quatre méthodes pour récupérer des données textuelles régulières de l’utilisateur.

public String readLine()
public String readLine(String fmt, Object... args)
public char[] readPassword()
public char[] readPassword(String fmt, Object... args)

Comme l’utilisation de System.in avec un BufferedReader, la méthode readLine() de Console lit l’entrée jusqu’à ce que l’utilisateur appuie sur la touche Entrée. La version surchargée de readLine() affiche un message formaté avant de demander l’entrée.

Les méthodes readPassword() sont similaires à la méthode readLine(), avec deux différences importantes:

  • Le texte que l’utilisateur saisit n’est pas répété et affiché à l’écran pendant qu’il tape.
  • Les données sont renvoyées sous forme de char[] au lieu de String.

La première fonctionnalité améliore la sécurité en ne montrant pas le mot de passe à l’écran si quelqu’un se trouve à côté de vous. La deuxième fonctionnalité implique d’empêcher les mots de passe d’entrer dans le pool de String.

Révision des Méthodes de Console

Le dernier exemple de code que nous présentons pose à l’utilisateur une série de questions et affiche des résultats basés sur ces informations en utilisant plusieurs des méthodes que nous avons apprises dans cette section:

Console console = System.console();
if (console == null) {
    throw new RuntimeException("Console non disponible");
} else {
    String nom = console.readLine("Veuillez entrer votre nom: ");
    console.writer().format("Bonjour %s", nom);
    console.writer().println();
    console.format("Quelle est votre adresse? ");
    String adresse = console.readLine();
    char[] motDePasse = console.readPassword("Entrez un mot de passe "
        + "entre %d et %d caractères: ", 5, 10);
    char[] verification = console.readPassword("Entrez le mot de passe à nouveau: ");
    console.printf("Les mots de passe "
        + (Arrays.equals(motDePasse, verification) ? "correspondent" : "ne correspondent pas"));
}

En supposant que la Console soit disponible, la sortie devrait ressembler à ce qui suit:

Veuillez entrer votre nom: Tanguy
Bonjour Tanguy
Quelle est votre adresse? Concarneau
Entrez un mot de passe entre 5 et 10 caractères:
Entrez le mot de passe à nouveau:
Les mots de passe correspondent