août
2010
Après le multi-catch/rethrow et depuis la version b105, les derniers builds du JDK 7 intègrent désormais le support des try-with-resources (bloc ARM), ce qui permettra enfin de pouvoir gérer proprement la fermeture des ressources de manière simple et efficace.
Voilà enfin une syntaxe claire et précise pour libérer les ressources proprement et sans erreur…
Quel est le problème avec les ressources ???
Contrairement à la mémoire, les autres types de ressources ne sont pas gérées par le GC, et il faut donc les libérer explicitement quoi qu’il arrive. Le GC peut bien servir de garde-fou mais le caractère imprévisible de la libération de la mémoire devient ici problématique, car dans ce cas on ne peut pas maitriser le moment exact de la libération des ressources.
Il est donc obligatoire d’effectuer un appel explicite à la méthode de libération (close()
en général).
Pour cela il est impératif d’utiliser un pattern simple mais un peu verbeux (voir la FAQ pour plus de détail : Comment libérer proprement les ressources ?) :
// 1 - Création de la ressource
try {
// 2 - Utilisation de la ressource
} finally {
// 3 - Libération de la ressource
}
Cela permet de libérer la ressource même en cas d’exception, même si ce n’est pas exempt de défauts :
- La syntaxe est assez lourde, surtout que pour avoir quelque chose de propre, il faut utiliser un bloc try/finally par ressource, ce qui peut s’avérer rapidement très lourd…
- La ressource est déclarée avant le bloc try, et sa visibilité est donc plus grande que neccessaire, alors qu’on ne devrait pas avoir à l’utiliser une fois qu’elle est libérée.
- L’utilisation du bloc catch est délicate, car cela implique une duplication de la gestion des erreurs dans le bloc finally. Il est plus pratique d’utiliser un bloc try/catch qui engloberait tout notre code, ce qui alourdit encore un peut plus le tout…
- Si une exception remonte lors de la fermeture de la ressource dans le bloc finally, elle pourrait remplacée une autre exception généré dans le bloc try, ce qui pourrait cacher l’origine exact d’une problème. Il aurait bien des solutions à cela, mais cela rendrait le code encore plus lourd pour un cas relativement rare.
A titre d’exemple, voici à quoi devrait ressembler le code de copie d’un fichier :
public void copy(String sourceFile, String destFile) throws IOException {
InputStream input = new FileInputStream(sourceFile);
try {
OutputStream output = new FileOutputStream(destFile);
try {
byte[] buf = new byte[8192];
int len;
while ( (len=input.read(buf)) >= 0 )
output.write(buf, 0, len);
} finally {
output.close();
}
} finally {
input.close();
}
}
Automatic Resource Management
Java 7 proposera donc une nouvelle structure baptisée « try-with-resources » qui permettra donc de simplifier tout cela :
try ( /* 1 - Création des ressources */ ) {
// 2 - Utilisation des ressources
}
- La syntaxe est plus courte, mais permet surtout d’utiliser un seul et unique bloc try pour définir plusieurs ressources. Fini les multiples imbrications de bloc !
- Il n’y a plus de problème de visibilité car les ressources sont déclarées dans le try, et ne sont donc visible qu’à l’intérieur de ce dernier.
- On peut ajouter un bloc catch qui gèrera également les éventuelles exceptions des libérations de ressources.
- L’API de la classe
Throwable
a été modifiée afin d’intégrer la notion de « suppressed exception ». En clair cela permet d’ajouter une exception à une autre dans le cas où elle se retrouverait à cacher la précédente. Ainsi il n’y a plus de problème d’exception qui disparaitrait remplacé par une autre.
Du coup, notre méthode « copy » ressemblera à quelque chose du genre :
public void copy(String sourceFile, String destFile) throws IOException {
try (InputStream input = new FileInputStream(sourceFile);
OutputStream output = new FileOutputStream(destFile) ) {
byte[] buf = new byte[8192];
int len;
while ( (len=input.read(buf)) >= 0 )
output.write(buf, 0, len);
}
}
Bref on se contente de déclarer les ressources dans le try puis de les utiliser.
Clair, net et précis !
11 Commentaires + Ajouter un commentaire
Tutoriels
Discussions
- Définition exacte de @Override
- Classes, méthodes private
- L'apparition du mot-clé const est-il prévu dans une version à venir du JDK?
- [REFLEXION] Connaitre toutes les classes qui implémentent une interface
- Possibilité d'accéder au type générique en runtime
- jre 1.5, tomcat 6.0 et multi processeurs
- [ fuite ] memoire
- Recuperation du nom des parametres
- Difference de performances Unix/Windows d'un programme?
Le stacktrace est similaire à celui qu’on a quand on a une exception source.
C’est à dire qu’on a le stacktrace de l’exception originale, suivi des stacktraces des exception « caché ».
Bref quelque chose comme cela :
C’est un peu plus compliqué que cela. Chaque ressource possède son propre bloc try/finally pour s’assurer qu’elle soit correctement libérée dans tous les cas.
La seule différence que je vois c’est le problème de signature. Mais en runtime ca change rien si j’ai bien suivi le billet sur le lancement transparent des « checked exception ».
On reçoit la première exception générée, dans l’ordre d’exécution.
Tu aurais un exemple de stacktrace généré ?
En faite j’utilise déjà pour mes développements personnels un mécanisme équivalent (sans la syntaxe ARM qui allège BEAUCOUP.
Non puisqu’elle ne font pas partie du bloc. De toute façons je vois pas vraiment d’intérêt à cela…
J’en vois pas nécessairement non plus, mais
C’est quand même assez verbeux tout ca ! Ce qui implique des risques d’erreurs et de mauvaises utilisations. Déjà que dans le try/finally pour la libération est rarement correct…
Je vois pas la source d’erreur … Le but était surtout d’être plus souple sur les clauses finally qui ne se cantonnent pas toujours à appeler une seule méthode d’un objet ressource (la ressource à fermer peut être this par exemple) et la proposition impose l’implémentation d’une méthode dont le nom n’est pas forcément parlant (par exemple un lock)
C’est un peu plus compliqué que cela. Chaque ressource possède son propre bloc try/finally pour s’assurer qu’elle soit correctement libérée dans tous les cas.
Pour une ressource unique :
On devrait avoir quelque chose comme cela :
Bref le pattern du try/finally de la FAQ Java, couplé à une gestion des exceptions sur la fermeture afin d’éviter le « problème » des exceptions pouvant être masquées par d’autres…
Si tu as plusieurs ressources, cela doit être empilé en utilisant un try/finally par ressource… ce qui devient vite indigeste à lire !!!
Mais il ne faut pas trop focalisé là dessus. Il faut juste penser que les ressources seront correctement libérées quoi qu’il arrive
Oui !
En fait lorsque tu utilises un try-with-ressource avec un catch/finally :
Cela équivaut en fait à utiliser un try/catch/finally contenant un try-with-ressource :
Donc toute erreurs lors de l’initialisation sera bien catché
On reçoit la première exception générée, dans l’ordre d’exécution.
Non puisqu’elle ne font pas partie du bloc. De toute façons je vois pas vraiment d’intérêt à cela…
C’est quand même assez verbeux tout ca ! Ce qui implique des risques d’erreurs et de mauvaises utilisations. Déjà que dans le try/finally pour la libération est rarement correct…
a++
Avec cette nouvelle syntaxe, les deux écritures sont elles équivalentes ? (Hormis la clause « throws » ; mais si j’ai bien suivis c’est juste un problème « compile-time »
Mes principales question sont :
Le support du try-with-resources dans JDBC 4.1 a été intégré dans le JDK7 : http://blogs.sun.com/darcy/entry/project_coin_jdbc_4_1
a++
Hello,
Je vais être original
Très intéressant comme article.. Ca permettra d’avoir du code plus concis et clair.
Merci pour cette information.
@++
Merci pour cet article très intéressant. Je testerais bien ça (avec les autres nouveautés de Java 7)… mais ayant déjà Git sur le feu, c’est comme toujours un problème de temps !
J’essayerai quand même de trouver un moment.
Closeable est trop restrictif car il impose un throws IOException. Du coup ils ont créé une nouvelle interface AutoCloseable qui définie une méthode close() throws Exception, ce qui permet de l’utiliser pour n’importe quel type d’exception (on peut spécialiser le type de l’exception via l’héritage).
Bien sûr désormais Closeable étend AutoCloseable afin que l’on puisse profiter de cela sur un très grand nombre de type de l’API.
Le gros morceau restant est JDBC, dont les ressources utiliseront AutoCloseable dans JDBC 4.1, mais dont l’intégration dans Java 7 n’est pas encore sûr…
Après il reste quelque classe qui pourrait utiliser cela, mais dont l’API ne possède pas de méthode close().
Là ca va se discuter au cas par cas selon les éventuels impacts sur la compatibilité…
Mais dans tous les cas on pourra utiliser un type wrapper…
a++
[EDIT] Oui pour l’IDE c’est normal puisqu’il utilise son propre parseur.
Je viens de tester sur ma machine, ça marche nickel Juste mon IDE qui est pas encore très content de la syntaxe, mais bon, ça viendra
Ca a l’air bien cool
Ca marche pour quel type de resources ? Tout ce qui implémente Closeable ?
Cool ça va bien alléger la syntaxe tout ça