1. Ressources externes

Lorsqu'un programme Java s'exécute, il interagit parfois avec des entités extérieures à la machine Java. Par exemple, avec des fichiers sur disque. Ces entités sont généralement appelées ressources externes. Les ressources internes sont les objets créés à l'intérieur de la machine Java.

En règle générale, l'interaction suit ce schéma :

Instruction Try-with-resources

Ressources de suivi

Le système d'exploitation suit rigoureusement les ressources disponibles et contrôle également l'accès partagé à celles-ci à partir de différents programmes. Par exemple, si un programme modifie un fichier, un autre programme ne peut pas modifier (ou supprimer) ce fichier. Ce principe n'est pas limité aux fichiers, mais ils fournissent l'exemple le plus facilement compréhensible.

Le système d'exploitation possède des fonctions (API) qui permettent à un programme d'acquérir et/ou de libérer des ressources. Si une ressource est occupée, seul le programme qui l'a acquise peut l'utiliser. Si une ressource est gratuite, alors n'importe quel programme peut l'acquérir.

Imaginez que votre bureau partage des tasses à café. Si quelqu'un prend une tasse, les autres ne peuvent plus la prendre. Mais une fois la tasse utilisée, lavée et remise à sa place, n'importe qui peut la reprendre. La situation avec des sièges dans un bus ou un métro est la même. Si une place est libre, n'importe qui peut la prendre. Si un siège est occupé, il est alors contrôlé par la personne qui l'a occupé.

Acquisition de ressources externes .

Chaque fois que votre programme Java commence à travailler avec un fichier sur disque, la machine Java demande au système d'exploitation un accès exclusif à celui-ci. Si la ressource est libre, alors la machine Java l'acquiert.

Mais une fois que vous avez fini de travailler avec le fichier, cette ressource (fichier) doit être libérée, c'est-à-dire que vous devez notifier au système d'exploitation que vous n'en avez plus besoin. Si vous ne le faites pas, la ressource continuera d'être détenue par votre programme.

Le système d'exploitation maintient une liste des ressources occupées par chaque programme en cours d'exécution. Si votre programme dépasse la limite de ressources assignée, le système d'exploitation ne vous donnera plus de nouvelles ressources.

La bonne nouvelle est que si votre programme se termine, toutes les ressources sont automatiquement libérées (le système d'exploitation le fait lui-même).

La mauvaise nouvelle est que si vous écrivez une application serveur (et de nombreuses applications serveur sont écrites en Java), votre serveur doit pouvoir fonctionner pendant des jours, des semaines et des mois sans s'arrêter. Et si vous ouvrez 100 fichiers par jour et que vous ne les fermez pas, dans quelques semaines, votre application atteindra sa limite de ressources et se bloquera. C'est loin de mois de travail stable.


2. close()méthode

Les classes qui utilisent des ressources externes ont une méthode spéciale pour les libérer : close().

Ci-dessous, nous donnons un exemple de programme qui écrit quelque chose dans un fichier puis ferme le fichier une fois terminé, c'est-à-dire qu'il libère les ressources du système d'exploitation. Cela ressemble à ceci :

Code Note
String path = "c:\\projects\\log.txt";
FileOutputStream output = new FileOutputStream(path);
output.write(1);
output.close();
Le chemin d'accès au fichier.
Obtenir l'objet fichier : acquérir la ressource.
Écrire dans le fichier
Fermer le fichier - libérer la ressource

Après avoir travaillé avec un fichier (ou d'autres ressources externes), vous devez appeler la close()méthode sur l'objet lié à la ressource externe.

Des exceptions

Tout semble simple. Mais des exceptions peuvent se produire lors de l'exécution d'un programme et la ressource externe ne sera pas libérée. Et c'est très mauvais.

Pour nous assurer que la close()méthode est toujours appelée, nous devons envelopper notre code dans un bloc try- catch- finallyet ajouter la close()méthode au finallybloc. Cela ressemblera à ceci :

try
{
   FileOutputStream output = new FileOutputStream(path);
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

Ce code ne compilera pas, car la outputvariable est déclarée à l'intérieur du try {}bloc, et n'est donc pas visible dans le finallybloc.

Réparons-le :

FileOutputStream output = new FileOutputStream(path);

try
{
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

Ce n'est pas grave, mais cela ne fonctionnera pas si une erreur se produit lors de la création de l' FileOutputStreamobjet, et cela peut se produire assez facilement.

Réparons-le :

FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

Il y a encore quelques critiques. Tout d'abord, si une erreur se produit lors de la création de l' FileOutputStreamobjet, alors la outputvariable sera nulle. Cette possibilité doit être prise en compte dans le finallybloc.

Deuxièmement, la close()méthode est toujours appelée dans le finallybloc, ce qui signifie qu'elle n'est pas nécessaire dans le trybloc. Le code final ressemblera à ceci :

FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   if (output != null)
      output.close();
}

Même si nous ne considérons pas le catchbloc, qui peut être omis, alors nos 3 lignes de code deviennent 10. Mais nous venons d'ouvrir le fichier et d'écrire 1. Un peu lourd, vous ne trouvez pas ?


3. try-avec-ressources

Et ici, les créateurs de Java ont décidé de nous saupoudrer de sucre syntaxique. À partir de sa 7e version, Java a une nouvelle tryinstruction -with-resources.

Il a été créé précisément pour résoudre le problème de l'appel obligatoire à la close()méthode. Le cas général semble assez simple :

try (ClassName name = new ClassName())
{
     Code that works with the name variable
}

Ceci est une autre variante de la try déclaration . Vous devez ajouter des parenthèses après le trymot-clé, puis créer des objets avec des ressources externes à l'intérieur des parenthèses. Pour chaque objet entre parenthèses, le compilateur ajoute une finallysection et un appel à la close()méthode.

Ci-dessous deux exemples équivalents :

Code long Code avec try-with-resources
FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
}
finally
{
   if (output != null)
   output.close();
}
try(FileOutputStream output = new FileOutputStream(path))
{
   output.write(1);
}

Le code utilisant try-with-resources est beaucoup plus court et plus facile à lire. Et moins nous avons de code, moins nous avons de chances de faire une faute de frappe ou une autre erreur.

Au fait, nous pouvons ajouter catchet finallybloquer à l' tryinstruction -with-resources. Ou vous ne pouvez pas les ajouter s'ils ne sont pas nécessaires.



4. Plusieurs variables en même temps

Au fait, vous pouvez souvent rencontrer une situation où vous devez ouvrir plusieurs fichiers en même temps. Supposons que vous copiez un fichier, vous avez donc besoin de deux objets : le fichier à partir duquel vous copiez des données et le fichier dans lequel vous copiez des données.

Dans ce cas, l' tryinstruction -with-resources vous permet d'y créer un mais plusieurs objets. Le code qui crée les objets doit être séparé par des points-virgules. Voici l'apparence générale de cette déclaration :

try (ClassName name = new ClassName(); ClassName2 name2 = new ClassName2())
{
   Code that works with the name and name2 variables
}

Exemple de copie de fichiers :

Code long Petit code
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

FileInputStream input = null;
FileOutputStream output = null;

try
{
   input = new FileInputStream(src);
   output = new FileOutputStream(dest);

   byte[] buffer = input.readAllBytes();
   output.write(buffer);
}
finally
{
   if (input != null)
      input.close();
   if (output != null)
      output.close();
}
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileInputStream input = new FileInputStream(src);

FileOutputStream output = new FileOutputStream(dest))
{
   byte[] buffer = input.readAllBytes();
   output.write(buffer);
}

Eh bien, que pouvons-nous dire ici? try-avec-ressources est une chose merveilleuse !


5. AutoCloseableinterfaces

Mais ce n'est pas tout. Le lecteur attentif commencera immédiatement à rechercher les pièges qui limitent la manière dont cette déclaration peut être appliquée.

Mais comment tryfonctionne l'instruction -with-resources si la classe n'a pas de close()méthode ? Eh bien, supposons que rien ne sera appelé. Pas de méthode, pas de problème.

Mais comment tryfonctionne l'instruction -with-resources si la classe a plusieurs close()méthodes ? Et ils ont besoin qu'on leur passe des arguments ? Et la classe n'a pas de close()méthode sans paramètres ?

J'espère que vous vous êtes vraiment posé ces questions, et peut-être d'autres encore.

Pour éviter de tels problèmes, les créateurs de Java ont créé une interface spéciale appelée AutoCloseable, qui n'a qu'une seule méthode — close(), qui n'a pas de paramètres.

Ils ont également ajouté la restriction selon laquelle seuls les objets des classes qui implémententAutoCloseable peuvent être déclarés comme ressources dans une tryinstruction -with-resources. Par conséquent, ces objets auront toujours une close()méthode sans paramètres.

Au fait, pensez-vous qu'il soit possible pour une tryinstruction -with-resources de déclarer comme ressource un objet dont la classe a sa propre close()méthode sans paramètre mais qui ne l'implémente pas AutoCloseable?

La mauvaise nouvelle : la bonne réponse est non — les classes doivent implémenter l' AutoCloseableinterface.

La bonne nouvelle : Java a beaucoup de classes qui implémentent cette interface, il est donc très probable que tout fonctionnera comme il se doit.