Accès rapide :
La vidéo
La syntaxe du « try-with-resources »
Comparaison entre try/finally et le try-with-resources
Utilisation conjointe d'un « try-with-resources » et de l'inférence de types pour les variables locales
Il est courant en Java de chercher à libérer des ressources à la fin de l'exécution d'un bloc try. Le langage Java permet cela depuis le début de son histoire via le bloc finally. Depuis Java SE 7.0, une nouvelle construction, le try-with-resources, vient compléter nos possibilités à ce sujet. Cette vidéo compare ces deux approches, exemples à l'appui.
Le « try-with-resources », syntaxe ajoutée à Java SE à partir de sa version 7.0, complète les possibilités de l'instruction try
.
Cette variante le l'instruction try
est en lien direct avec l'interface java.lang.AutoCloseable
, elle aussi ajoutée à partir de la
version 7.0 du langage.
Cette interface ne définit qu'une seule méthode abstraite : la méthode close
. Ainsi, tout type de données concret implémentant cette interface
proposera une méthode close
. C'est parfait, c'est ce qui garantira à l'instruction « try-with-resources » qu'elle pourra forcer la
fermeture de la ressource. Du coup, si vous cherchez à utiliser cette instruction sur un type n'implémentant pas l'interface attendue, une erreur de
compilation sera produite. Voici un exemple de code produisant cette erreur de compilation.
1 2 3 4 5 6 7 8 9 10 11 12 |
public class BadSyntax { public static void main( String[] args ) { // Cet exemple ne marche pas, car String n'implémente pas l'interface AutoCloseable. try ( String demo = "Hello" ) { System.out.println( demo ); } } } |
Et voici l'erreur produite par le compilateur.
$> javac BadSyntax.java BadSyntax.java:7: error: incompatible types: try-with-resources not applicable to variable type try ( String demo = "Hello" ) { ^ $>
Pour ce qui est de la syntaxe, comme vous l'avez constaté, on doit positionner les ressources à libérer entre parenthèses et directement à la
suite du mot clé try
(et donc avant l'accolade ouvrante). Comme le nom de la construction l'indique (« try-with-resources »), on peut
avoir plusieurs ressources gérées par le try
. Dans ce cas, séparer les différentes ressources via des caractères ;
.
Notez qu'on peut désormais avoir un bloc « try-with-resources » sans bloc catch et sans bloc finally
.
Voici trois exemples de syntaxes.
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 |
// Une seule ressource automatiquement gérée try ( FileInputStream fis = new FileInputStream( "inputFile.txt" ) ) { // Ici on manipule de fichier } // Appel à la méthode close automatique // Deux ressources automatiquement gérées try ( FileInputStream fis = new FileInputStream( "inputFile.txt" ) ; FileOutputStream fos = new FileOutputStream( "outputFile.txt" ) ) { // Ici on manipule nos deux fichiers (une copie de fichier, par exemple) } // Appels aux méthodes close automatique // Deux ressources automatiquement gérées, avec un bloc catch pour gérer les erreurs. try ( FileInputStream fis = new FileInputStream( "inputFile.txt" ) ; FileOutputStream fos = new FileOutputStream( "outputFile.txt" ) ) { // Ici on manipule nos deux fichiers (une copie de fichier, par exemple) } catch ( Exception exception ) { exception.printStackTrace(); } |
Avec l'arrivée du Java SE 9.0, il est maintenant possible de déclarer une variable correspondant à une ressource à libérer automatiquement en dehors du bloc try. Voici un exemple d'utilisation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import java.io.FileInputStream; public class BadSyntax { public static void main( String[] args ) throws Exception { FileInputStream fis = new FileInputStream( "inputFile.txt" ); // Une seule ressource automatiquement gérée try ( fis ) { // Uniquement si Java SE >= 9.0 // Ici on manipule de fichier } // Appel à la méthode close automatique } } |
Vous devriez commencer à vous dire que l'instruction « try-with-resources » est plus simple à utiliser qu'une instruction try / catch / finally
traditionnelle. Si tel est le cas, sachez que vous avez effectivement raison. Afin de bien comprendre ce point, je vous propose de comparer deux programmes
fonctionnellement identiques : à savoir la copie de fichier déjà présentée dans le chapitre précédent. Voici la version utilisant une instruction try
traditionnelle.
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class Copy { public static void main( String [] args ) { // Si vous ne fournissez pas deux paramètres au main, // on affiche l'usage de notre commande. if ( args.length == 0 ) { System.out.println( "Usage: java Copy sourceFile destFile" ); System.exit( 0 ); } // On calcule le nombre total d'octets du fichier source. long length = new File( args[0] ).length(); // On prépare trois variables qui correspondent au fichier source, // au fichier de destination ainsi qu'un buffer d'octets. FileInputStream inputStream = null; FileOutputStream outputStream = null; byte [] buffer = new byte[ 1024 * 1024 ]; try { // On ouvre les deux fichiers (en lecture et en écriture) inputStream = new FileInputStream( args[0] ); outputStream = new FileOutputStream( args[1] ); // On recopie les octets du fichier, tant qu'il y en a. while ( length > 0 ) { int readedBytes = inputStream.read( buffer ); outputStream.write( buffer, 0, readedBytes ); length -= readedBytes; } System.out.println( "Copie du fichier terminée" ); } catch( IOException exception ) { // Il y a donc eu une erreur durant la copie des fichiers System.err.println( "Impossible de réaliser la copie du fichier" ); } catch( SecurityException exception ) { // On a un problème de droits sur les fichiers (java.lang.SecurityException) System.err.println( "Vous n'avez pas les droits pour réaliser la copie du fichier" ); } finally { // On ferme les fichiers. if ( inputStream != null ) { try { inputStream.close(); } catch( Exception e ) { /* Tant pis */ } } if ( outputStream != null ) { try { outputStream.close(); } catch( Exception e ) { /* Tant pis */ } } } } } |
Et maintenant voici le même programme avec une instruction « try-with-resources ».
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 46 47 48 49 |
import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class Copy { public static void main( String [] args ) { // Si vous ne fournissez pas deux paramètres au main, // on affiche l'usage de notre commande. if ( args.length == 0 ) { System.out.println( "Usage: java Copy sourceFile destFile" ); System.exit( 0 ); } // On calcule le nombre total d'octets du fichier source. long length = new File( args[0] ).length(); byte [] buffer = new byte[ 1024 * 1024 ]; // On ouvre les deux fichiers (en lecture et en écriture) try ( FileInputStream inputStream = new FileInputStream( args[0] ) ; FileOutputStream outputStream = new FileOutputStream( args[1] ) ) { // On recopie les octets du fichier, tant qu'il y en a. while ( length > 0 ) { int readedBytes = inputStream.read( buffer ); outputStream.write( buffer, 0, readedBytes ); length -= readedBytes; } System.out.println( "Copie du fichier terminée" ); } catch( IOException exception ) { // Il y a donc eu une erreur durant la copie des fichiers System.err.println( "Impossible de réaliser la copie du fichier" ); } catch( SecurityException exception ) { // On a un problème de droits sur les fichiers (java.lang.SecurityException) System.err.println( "Vous n'avez pas les droits pour réaliser la copie du fichier" ); } } } |
Regardez déjà les nombres de lignes de codes utilisés par les deux exemples. Le second code est presque 1/3 plus court que le premier.
Cela est notamment dû au fait qu'avec la seconde approche les blocs catch
de l'instruction try
servent aussi à l'exécution
des deux appels aux méthodes close
, contrairement à la première version. Notez aussi qu'il n'est plus nécessaire de déclarer les deux variables
de flux (fis
et fos
) en dehors du bloc try
, ce qui permet de mieux contôler leurs portées.
Nous en avons déjà parlé dans le chapitre relatif à l'inférence de types pour les variables locales, mais Java SE 10 propose
une manière de simplifier la déclaration de vos variables locales. Les ressources gérées par le « try-with-resources » étant aussi gérées par des
variables locales, il est possible d'y utiliser le mot clé var
. Voici un exemple de code appliqué à une exécution de code en base de données.
1 2 3 4 5 6 7 8 9 |
try ( var connection = DriverManager.getConnection() ) { String strSql = "SELECT * FROM Table WHERE pk=?"; try ( var statement = connection.prepareStatement( strSql ) { statement.setInt( 1, 55 ); try ( var resultSet = statement.executeQuery() ) { // TODO: finir le code } } } |
connection
, statement
et resultSet
) sont déduits des appels de méthodes associées.
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 :