Dlaczego zmiana zwracanej zmiennej w bloku finally nie zmienia zwracanej wartości?

Mam prostą klasę Javy, jak pokazano poniżej:

public class Test {

    private String s;

    public String foo() {
        try {
            s = "dev";
            return s;
        } 
        finally {
            s = "override variable s";
            System.out.println("Entry in finally Block");  
        }
    }

    public static void main(String[] xyz) {
        Test obj = new Test();
        System.out.println(obj.foo());
    }
}

A wyjście tego kodu jest takie:

Entry in finally Block
dev  

Dlaczego s nie jest nadpisany w bloku finally, ale kontroluje Wydruk?

Author: Dev, 2013-04-16

7 answers

Blok try kończy się wykonaniem instrukcji return, a wartość s w czasie wykonywania instrukcji return jest wartością zwracaną przez metodę. Fakt, że klauzula finally zmienia później wartość s (po zakończeniu instrukcji return) nie zmienia (w tym momencie) wartości zwracanej.

Zauważ, że powyższe dotyczy zmian samej wartości s w bloku finally, A Nie obiektu, do którego odwołuje się s. If s was a reference to zmienny obiekt (który String nie jest) i zawartość obiektu zostały zmienione w bloku finally, wtedy zmiany te będą widoczne w zwracanej wartości.

Szczegółowe zasady działania tego wszystkiego można znaleźć w sekcja 14.20.2 specyfikacji języka Java. Zwróć uwagę, że wykonanie instrukcji return liczy się jako nagłe zakończenie bloku try (sekcja rozpoczynająca "jeśli wykonanie bloku try zakończy się nagle z jakiegokolwiek innego powodu R...." dotyczy). Zobacz sekcja 14.17 JLS , dlaczego return jest nagłym zakończeniem bloku.

W drodze dalszych szczegółów: jeśli zarówno try blok, jak i finally Blok try-finally wypowiedzenie kończy się nagle z powodu wypowiedzeniareturn, wtedy stosuje się następujące zasady z §14.20.2:

Jeśli wykonanie try bloku zakończy się nagle z jakiegokolwiek innego powodu R [poza rzuceniem wyjątku], to finally blok zostanie wykonany, a następnie jest wybór:

  • Jeśli {[4] } Blok kończy się normalnie, to try polecenie kończy się nagle z powodu r.
  • Jeśli finally Blok kończy się nagle z powodu s, to polecenie try kończy się nagle z powodu s (a powód R jest odrzucany).

Wynikiem jest to, że instrukcja return w bloku finally określa zwracaną wartość całej instrukcji try-finally, a zwracana wartość z bloku try jest odrzucana. Podobny rzecz występuje w instrukcji try-catch-finally jeśli blok try rzuca wyjątek, jest on przechwytywany przez blok catch, a zarówno blok catch, jak i blok finally mają Instrukcje return.

 168
Author: Ted Hopp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55

Ponieważ wartość zwracana jest umieszczana na stosie przed wywołaniem finally.

 66
Author: Tordek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-16 07:12:59

Jeśli zajrzymy do kodu bajtowego, zauważymy, że JDK dokonał znaczącej optymalizacji, a foo() metoda wygląda następująco:

String tmp = null;
try {
    s = "dev"
    tmp = s;
    s = "override variable s";
    return tmp;
} catch (RuntimeException e){
    s = "override variable s";
    throw e;
}

And bytecode:

0:  ldc #7;         //loading String "dev"
2:  putstatic   #8; //storing it to a static variable
5:  getstatic   #8; //loading "dev" from a static variable
8:  astore_0        //storing "dev" to a temp variable
9:  ldc #9;         //loading String "override variable s"
11: putstatic   #8; //setting a static variable
14: aload_0         //loading a temp avariable
15: areturn         //returning it
16: astore_1
17: ldc #9;         //loading String "override variable s"
19: putstatic   #8; //setting a static variable
22: aload_1
23: athrow

Java zachowała ciąg "dev" przed zmianą przed powrotem. W rzeczywistości tutaj nie ma ostatecznie bloku w ogóle.

 32
Author: Mikhail,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-17 05:56:11

Są tu 2 rzeczy godne uwagi:

  • ciągi są niezmienne. Gdy ustawisz s na "nadpisuj zmienną s", ustawisz s, aby odnosiła się do inlined String, nie zmieniając wewnętrznego bufora znaków obiektu S, aby zmienić go na "nadpisuj zmienną s".
  • umieszczasz odniesienie do s na stosie, aby powrócić do kodu wywołującego. Następnie (gdy zostanie uruchomiony blok finally), zmiana referencji nie powinna nic zrobić dla zwracanej wartości już na stosie.
 22
Author: 0xCAFEBABE,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-16 15:08:23

Zmieniłem trochę Twój kod, żeby udowodnić sens Teda.

Jak widać na wyjściu {[2] } jest rzeczywiście zmieniony, ale po powrocie.

public class Test {

public String s;

public String foo() {

    try {
        s = "dev";
        return s;
    } finally {
        s = "override variable s";
        System.out.println("Entry in finally Block");

    }
}

public static void main(String[] xyz) {
    Test obj = new Test();
    System.out.println(obj.foo());
    System.out.println(obj.s);
}
}

Wyjście:

Entry in finally Block 
dev 
override variable s
 13
Author: Frank,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-05 22:32:27

Technicznie rzecz biorąc, return w bloku try nie będzie ignorowany, jeśli zdefiniowany jest blok finally, tylko jeśli ten ostatecznie blok zawiera również return.

Jest to wątpliwa decyzja projektowa, która prawdopodobnie była błędem z perspektywy czasu(podobnie jak odniesienia, które domyślnie są nullable/mutable i, według niektórych, sprawdzonych WYJĄTKÓW). Pod wieloma względami zachowanie to jest dokładnie zgodne z potocznym rozumieniem tego, co oznacza finally - " bez względu na to, co dzieje się wcześniej w bloku try , Zawsze uruchamiaj ten kod."Jeśli więc zwrócisz true z finally bloku, ogólny efekt musi być zawsze do return s, nie?

Ogólnie rzecz biorąc, rzadko jest to dobry idiom i powinieneś używać finally bloków do czyszczenia / zamykania zasobów, ale rzadko, jeśli w ogóle zwracasz z nich wartość.

 5
Author: Kiran Jujare,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-16 07:23:48

Spróbuj tak: jeśli chcesz wydrukować wartość nadpisania s.

finally {
    s = "override variable s";    
    System.out.println("Entry in finally Block");
    return s;
}
 0
Author: Achintya Jha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-16 07:15:34