Frage

Ich habe sieben Testfälle geschrieben, um das Verhalten des zu verstehen finally Block.Welche Logik steckt hinter dem Wie? finally funktioniert?

package core;

public class Test {
    public static void main(String[] args) {
        new Test().testFinally();
    }

    public void testFinally() {
        System.out.println("One = " + tryOne());
        System.out.println("Two = " + tryTwo());
        System.out.println("Three = " + tryThree());
        System.out.println("Four = " + tryFour());
        System.out.println("Five = " + tryFive());
        System.out.println("Six = " + trySix());
        System.out.println("Seven = " + trySeven());
    }

    protected StringBuilder tryOne() {
        StringBuilder builder = new StringBuilder();
        try {
            builder.append("Cool");
            return builder.append("Return");
        }
        finally {
            builder = null;
        }
    }

    protected String tryTwo() {
        String builder = "Cool";
        try {
            return builder += "Return";
        }
        finally {
            builder = null;
        }
    }

    protected int tryThree() {
        int builder = 99;
        try {
            return builder += 1;
        }
        finally {
            builder = 0;
        }
    }

    protected StringBuilder tryFour() {
        StringBuilder builder = new StringBuilder();
        try {
            builder.append("Cool");
            return builder.append("Return");
        }
        finally {
            builder.append("+1");
        }
    }

    protected int tryFive() {
        int count = 0;
        try {
            count = 99;
        }
        finally {
            count++;
        }
        return count;
    }

    protected int trySix() {
        int count = 0;
        try {
            count = 99;
        }
        finally {
            count = 1;
        }
        return count;
    }

    protected int trySeven() {
        int count = 0;
        try {
            count = 99;
            return count;
        }
        finally {
            count++;
        }
    }
}

Warum builder = null funktioniert nicht?

Warum tut builder.append("+1") Arbeit während count++( In Versuchen Sie es mit Seven()) tut nicht arbeiten?

War es hilfreich?

Lösung

Sobald Sie die Rückgabe durchgeführt haben, besteht die einzige Möglichkeit, dies zu umgehen, darin, eine weitere Rückgabe durchzuführen (wie unter beschrieben). Rückkehr von einem „finally“-Block in Java, das ist fast immer eine schlechte Idee) oder auf andere Weise abrupt abschließen.Ihre Tests kehren nie endgültig zurück.

JLS §14.1 definiert einen abrupten Abschluss.Eine der abrupten Vervollständigungsarten ist die Rückgabe.Die Try-Blöcke in 1,2,3,4 und 7 werden aufgrund von Rückgaben abrupt beendet.Wie in § erklärt14.20.2, Wenn der Try-Block aus einem Grund R außer einem Wurf abrupt abgeschlossen wird, wird der Final-Block sofort ausgeführt.

Wenn der „finally“-Block normal abgeschlossen wird (was unter anderem bedeutet, dass es keine Rückgabe gibt), „wird die try-Anweisung aus Grund R abrupt abgeschlossen.“Mit anderen Worten: Die durch den Versuch initiierte Rückgabe bleibt intakt;Dies gilt für alle Ihre Tests.Wenn Sie von der Schlussfolgerung zurückkehren, "wird die Aussageaussage aus Gründen s abrupt abgeschlossen (und die Vernunft R wird verworfen)." (S hier ist die neue übergeordnete Rückkehr).

Wenn Sie also in tryOne Folgendes getan haben:

finally {
            builder = null;
            return builder;
        }

Diese neue Rückgabe S würde die ursprüngliche Rückgabe R überschreiben.

Für builder.append("+1") In tryFour, Beachten Sie, dass StringBuilder veränderbar ist, sodass Sie immer noch einen Verweis auf dasselbe Objekt zurückgeben, das im Versuch angegeben wurde.Du machst nur eine Last-Minute-Mutation.

tryFive Und trySix sind unkompliziert.Da es im try kein Return gibt, werden try und finally beide normal abgeschlossen und die Ausführung erfolgt auf die gleiche Weise, als ob es kein try-finally gäbe.

Andere Tipps

Beginnen wir mit einem Anwendungsfall, den Sie häufiger sehen werden: Sie verfügen über eine Ressource, die Sie benötigen muss schließen, um ein Auslaufen zu vermeiden.

public void deleteRows(Connection conn) throws SQLException {
    Statement statement = conn.createStatement();
    try {
        statement.execute("DELETE * FROM foo");
    } finally {
        statement.close();
    }
}

In diesem Fall müssen wir die Anweisung schließen, wenn wir fertig sind, damit keine Datenbankressourcen verloren gehen.Dadurch wird sichergestellt, dass wir im Falle einer ausgelösten Ausnahme immer unsere Anweisung schließen, bevor die Funktion beendet wird.

versuchen { ...} Endlich { ...}-Blöcke sollen sicherstellen, dass immer etwas ausgeführt wird, wenn die Methode beendet wird.Dies ist am nützlichsten für Ausnahmefälle.Wenn Sie feststellen, dass Sie so etwas tun:

public String thisShouldBeRefactored(List<String> foo) {
    try { 
        if(foo == null) {
            return null;
        } else if(foo.length == 1) {
            return foo.get(0);
        } else {
            return foo.get(1);
        }
    } finally {
        System.out.println("Exiting function!");
    }
}

Du verwendest es schließlich nicht wirklich richtig.Dies führt zu einer Leistungseinbuße.Bleiben Sie bei der Verwendung, wenn Sie Ausnahmefälle haben, die Sie bereinigen müssen.Versuchen Sie, das Obige wie folgt umzugestalten:

public String thisShouldBeRefactored(List<String> foo) {
    final String result;

    if(foo == null) {
        result = null;
    } else if(foo.length == 1) {
        result = foo.get(0);
    } else {
        result = foo.get(1);
    }

    System.out.println("Exiting function!");

    return result;
}

Der final-Block wird ausgeführt, wenn Sie den try-Block verlassen.Die „return“-Anweisung bewirkt zwei Dinge: Erstens legt sie den Rückgabewert der Funktion fest und zweitens beendet sie die Funktion.Normalerweise würde dies wie eine atomare Operation aussehen, aber innerhalb eines Try-Blocks führt es dazu, dass der Final-Block ausgeführt wird, nachdem der Rückgabewert festgelegt wurde und bevor die Funktion beendet wird.

Rückführung:

  1. Rückgabewert zuweisen
  2. Laufen Sie schließlich blockiert
  3. Exit-Funktion

Beispiel eins (primitiv):

int count = 1;//Assign local primitive count to 1
try{
  return count; //Assign primitive return value to count (1)
}finally{
  count++ //Updates count but not return value
}

Beispiel zwei (Referenz):

StringBuilder sb = new StringBuilder();//Assign sb a new StringBuilder
try{
    return sb;//return a reference to StringBuilder
}finally{
    sb.append("hello");//modifies the returned StringBuilder
}

Beispiel drei (Referenz):

   StringBuilder sb = new StringBuilder();//Assign sb a new StringBuilder
   try{
      return sb;//return a reference to StringBuilder
   }finally{
      sb = null;//Update local reference sb not return value
   }

Beispiel vier (Rückgabe):

   int count = 1;   //assign count
   try{
      return count; //return current value of count (1)
   }finally{
      count++;      //update count to two but not return value
      return count; //return current value of count (2) 
                    //replaces old return value and exits the finally block
   }

builder = null Und builder.append("+1") Sind Arbeiten.Es ist nur so, dass sie keinen Einfluss darauf haben, was Sie zurückgeben.Die Funktion gibt zurück, was die return Aussage hat, unabhängig davon, was danach passiert.

Der Grund für den Unterschied liegt darin, dass builder wird per Referenz übergeben. builder=null ändert die lokal Kopie von builder. builder.append("+1") wirkt sich auf die vom Elternteil gehaltene Kopie aus.

Warum builder = null funktioniert nicht?
Weil Sie die lokale Referenz auf Null setzen, wodurch sich der Inhalt des Speichers nicht ändert.Es funktioniert also. Wenn Sie versuchen, nach der endgültigen Blockierung auf den Builder zuzugreifen, erhalten Sie null.
Warum builder.append("+1") work?
Da Sie den Inhalt des Speichers mithilfe der Referenz ändern, sollte es funktionieren.
Warum count++ funktioniert nicht in testFive()?
Bei mir funktioniert es gut.Es gibt wie erwartet 100 aus.

Überlegen Sie, was der Compiler tatsächlich für die Return-Anweisung tut, zum Beispiel in tryOne():es kopiert einen Verweis auf builder zurück zur Umgebung der aufrufenden Funktion.Nachdem dies erledigt ist, aber bevor die Kontrolle wieder an die aufrufende Funktion übergeht, wird der „finally“-Block ausgeführt.In der Praxis haben Sie also eher etwas Ähnliches:

protected StringBuilder tryOne() {
    StringBuilder builder = new StringBuilder();
    try {
        builder.append("Cool");
        builder.append("Return");
        StringBuilder temp = builder;
        return temp;
    } finally {
        builder = null;
    }
}

Oder im Hinblick auf die Reihenfolge, in der Anweisungen tatsächlich ausgeführt werden (wobei mögliche Ausnahmen natürlich ignoriert werden), sieht es eher so aus:

protected StringBuilder tryOne() {
    StringBuilder builder = new StringBuilder();
    builder.append("Cool");
    builder.append("Return");
    StringBuilder temp = builder;
    builder = null;
    return temp;
}

Also Einstellung builder = null läuft, aber es macht einfach nichts Nützliches.Allerdings läuft builder.append("something") Wille haben einen sichtbaren Effekt, da sowohl temp als auch builder auf dasselbe (veränderbare) Objekt verweisen.

Ebenso ist das, was in trySeven() wirklich passiert, eher so:

protected int trySeven() {
    int count = 0;
    count = 99;
    int temp = count;
    count++;
    return temp;
}

Da es sich in diesem Fall um ein int handelt, sind die Kopien unabhängig, sodass das Erhöhen einer Kopie keinen Einfluss auf die andere hat.

Abgesehen davon bleibt die Tatsache bestehen, dass das Einfügen von Return-Anweisungen in einen try-finally-Block eindeutig verwirrend ist. Wenn Sie also in dieser Angelegenheit irgendeine Wahl haben, ist es besser, die Dinge so umzuschreiben, dass alle Ihre Return-Anweisungen liegen außerhalb aller Try-finally-Blöcke.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top