1. Externe Ressourcen

Während ein Java-Programm ausgeführt wird, interagiert es manchmal mit Entitäten außerhalb der Java-Maschine. Zum Beispiel mit Dateien auf der Festplatte. Diese Entitäten werden üblicherweise als externe Ressourcen bezeichnet. Interne Ressourcen sind die in der Java-Maschine erstellten Objekte.

Typischerweise folgt die Interaktion diesem Schema:

Try-with-resources-Anweisung

Ressourcen verfolgen

Das Betriebssystem überwacht strikt die verfügbaren Ressourcen und kontrolliert auch den gemeinsamen Zugriff darauf von verschiedenen Programmen aus. Wenn beispielsweise ein Programm eine Datei ändert, kann ein anderes Programm diese Datei nicht ändern (oder löschen). Dieses Prinzip ist nicht auf Dateien beschränkt, aber sie bieten das am besten verständliche Beispiel.

Das Betriebssystem verfügt über Funktionen (APIs), die es einem Programm ermöglichen, Ressourcen zu erwerben und/oder freizugeben. Wenn eine Ressource ausgelastet ist, kann nur das Programm, das sie erworben hat, damit arbeiten. Wenn eine Ressource frei ist, kann sie von jedem Programm erworben werden.

Stellen Sie sich vor, dass in Ihrem Büro Kaffeetassen geteilt werden. Wenn jemand einen Becher nimmt, können andere ihn nicht mehr nehmen. Aber sobald der Becher benutzt, gewaschen und wieder an seinen Platz gestellt wird, kann ihn jeder wieder mitnehmen. Ähnlich verhält es sich mit den Sitzplätzen im Bus oder in der U-Bahn. Wenn ein Platz frei ist, kann ihn jeder nehmen. Wenn ein Sitzplatz belegt ist, wird er von der Person kontrolliert, die ihn eingenommen hat.

Akquise externer Ressourcen .

Jedes Mal, wenn Ihr Java-Programm beginnt, mit einer Datei auf der Festplatte zu arbeiten, bittet die Java-Maschine das Betriebssystem um exklusiven Zugriff darauf. Wenn die Ressource frei ist, erwirbt sie die Java-Maschine.

Nachdem Sie jedoch mit der Arbeit an der Datei fertig sind, muss diese Ressource (Datei) freigegeben werden, dh Sie müssen dem Betriebssystem mitteilen, dass Sie sie nicht mehr benötigen. Wenn Sie dies nicht tun, wird die Ressource weiterhin von Ihrem Programm gehalten.

Das Betriebssystem führt eine Liste der von jedem laufenden Programm belegten Ressourcen. Wenn Ihr Programm das zugewiesene Ressourcenlimit überschreitet, stellt Ihnen das Betriebssystem keine neuen Ressourcen mehr zur Verfügung.

Die gute Nachricht ist, dass beim Beenden Ihres Programms automatisch alle Ressourcen freigegeben werden (dies geschieht durch das Betriebssystem selbst).

Die schlechte Nachricht ist: Wenn Sie eine Serveranwendung schreiben (und viele Serveranwendungen sind in Java geschrieben), muss Ihr Server tage-, wochen- und monatelang ohne Unterbrechung laufen können. Und wenn Sie täglich 100 Dateien öffnen und nicht schließen, erreicht Ihre Anwendung in ein paar Wochen ihre Ressourcengrenze und stürzt ab. Das ist weit weniger als Monate stabiler Arbeit.


2. close()Methode

Klassen, die externe Ressourcen verwenden, verfügen über eine spezielle Methode, um diese freizugeben: close().

Im Folgenden stellen wir ein Beispiel für ein Programm vor, das etwas in eine Datei schreibt und die Datei dann schließt, wenn es fertig ist, d. h. es gibt Ressourcen des Betriebssystems frei. Es sieht ungefähr so ​​aus:

Code Notiz
String path = "c:\\projects\\log.txt";
FileOutputStream output = new FileOutputStream(path);
output.write(1);
output.close();
Der Pfad zur Datei.
Holen Sie sich das Dateiobjekt: Erwerben Sie die Ressource.
In die Datei schreiben.
Die Datei schließen – die Ressource freigeben

Nachdem Sie mit einer Datei (oder anderen externen Ressourcen) gearbeitet haben, müssen Sie die close()Methode für das mit der externen Ressource verknüpfte Objekt aufrufen.

Ausnahmen

Es scheint alles einfach zu sein. Bei der Ausführung eines Programms können jedoch Ausnahmen auftreten und die externe Ressource wird nicht freigegeben. Und das ist sehr schlimm.

Um sicherzustellen , dass die close()Methode immer aufgerufen wird, müssen wir unseren Code in einen --Block einschließen tryund die Methode zum Block hinzufügen. Es wird ungefähr so ​​aussehen:catchfinallyclose()finally

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

Dieser Code lässt sich nicht kompilieren, da die outputVariable innerhalb des Blocks deklariert wird try {}und daher im finallyBlock nicht sichtbar ist.

Beheben wir das Problem:

FileOutputStream output = new FileOutputStream(path);

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

Es ist in Ordnung, aber es funktioniert nicht, wenn beim Erstellen des FileOutputStreamObjekts ein Fehler auftritt, und das kann ganz einfach passieren.

Beheben wir das Problem:

FileOutputStream output = null;

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

Es gibt noch ein paar Kritikpunkte. Wenn beim Erstellen des FileOutputStreamObjekts ein Fehler auftritt, outputist die Variable zunächst null. Diese Möglichkeit muss im Block berücksichtigt werden finally.

Zweitens close()wird die Methode immer im finallyBlock aufgerufen, was bedeutet, dass sie im tryBlock nicht erforderlich ist. Der endgültige Code sieht folgendermaßen aus:

FileOutputStream output = null;

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

Selbst wenn wir den Block nicht berücksichtigen catch, der weggelassen werden kann, werden aus unseren drei Codezeilen zehn. Aber wir haben im Grunde nur die Datei geöffnet und 1 geschrieben. Ein bisschen umständlich, finden Sie nicht?


3. try-mit-Ressourcen

Und hier haben die Entwickler von Java beschlossen, uns mit etwas syntaktischem Zucker zu bestreuen. Ab der 7. Version verfügt Java über eine neue try-with-resources-Anweisung.

Es wurde genau zur Lösung des Problems mit dem obligatorischen Aufruf der close()Methode erstellt. Der allgemeine Fall sieht ganz einfach aus:

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

Dies ist eine weitere Variation der try Aussage . Sie müssen nach dem trySchlüsselwort Klammern hinzufügen und dann Objekte mit externen Ressourcen innerhalb der Klammern erstellen. Für jedes Objekt in den Klammern fügt der Compiler einen finallyAbschnitt und einen Aufruf der close()Methode hinzu.

Nachfolgend finden Sie zwei gleichwertige Beispiele:

Langer Code Code mit Try-with-Ressourcen
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);
}

Der Code, der try-with-resources verwendet, ist viel kürzer und einfacher zu lesen. Und je weniger Code wir haben, desto geringer ist die Wahrscheinlichkeit, dass wir einen Tippfehler oder einen anderen Fehler machen.

Übrigens können wir der -with-resources-Anweisung catchauch Blöcke hinzufügen . Oder Sie können sie nicht hinzufügen, wenn sie nicht benötigt werden.finallytry



4. Mehrere Variablen gleichzeitig

Übrigens kommt es oft vor, dass Sie mehrere Dateien gleichzeitig öffnen müssen. Nehmen wir an, Sie kopieren eine Datei und benötigen daher zwei Objekte: die Datei, aus der Sie Daten kopieren, und die Datei, in die Sie Daten kopieren.

In diesem Fall trykönnen Sie mit der Anweisung -with-resources ein, aber mehrere Objekte darin erstellen. Der Code, der die Objekte erstellt, muss durch Semikolons getrennt werden. Hier ist das allgemeine Erscheinungsbild dieser Aussage:

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

Beispiel für das Kopieren von Dateien:

Langer Code Kurzcode
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);
}

Nun, was können wir hier sagen? try-with-resources ist eine wunderbare Sache!


5. AutoCloseableSchnittstelle

Aber das ist nicht alles. Der aufmerksame Leser wird sofort nach Fallstricken suchen, die die Anwendung dieser Aussage einschränken.

Aber wie tryfunktioniert die Anweisung -with-resources, wenn die Klasse keine close()Methode hat? Angenommen, es wird nichts aufgerufen. Keine Methode, kein Problem.

Aber wie tryfunktioniert die -with-resources-Anweisung, wenn die Klasse mehrere close()Methoden hat? Und sie brauchen Argumente, die ihnen vermittelt werden? Und die Klasse hat keine close()Methode ohne Parameter?

Ich hoffe, Sie haben sich diese und vielleicht noch andere Fragen wirklich gestellt.

Um solche Probleme zu vermeiden, haben die Java-Entwickler eine spezielle Schnittstelle namens entwickelt AutoCloseable, die nur eine Methode hat – close()die keine Parameter hat.

Sie haben außerdem die Einschränkung hinzugefügt, dass nur Objekte von Klassen, die implementieren,AutoCloseable in einer try-with-resources-Anweisung als Ressourcen deklariert werden können. Daher verfügen solche Objekte immer über eine close()Methode ohne Parameter.

Halten Sie es übrigens für möglich, dass eine try-with-resources-Anweisung ein Objekt als Ressource deklariert, dessen Klasse über eine eigene close()Methode ohne Parameter verfügt, die jedoch nicht implementiert wird AutoCloseable?

Die schlechte Nachricht: Die richtige Antwort ist nein – die Klassen müssen die AutoCloseableSchnittstelle implementieren.

Die gute Nachricht: Java verfügt über viele Klassen, die diese Schnittstelle implementieren, daher ist es sehr wahrscheinlich, dass alles so funktioniert, wie es sollte.