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 :