Accès rapide :
La vidéo
La classe java.util.Scanner
Passer vos propres expressions régulières au Scanner
Changer le format du séparateur de votre Scanner
Travaux pratiques
Le sujet
La correction
La classe utilitaire java.util.Scanner
permet de parcourir un flux et d'en extraire son contenu en utilisant des expressions régulières.
Cette vidéo vous montre comment utiliser cette classe.
Depuis la mise à disposition du Java SE 5.0, vous avez la possibilité d'utiliser une classe utilitaire très intéressante : la classe
java.util.Scanner
. Celle-ci est, en quelque sorte, un outil permettant de simplifier l'utilisation d'expressions régulières dans vos
programmes. Cet outil peut, de plus, fonctionner de différentes manières.
On crée un scanner par-dessus un flux (System.in
, par exemple). Ensuite, ce scanner permet d'extraire des informations en spécifiant une
ou plusieurs expressions régulières correspondantes aux données souhaitées. Pour les principaux types de données Java, des expressions régulières
adaptées sont déjà prédéfinies : vous n'aurez donc pas à les fournir. Il est aussi possible de spécifier le format des séparateurs entre chaque donnée
à extraire par le biais d'une expression régulières (par défaut, un séparateur est constitué d'un ou plusieurs caractères parmi l'espace et les
caractères de tabulation et de retour à la ligne).
La manière la plus simple d'utiliser cette classe, consiste à utiliser les expressions régulières qu'elle contient pour extraire des données correspondantes
aux types de bases du langage Java. Pour ce faire, il faudra invoquer une méthode préfixée par next
, en fonction du type de données
considéré. Par exemple, si vous souhaitez extraire un entier, il faudra invoquer la méthode nextInt
. Voici un premier exemple d'utilisation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import java.util.Scanner; public class TestScanner { public static void main(String[] args) { try ( Scanner scanner = new Scanner( System.in ) ) { System.out.print( "Veuillez saisir un premier entier : " ); int a = scanner.nextInt(); System.out.print( "Veuillez saisir un second entier : " ); int b = scanner.nextInt(); int result = a + b; System.out.printf( "La somme de %d et de %d vaut %d\n", a, b, result ); } } } |
Scanner
utilisée par ce programme est placée dans une instruction try
.
Il s'agit plus précisément d'un « try-with-resources » introduit dans le langage Java SE 7.0. Cette forme de try
permet de
garantir que le scanner sera bien libéré en sortie de try
en y invoquant automatiquement la méthode Scanner.close()
.
Nous reviendrons plus en détail sur cette instruction « try-with-resources » dans un futur chapitre de ce cours.
Outre la méthode nextInt
, vous y retrouvez aussi nextByte
, nextShort
, nextLong
,
nextFloat
, nextDouble
, ... Pour chacune de ces méthodes, une expression régulière est utilisée par votre scanner.
Mémorisez que pour les méthodes d'extraction d'entiers, vous pouvez aussi contrôler la base décimale à utiliser.
La méthode nextLine
permet de lire une ligne de texte jusqu'au prochain retour à la ligne. Voici un exemple d'utilisation de cette
méthode.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
import java.util.Scanner; public class TestScanner { public static void main(String[] args) { try ( Scanner scanner = new Scanner( System.in ) ) { while( true ) { System.out.print( "Enter your login: " ); String login = scanner.nextLine(); System.out.print( "Enter your password: " ); String password = scanner.nextLine(); if ( login.equals( "Bond" ) && password.equals( "007" ) ) { break; } } System.out.println( "Welcome James" ); System.out.print( "How many spies are you killed: " ); int count = scanner.nextInt(); if ( count > 100 ) System.out.println( "You are a great spy!!!" ); } } } |
Il est possible de demander au scanner si une donnée particulière est disponible. Plusieurs méthodes existent pour répondre à ce besoin, en fonction
du type de la donnée souhaité. Citons notamment Scanner.hasNext()
pour savoir si des caractères sont disponibles,
Scanner.hasNextInt
pour savoir si une donnée de type int
est immédiatement disponible, ... Bien entendu, toutes ces méthodes
renvoient un résultat de type booléen. Voici un petit exemple d'utilisation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import java.util.Scanner; public class TestScanner { public static void main( String[] args ) throws Exception { ProcessBuilder processBuilder = new ProcessBuilder( "ifconfig" ); processBuilder.redirectErrorStream( true ); try (Scanner scanner = new Scanner( processBuilder.start().getInputStream() )) { while ( scanner.hasNext() ) { String line = scanner.nextLine(); if ( line.length() > 0 && line.charAt( 0 ) != ' ' ) { System.out.println( line ); } } System.out.println( "-------------------------------------\nBye bye" ); } } } |
java.lang.ProcessBuilder
qui permet de lancer un nouveau processus (un nouveau programme, ici la commande
ifconfig
sur un système linux) et de récupérer la sortie standard produite par ce processus. C'est sur cette sortie standard que nous
greffons notre scanner afin d'en récupérer les lignes de texte.
throws
exception, présent dans la définition du main
, est utilisé pour indiquer que nous ne souhaitons pas gérer les
erreurs et que si une erreur doit survenir alors elle sera affichée dans la console et que le programme devra s'arrêter.
Vous l'avez peut-être déjà constaté, il existe une autre méthode appelée simplement next
. Elle permet d'extraire une chaîne de caractères
jusqu'au prochain séparateur (délimiteur). Le type de retour est, bien entendu, String
.
Par défaut, un séparateur est constitué d'un ou plusieurs caractères parmi l'espace, un caractère de tabulation ou un caractère de retour à la ligne.
java.util.Scanner
, consultez notre
documentation de l'API Java.
Revenons à nos expressions régulières. Pour indiquer à notre scanner le format de la prochaine donnée à extraire, nous utilisons la méthode
Scanner.next( regExp )
. Attention, si cette donnée ne correspond pas au format de votre expression régulière, une erreur (une
exception) de type java.util.InputMismatchException
sera déclenchée.
Le programme suivant demande à saisir trois informations, chacune d'un type différent, et récupère ces données grâce au un scanner. Chaque donnée doit être séparée de la précédente via un (ou plusieurs) espace(s) ou retour(s) à la ligne.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import java.util.InputMismatchException; import java.util.Locale; import java.util.Scanner; public class TestScanner { public static void main( String[] args ) throws InputMismatchException { try ( Scanner scanner = new Scanner( System.in ) ) { // La valeur double devra utiliser le . comme séparateur de partie décimale scanner.useLocale( Locale.ENGLISH ); System.out.print( "Enter an IP address, a double value and an email: " ); String ip = scanner.next( "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}" ); System.out.println( "IP address == " + ip ); double value = scanner.nextDouble(); System.out.println( "double == " + value ); String email = scanner.next( "[\\w.-]+@[\\w.-]+\\.[a-z]{2,}" ); System.out.println( "email == " + email ); } } } |
Testons ce programme : je vais le lancer une fois en y saisissant des données compatible avec les types attendus, puis une seconde fois avec des données non compatibles, histoire de voir les résultats produits. Voici ces résultats.
$> java TestScanner Enter an IP address, a double value and an email: 127.0.0.1 3.1415 user@enterprise.com IP address == 127.0.0.1 double == 3.1415 email == user@enterprise.com $> java TestScanner Enter an IP address, a double value and an email: 127.0.0.1 user@enterprise.com IP address == 127.0.0.1 Exception in thread "main" java.util.InputMismatchException at java.util.Scanner.throwFor(Scanner.java:864) at java.util.Scanner.next(Scanner.java:1485) at java.util.Scanner.nextDouble(Scanner.java:2413) at TestScanner.main(TestScanner.java:18) $>
Vous pouvez aussi utiliser une expression régulière pour définir le format des séparateurs utilisés par votre scanner. A titre d'exemple, nous
allons réutiliser le programme précédent, mais nous allons y changer le séparateur : il sera maintenant constitué du caractère ;
préfixé et suffixé d'au moins un blanc (dans notre exemple la tabulation ne sera plus supportée). L'expression régulière sera la suivante :
+; +
. Voici la nouvelle version du programme.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import java.util.InputMismatchException; import java.util.Locale; import java.util.Scanner; public class TestScanner { public static void main( String[] args ) throws InputMismatchException { try ( Scanner scanner = new Scanner( System.in ) ) { // La valeur double devra utiliser le . comme séparateur de partie décimale scanner.useLocale( Locale.ENGLISH ); scanner.useDelimiter( " +; +" ); System.out.print( "Enter an IP address, a double value and an email: " ); String ip = scanner.next( "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}" ); System.out.println( "IP address == " + ip ); double value = scanner.nextDouble(); System.out.println( "double == " + value ); String email = scanner.next( "[\\w.-]+@[\\w.-]+\\.[a-z]{2,}" ); System.out.println( "email == " + email ); } } } |
Testons maintenant notre nouveau séparateur.
$> java TestScanner Enter an IP address, a double value and an email: 127.0.0.1 ; 3.1415 ; user@enterprise.com ; IP address == 127.0.0.1 double == 3.1415 email == user@enterprise.com $>
;
!!!
Commencez par créer un fichier appelé data.csv
. Le format CSV (Comma Separated Value) est souvent utilisé par les tableurs des packs
office (Libre Office Calc, ...). Il permet d'organiser des données sous forme tabulaire. Deux principaux séparateurs sont couramment utilisés :
la virgule (comma en anglais) ou le point-virgule (semicolon en anglais). Traditionnellement, la première ligne du fichier définie les titres des
colonnes du fichier. Voici le contenu de notre fichier data.csv
.
Identifier;First name;Last name;Email 0;Johnny;English;bean@mi0.uk 7;James;Bond;007@mi6.uk 8;Jason;Bourne;bourne@cia.us 9;Hubert;Bonisseur De La Bath;oss117@espions.fr
Bien entendu, nous n'avons pas encore abordé la gestion des fichiers dans ce cours. Je vous propose donc un code de base réalisant l'ouverture du
fichier. Il faut juste savoir qu'un fichier en lecture, et en Java, sera géré par la classe java.io.FileInputStream
. Cette classe est
compatible, par héritage, avec la classe java.io.InputStream
qui est attendue par un scanner. Donc c'est parfait, nous devrions pouvoir
extraire les données du fichier via un scanner : vous allez donc pouvoir travailler. ;-)
Voici le squelette du programme que je vous propose de développer.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import java.io.FileInputStream; import java.util.Scanner; public class CsvReader { public static final int COLUMN_COUNT = 4; public static void main( String[] args ) throws Exception { try ( FileInputStream inputStream = new FileInputStream( "data.csv" ) ; Scanner scanner = new Scanner( inputStream ) ) { // Affichage } } } |
Le but du TP est simple. On sait que le tableau possède quatre colonnes : afficher le contenu du tableau sous forme tabulaire dans la console, avec la ligne de titre mise en évidence.
J'ai cherché à faire une présentation un peu sexy en présentant un tableau sur la console.
Histoire que les cellules aient toutes les mêmes tailles dans le tableau, je me suis aidé de la méthode String.printf
afin de pouvoir utiliser des affichages formatés.
La partie la plus délicate est certainement de correctement gérer les séparateurs.
A ce niveau, je me suis un peu simplifié la vie en acceptant soit un caractère ,
, soit un caractère ;
ou enfin un retour à la
ligne. Cette dernière possibilité est très importante si on veut correctement passer à la ligne suivante.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
import java.io.FileInputStream; import java.util.Scanner; public class CsvReader { public static final int COLUMN_COUNT = 4; public static void main( String[] args ) throws Exception { try ( FileInputStream inputStream = new FileInputStream( "data.csv" ) ; Scanner scanner = new Scanner( inputStream ) ) { scanner.useDelimiter( "[,;\\n]" ); String identifier = scanner.next(); String firstName = scanner.next(); String lastName = scanner.next(); String email = scanner.next(); String segment = "--------------------"; System.out.printf( "+%s+%s+%s+%s+\n", segment, segment, segment, segment ); System.out.printf( "|%-20s|%-20s|%-20s|%-20s|\n", identifier, firstName, lastName, email ); System.out.printf( "+%s+%s+%s+%s+\n", segment, segment, segment, segment ); while( scanner.hasNext() ) { identifier = scanner.next(); firstName = scanner.next(); lastName = scanner.next(); email = scanner.next(); System.out.printf( "|%-20s|%-20s|%-20s|%-20s|\n", identifier, firstName, lastName, email ); } System.out.printf( "+%s+%s+%s+%s+\n", segment, segment, segment, segment ); } } } |
Et voici les résultats produits par cet exemple.
+--------------------+--------------------+--------------------+--------------------+ |Identifier |First name |Last name |Email | +--------------------+--------------------+--------------------+--------------------+ |0 |Johnny |English |bean@mi0.uk | |7 |James |Bond |007@mi6.uk | |8 |Jason |Bourne |bourne@cia.us | |9 |Hubert |Bonisseur De La Bath|oss117@espions.fr | +--------------------+--------------------+--------------------+--------------------+
Améliorations / Corrections
Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.
Emplacement :
Description des améliorations :