Maison >Java >javaDidacticiel >Introduction détaillée à l'exemple de code d'essai avec ressource en Java

Introduction détaillée à l'exemple de code d'essai avec ressource en Java

黄舟
黄舟original
2017-03-21 11:08:141496parcourir

Contexte

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.

Pratique pratique

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.

Principe

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.

Protection des exceptions

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 sendDatalancer des exceptionsclose 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");
    }
}
Modifier la méthode principale :

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();
            }
        }
    }
}
Après l'avoir exécutée, nous avons trouvé :

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)
	......
D'accord, voici le problème , puisque nous ne pouvons que lancer une exception, donc ce que vous voyez au niveau supérieur est la dernière exception levée - c'est-à-dire le

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
Nous pouvons voir qu'il y a une invite

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

Une petite question

Dans le processus d'utilisation de try-with-resource, vous devez comprendre la logique d'implémentation interne de la méthode

de la ressource. Sinon, des ressources pourraient encore être divulguées. close

Par exemple, un grand nombre de

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!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn