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?
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], tofinally
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 polecenietry
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
.
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.
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.
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.
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
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ść.
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;
}
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