Comme nous le savons tous, toutes les ressources système ouvertes, telles que les flux, les fichiers ou les connexions Socket, doivent être fermées manuellement par les développeurs, sinon, à mesure que le programme continue de s'exécuter, les fuites de ressources s'accumuleront. . Accident de production majeur.
Dans le monde de Java, il existe un kung-fu appelé enfin, qui peut garantir que lorsque vous devenez fou en pratiquant les arts martiaux, vous pouvez toujours effectuer quelques opérations d'auto-sauvetage. Dans les temps anciens, le code qui gérait la fermeture des ressources était généralement écrit en blocs finally. Cependant, si vous ouvrez plusieurs ressources en même temps, un scénario cauchemardesque se produira :
public class Demo { public static void main(String[] args) { BufferedInputStream bin = null; BufferedOutputStream bout = null; try { bin = new BufferedInputStream(new FileInputStream(new File("test.txt"))); bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt"))); int b; while ((b = bin.read()) != -1) { bout.write(b); } } catch (IOException e) { e.printStackTrace(); } finally { if (bin != null) { try { bin.close(); } catch (IOException e) { e.printStackTrace(); } finally { if (bout != null) { try { bout.close(); } catch (IOException e) { e.printStackTrace(); } } } } } } }
Oh mon Dieu ! ! ! Il y a plus de codes pour fermer des ressources que de codes professionnels ! ! ! En effet, nous devons non seulement fermer BufferedInputStream
, mais nous devons également nous assurer que si une exception se produit lors de la fermeture de BufferedInputStream
, BufferedOutputStream
doit également être fermé correctement. Nous devons donc finalement recourir à la méthode finale d'imbrication. On peut imaginer que plus les ressources seront ouvertes, plus l'imbrication sera finalement profonde ! ! !
Ce qui est encore plus dégoûtant, c'est que le Pythonprogrammeur a fait face à ce problème et a en fait souri et dit avec charme : « Nous n'avons pas du tout besoin de penser à ça ~ » :
Mais pas de panique, mon frère ! Nous pouvons utiliser le nouveau sucre syntaxique try-with-resource de Java 1.7 pour ouvrir des ressources, sans que les programmeurs aient besoin d'écrire des ressources pour fermer eux-mêmes le code. Maman n'a plus à s'inquiéter de la casse de mon écriture ! On utilise try-with-resource pour réécrire l'exemple précédent :
public class TryWithResource { public static void main(String[] args) { try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt"))); BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) { int b; while ((b = bin.read()) != -1) { bout.write(b); } } catch (IOException e) { e.printStackTrace(); } } }
N'est-ce pas très simple ? N'est-ce pas excitant ? Ne soyez plus méprisé par les programmeurs Python ! Eh bien, le principe de mise en œuvre et le mécanisme interne seront expliqués en détail ci-dessous.
Afin de coopérer avec try-with-resource, la ressource doit implémenter l'interface AutoClosable
. La classe d'implémentation de cette interface doit remplacer la méthode close
:
public class Connection implements AutoCloseable { public void sendData() { System.out.println("正在发送数据"); } @Override public void close() throws Exception { System.out.println("正在关闭连接"); } }
Classe appelante :
public class TryWithResource { public static void main(String[] args) { try (Connection conn = new Connection()) { conn.sendData(); } catch (Exception e) { e.printStackTrace(); } } }
Résultat de sortie après exécution :
正在发送数据 正在关闭连接
Résultat passé Nous pouvons voir que la méthode close est automatiquement appelée.
Alors comment ça se fait ? Je crois que vous qui êtes intelligent avez dû deviner qu'en fait, tout cela est causé par le maître du compilateur. Décompilons tout de suite le fichier de classe dans l'exemple :
public class TryWithResource { public TryWithResource() { } public static void main(String[] args) { try { Connection e = new Connection(); Throwable var2 = null; try { e.sendData(); } catch (Throwable var12) { var2 = var12; throw var12; } finally { if(e != null) { if(var2 != null) { try { e.close(); } catch (Throwable var11) { var2.addSuppressed(var11); } } else { e.close(); } } } } catch (Exception var14) { var14.printStackTrace(); } } }
Avez-vous vu qu'aux lignes 15 à 27, le compilateur a automatiquement généré le bloc final pour nous et a appelé la méthode close de la ressource qu'il contient, donc La méthode close dans l'exemple sera exécutée au moment de l'exécution.
Je crois que ceux d'entre vous qui font attention ont dû découvrir que le code que vous venez de décompiler (ligne 21) en a un de plus addSuppressed
que le code écrit dans les temps anciens. Afin de comprendre le but de ce code, modifions légèrement l'exemple de tout à l'heure : nous modifions le code tout à l'heure pour revenir à la manière de fermer manuellement les exceptions dans les temps anciens, et sendData
lancer des exceptions<🎜 dans le close
et méthodes > :
public class Connection implements AutoCloseable { public void sendData() throws Exception { throw new Exception("send data"); } @Override public void close() throws Exception { throw new MyException("close"); } }
public class TryWithResource { public static void main(String[] args) { try { test(); } catch (Exception e) { e.printStackTrace(); } } private static void test() throws Exception { Connection conn = null; try { conn = new Connection(); conn.sendData(); } finally { if (conn != null) { conn.close(); } } } }
basic.exception.MyException: close at basic.exception.Connection.close(Connection.java:10) at basic.exception.TryWithResource.test(TryWithResource.java:82) at basic.exception.TryWithResource.main(TryWithResource.java:7) ......
lancé par la méthode close
, et le MyException
lancé par sendData
est ignoré . C’est ce qu’on appelle le blindage des exceptions. En raison de la perte d'informations sur les exceptions, le masquage des exceptions peut rendre certains bogues extrêmement difficiles à trouver. Les programmeurs doivent travailler des heures supplémentaires pour trouver des bogues. Comment ne pas éliminer un tel cancer ! Heureusement, afin de résoudre ce problème, à partir de Java 1.7, les grands ont ajouté une nouvelle méthode Exception
à la classe Throwable
, qui prend en charge l'attachement d'une exception à une autre exception pour éviter le masquage des exceptions. Alors, dans quel format les informations sur les exceptions bloquées seront-elles affichées ? Exécutons à nouveau la méthode principale qui vient d'être enveloppée avec try-with-resource : addSuppressed
java.lang.Exception: send data at basic.exception.Connection.sendData(Connection.java:5) at basic.exception.TryWithResource.main(TryWithResource.java:14) ...... Suppressed: basic.exception.MyException: close at basic.exception.Connection.close(Connection.java:10) at basic.exception.TryWithResource.main(TryWithResource.java:15) ... 5 more
supplémentaire dans les informations sur l'exception, nous indiquant que cette exception se compose en fait de deux exceptions Composition, Suppressed
est une exception qui est supprimée. Félicitations! MyException
de la ressource. Sinon, des ressources pourraient encore être divulguées. close
modèles de décor sont utilisés dans Java BIO. Lorsque la méthode du décorateur est appelée, elle appelle essentiellement la méthode close
du flux enveloppé à l'intérieur du décorateur. Par exemple : close
public class TryWithResource { public static void main(String[] args) { try (FileInputStream fin = new FileInputStream(new File("input.txt")); GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(new File("out.txt")))) { byte[] buffer = new byte[4096]; int read; while ((read = fin.read(buffer)) != -1) { out.write(buffer, 0, read); } } catch (IOException e) { e.printStackTrace(); } } }
在上述代码中,我们从FileInputStream
中读取字节,并且写入到GZIPOutputStream
中。GZIPOutputStream
实际上是FileOutputStream
的装饰器。由于try-with-resource的特性,实际编译之后的代码会在后面带上finally代码块,并且在里面调用fin.close()方法和out.close()方法。我们再来看GZIPOutputStream
类的close方法:
public void close() throws IOException { if (!closed) { finish(); if (usesDefaultDeflater) def.end(); out.close(); closed = true; } }
我们可以看到,out变量实际上代表的是被装饰的FileOutputStream
类。在调用out变量的close
方法之前,GZIPOutputStream
还做了finish
操作,该操作还会继续往FileOutputStream
中写压缩信息,此时如果出现异常,则会out.close()
方法被略过,然而这个才是最底层的资源关闭方法。正确的做法是应该在try-with-resource中单独声明最底层的资源,保证对应的close
方法一定能够被调用。在刚才的例子中,我们需要单独声明每个FileInputStream
以及FileOutputStream
:
public class TryWithResource { public static void main(String[] args) { try (FileInputStream fin = new FileInputStream(new File("input.txt")); FileOutputStream fout = new FileOutputStream(new File("out.txt")); GZIPOutputStream out = new GZIPOutputStream(fout)) { byte[] buffer = new byte[4096]; int read; while ((read = fin.read(buffer)) != -1) { out.write(buffer, 0, read); } } catch (IOException e) { e.printStackTrace(); } } }
由于编译器会自动生成fout.close()
的代码,这样肯定能够保证真正的流被关闭。
怎么样,是不是很简单呢,如果学会了话
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!