Döndürülen değişkeni bir final bloğunda değiştirmek neden dönüş değerini değiştirmiyor?


147

Aşağıda gösterildiği gibi basit bir Java sınıfım var:

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());
    }
}

Ve bu kodun çıktısı şudur:

Entry in finally Block
dev  

Neden blokta sgeçersiz kılınmamış finally, ancak yine de basılı çıktıyı kontrol et?


11
Benimle nihayet engellemek tekrarda return ifadesini koymalıyız, nihayet bloğu her zaman çalıştırılır
Juan Antonio Gomez Moriano

1
denemek bloğu s döndürür
Devendra

6
İfadelerin sırası önemlidir. sDeğerini değiştirmeden geri dönüyorsunuz .
Peter Lawrey


Yanıtlar:


168

tryYürütülmesi ile blok tamamlar returntablosu ve değerinin szaman returntablosu yürütür yöntemi tarafından döndürülen bir değerdir. Aslında finallyhükmü sonradan değerini değiştirir s(sonra returnifadesi tamamlanıncaya) dönüş değerini değiştirmek (bu noktada) değildir.

Yukarıdakilerin , referans veren nesneye değil s, finallybloktaki kendisinin değerindeki değişikliklerle silgilendiğini unutmayın. Eğer skesilebilir bir nesne için bir referans (ki Stringdeğildir) ve içeriği nesnenin değiştiğini finallyblok, daha sonra bu değişiklikler verilen değer görülebilir.

Tüm bunların nasıl çalıştığına dair ayrıntılı kurallar Java Dil Spesifikasyonu Bölüm 14.20.2'de bulunabilir . Bir returnifadenin yürütülmesinin trybloğun ani bir sonlandırılması olarak sayıldığına dikkat edin (" Try bloğunun yürütülmesi başka herhangi bir nedenle R .... ' nin yürütülmesi aniden tamamlanırsa " başlayan bölüm geçerlidir). Bir ifadenin neden bir bloğun aniden sonlandırılması olduğunu öğrenmek için JLS Bölüm 14.17'ye bakın return.

Daha ayrıntılı olarak: Bir ifadenin hem trybloğu hem de bloğu ifadeler nedeniyle aniden sona ererse, §14.20.2'deki aşağıdaki kurallar geçerlidir:finallytry-finallyreturn

tryBloğun yürütülmesi başka herhangi bir nedenle R [bir istisna atmanın yanı sıra] aniden tamamlanırsa, finallyblok yürütülür ve sonra bir seçim vardır:

  • Eğer finallyblok ardından, normalde tamamlar trynedeni R. için deyim tamamlamalar aniden
  • Eğer finallyblok nedeni S aniden tamamlar, sonra tryaniden nedeni S için deyim tamamlamalar (ve neden R atılır).

Sonuç, bloktaki ifadenin tüm ifadenin dönüş değerini belirlemesi returnve finallybloktan try-finallydöndürülen değerin tryatılmasıdır. Benzer bir şey meydana try-catch-finallyeğer deyimi tryblok, bir tarafından yakalanan bir özel durum atar catchbloğu ve her iki catchblok ve finallyblok var returnifadeleri.


Nihayet bloğuna bir değer eklemek yerine String yerine StringBuilder sınıfını kullanırsam, dönüş değerini değiştirir.
Devendra

6
@dev - Bunu cevabımın ikinci paragrafında tartışıyorum. Tanımladığınız durumda, finallyblok döndürülen nesneyi ( StringBuilder) değiştirmez, ancak nesnenin iç kısımlarını değiştirebilir. finallyYönteminden önce blok yürütür (olsa bile aslında döner returnçağıran kod dönen değeri görmeden bu değişikliklerin meydana deyimi bittikten), bu yüzden.
Ted Hopp

List ile deneyin, StringBuilder gibi aynı davranışı elde edersiniz.
Yogesh Prajapati

1
@yogeshprajapati - Evet. Aynı herhangi değişken dönüş değeri ile doğrudur ( StringBuilder, List, Set, reklam nauseum): İçinde içeriğini değiştirirseniz finallybloğu, söz konusu değişiklikler çağıran kod yöntemi nihayet çıkışlar görülür.
Ted Hopp

66

Çünkü dönüş değeri, en sonunda çağrılmadan önce yığına konur.


3
Bu doğru, ancak döndürülen dizenin neden değişmediğine ilişkin OP'nin sorusuna hitap etmiyor. Bu, dizge değişmezliği ve nesnelere karşı referanslar ile, yığındaki dönüş değerini itmekten daha fazlasıyla ilgilidir.
templatetypedef

Her iki konu da birbiriyle ilişkilidir.
Tordek

2
@templatetypedef, String değiştirilebilir olsa bile, kullanılması =onu değiştirmez.
Owen

1
@Saul - Owen'in noktası (doğru olan), değişmezliğin, OP'nin finallybloğunun dönüş değerini neden etkilemediğiyle ilgisi olmamasıydı. Bence templatetypedef'in ulaşmış olabileceği şey (bu net olmasa da), döndürülen değerin değişmez bir nesneye referans olması nedeniyle, finallybloktaki kodu değiştirmenin bile (başka bir returnifadeyi kullanmak dışında ) yöntemden döndürülen değer.
Ted Hopp

1
@templatetypedef Hayır değil. String değişmezliği ile hiçbir ilgisi yok. Aynısı diğer türlerde de olur. Sonunda bloğa girmeden önce dönüş ifadesini değerlendirmek, başka bir deyişle tam olarak 'yığın üzerindeki dönüş değerini itmek' ile ilgilidir.
Marquis of Lorne

32

Bayt kodunun içine bakarsak, JDK'nın önemli bir optimizasyon yaptığını ve foo () yönteminin şöyle göründüğünü göreceğiz :

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

Ve bayt kodu:

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 "dev" dizesini geri dönmeden önce değiştirilmekten korudu. Aslında burada nihayetinde engel yoktur.


Bu bir optimizasyon değildir. Bu sadece gerekli anlambilimin bir uygulamasıdır.
Marquis of Lorne

22

Burada dikkate değer 2 şey var:

  • Dizeler değişmezdir. S'yi "s değişkenlerini geçersiz kıl" olarak ayarladığınızda, s nesnesinin doğal karakter arabelleğini "değişken s geçersiz kıl" olarak değiştirmek için değil, satır içi dizeye başvuracak şekilde ayarlarsınız.
  • Çağıran koda dönmek için yığındaki 'lere bir referans koyarsınız. Daha sonra (nihayet bloğu çalıştığında), referansı değiştirmek zaten yığında bulunan dönüş değeri için hiçbir şey yapmamalıdır.

1
bu yüzden eğer stringbuffer alırsam, sting geçersiz kılınacaktır?
Devendra

10
@dev - Maddede arabelleğin içeriğini değiştirirseniz finally, bu çağrı kodunda görünür . Bununla birlikte, yeni bir stringbuffer atadıysanız s, davranış şimdi olduğu gibi olacaktır.
Ted Hopp

evet son olarak dizge tamponuna eklersem blok değişiklikleri çıktıya yansıtılır.
Devendra

@ 0xCAFEBABE siz de çok güzel cevaplar ve konseptler veriyorsunuz, çok teşekkür ederim.
Devendra

13

Ted'in amacını kanıtlamak için kodunuzu biraz değiştiriyorum.

Gördüğünüz gibi çıktı sgerçekten değişiyor ama dönüşten sonra.

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);
}
}

Çıktı:

Entry in finally Block 
dev 
override variable s

geçersiz kılınan dizeler neden geri dönmüyor.
Devendra

2
Ted ve Tordek'in zaten söylediği gibi, "dönüş değeri, nihayet çalıştırılmadan önce yığına konur"
Frank

1
Bu iyi bir ek bilgi olsa da, soruyu yanıtlamadığı için (kendi başına) ona oy vermek konusunda isteksizim.
Joachim Sauer

5

Teknik olarak konuşursak, returntry bloğundaki try bloğu, bir finallyblok tanımlanmışsa, yalnızca bu son blok ayrıca bir return.

Bu, muhtemelen geçmişe bakıldığında bir hata olan şüpheli bir tasarım kararıdır (referansların varsayılan olarak nullable / değiştirilebilir olması ve bazılarına göre kontrol edilen istisnalar gibi). Birçok yönden bu davranış, ne finallyanlama geldiğine dair günlük konuşma anlayışıyla tam olarak tutarlıdır - " tryblokta önceden ne olursa olsun , her zaman bu kodu çalıştırın." Bu nedenle, bir finallybloktan true döndürürseniz , genel etki her zaman olmalıdır return s, hayır?

Genel olarak, bu nadiren iyi bir deyimdir ve finallykaynakları temizlemek / kapatmak için blokları serbestçe kullanmalısınız, ancak nadiren onlardan bir değer döndürürseniz.


Neden şüpheli? Diğer tüm bağlamlarda değerlendirme sırasına uygundur.
Marquis of Lorne

0

Şunu deneyin: s'nin geçersiz kılma değerini yazdırmak istiyorsanız.

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

1
uyarı veriyor .. nihayet blok normal şekilde tamamlanmıyor
Devendra
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.