लौटे हुए वैरिएबल को आखिर ब्लॉक में बदलने से रिटर्न वैल्यू क्यों नहीं बदलती?


146

मेरे पास एक सरल जावा क्लास है जैसा कि नीचे दिखाया गया है:

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

और इस कोड का आउटपुट यह है:

Entry in finally Block
dev  

ब्लॉक sमें ओवरराइड क्यों नहीं किया जाता है finally, फिर भी प्रिंटेड आउटपुट को नियंत्रित करते हैं?


11
आपको अंत में ब्लॉक पर रिटर्न स्टेटमेंट डालना चाहिए, मेरे साथ दोहराएं, आखिरकार ब्लॉक हमेशा निष्पादित होता है
जुआन एंटोनियो गोमेज़ मोरियानो

1
कोशिश में इसे वापस
लौटाओ

6
बयानों का क्रम मायने रखता है। sमान बदलने से पहले आप लौट रहे हैं ।
पीटर लॉरी

6
लेकिन, आपfinally C # (जो आप नहीं कर सकते) के विपरीत, ब्लॉक में नया मान वापस कर सकते हैं
एल्विन वोंग

जवाबों:


167

tryके निष्पादन के साथ ब्लॉक कम्प्लिट्स returnबयान और का मूल्य sसमय में returnबयान निष्पादित करता है विधि द्वारा दिए गए मान है। तथ्य यह है कि finallyक्लॉज बाद में s( returnस्टेटमेंट पूरा होने के बाद ) का मान बदलता है (उस बिंदु पर) रिटर्न वैल्यू नहीं बदलता है।

ध्यान दें कि उपरोक्त खंड ब्लॉक sमें स्वयं के मूल्य में परिवर्तन करता finallyहै, न कि उस वस्तु के sसंदर्भ में। यदि sएक उत्परिवर्तनीय वस्तु (जो Stringनहीं है) और ऑब्जेक्ट की सामग्री को finallyब्लॉक में बदल दिया गया था , तो उन परिवर्तनों को लौटे मूल्य में देखा जाएगा।

यह सब कैसे संचालित होता है, इसके विस्तृत नियम जावा भाषा विनिर्देश की धारा 14.20.2 में पाए जा सकते हैं । ध्यान दें कि एक returnस्टेटमेंट का निष्पादन tryब्लॉक के अचानक समापन के रूप में गिना जाता है (" यदि ब्लॉक का निष्पादन किसी अन्य कारण R .... के लिए अचानक पूरा हो जाता है तो " सेक्शन शुरू होता है)। जेएलएस की धारा 14.17 इस बात के लिए देखें कि एक returnबयान एक ब्लॉक की अचानक समाप्ति क्यों है ।

अधिक विस्तार के माध्यम से: अगर दोनों tryब्लॉक और finallyएक के ब्लॉक try-finallyबयान की वजह से अचानक समाप्त returnबयान, तो §14.20.2 से निम्नलिखित नियम लागू होते हैं:

यदि tryब्लॉक का निष्पादन किसी अन्य कारण से अचानक पूरा हो जाता है R [एक अपवाद को फेंकने के अलावा], तो finallyब्लॉक निष्पादित होता है, और फिर एक विकल्प होता है:

  • यदि finallyब्लॉक सामान्य रूप से पूरा होता है, तो tryकथन आर के लिए अचानक पूरा हो जाता है।
  • यदि finallyब्लॉक S के कारण अचानक पूरा हो जाता है, तो tryकथन S (और कारण R को छोड़ दिया जाता है) के कारण अचानक पूरा हो जाता है।

नतीजा यह है कि है returnमें बयान finallyब्लॉक पूरी की वापसी मूल्य निर्धारित करता है try-finallyबयान है, और से दिए गए मान tryब्लॉक खारिज कर दिया है। ऐसा ही एक बात यह है कि एक में होता है try-catch-finallyअगर बयान tryब्लॉक एक अपवाद है, यह एक द्वारा पकड़ा जाता है फेंकता catchब्लॉक, और दोनों catchब्लॉक और finallyब्लॉक returnबयान।


यदि मैं स्ट्रिंग के बजाय StringBuilder वर्ग का उपयोग करता हूं, तो अंत में ब्लॉक में कुछ मान को जोड़ने के बजाय, इसका रिटर्न मान बदल जाता है। क्यों?
देवेन्द्र

6
@देव - मैं चर्चा करता हूं कि मेरे उत्तर के दूसरे पैराग्राफ में। आपके द्वारा वर्णित स्थिति में, finallyब्लॉक नहीं बदलता है कि किस वस्तु को लौटाया गया है ( StringBuilder) लेकिन यह ऑब्जेक्ट के आंतरिक हिस्से को बदल सकता है। finallyविधि से पहले ब्लॉक कार्यान्वित वास्तव में रिटर्न (भले ही returnपहले बुला कोड दिए गए मान देखता बयान समाप्त हो गया है), इसलिए उन परिवर्तन होते हैं।
टेड हॉप

सूची के साथ प्रयास करें, आपको StringBuilder जैसा व्यवहार मिलता है।
योगेश प्रजापति

1
@ योगेशप्रजापति - हां। एक ही किसी भी परिवर्तनशील वापसी मान के साथ सच है ( StringBuilder, List, Set, विज्ञापन nauseum): यदि आप में सामग्री बदलने के finallyब्लॉक, फिर उन परिवर्तनों को बुला कोड जब विधि अंत में बाहर निकलता है में देखा जाता है।
टेड हॉप

यह कहता है कि क्या होता है, यह क्यों नहीं कहता है (विशेष रूप से उपयोगी होगा अगर यह इंगित करता है कि यह जेएलएस में कहां कवर किया गया था)।
टीजे क्राउडर

65

क्योंकि कॉल करने से पहले स्टैक पर रिटर्न वैल्यू डाल दी जाती है।


3
यह सच है, लेकिन यह ओपी के सवाल को संबोधित नहीं करता है कि लौटे स्ट्रिंग में बदलाव क्यों नहीं हुआ। स्टैक पर रिटर्न वैल्यू को पुश करने से ज्यादा स्ट्रिंग इम्यूटेबिलिटी और रेफरेंस बनाम ऑब्जेक्ट के साथ करना पड़ता है।
templatetypedef

दोनों मुद्दे संबंधित हैं।
टोरडेक

2
@templatetypedef भले ही स्ट्रिंग उत्परिवर्तित थे, का उपयोग करके =इसे उत्परिवर्तित नहीं करेंगे।
ओवेन

1
@ सैल - ओवेन की बात (जो सही है) यह थी कि ओपी के finallyब्लॉक ने रिटर्न वैल्यू पर कोई असर नहीं डाला इसलिए इसका कोई लेना-देना नहीं है। मुझे लगता है कि हो सकता है कि templatetypedef हो रहा हो (हालांकि यह स्पष्ट नहीं है) ऐसा इसलिए है क्योंकि लौटाया गया मान किसी अपरिवर्तनीय वस्तु का संदर्भ है, यहां तक ​​कि कोड को finallyब्लॉक में बदलना (अन्य returnकथन का उपयोग करने के अलावा ) प्रभावित नहीं कर सकता है। मूल्य विधि से वापस आ गया।
टेड हॉप

1
@templatetypedef नहीं यह नहीं है। स्ट्रिंग अपरिवर्तनीयता से कोई लेना देना नहीं है। किसी अन्य प्रकार के साथ भी ऐसा ही होगा। अंत में-ब्लॉक में प्रवेश करने से पहले रिटर्न-एक्सप्रेशन का मूल्यांकन करना पड़ता है, दूसरे शब्दों में 'स्टैक पर रिटर्न वैल्यू को आगे बढ़ाते हैं'।
लोर्ने की

32

यदि हम bytecode के अंदर देखते हैं, तो हम ध्यान देंगे कि JDK ने एक महत्वपूर्ण अनुकूलन किया है, और foo () विधि इस प्रकार है:

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

और बायटेकोड:

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

जावा ने "देव" स्ट्रिंग को लौटने से पहले बदल दिया। वास्तव में यहाँ कोई अंत में ब्लॉक है।


यह एक अनुकूलन नहीं है। यह बस आवश्यक शब्दार्थों का एक कार्यान्वयन है।
लोर्ने की

22

यहाँ 2 बातें उल्लेखनीय हैं:

  • तार अपरिवर्तनीय हैं। जब आप "ओवरराइड वैरिएबल s" सेट करते हैं, तो आप "ओवरराइड वैरिएबल एस" को बदलने के लिए सेट किए गए ऑब्जेक्ट के अंतर्निहित चार बफ़र को न बदलकर, इनलाइन स्ट्रिंग को संदर्भित करना चाहते हैं।
  • आपने कॉलिंग कोड पर लौटने के लिए स्टैक पर s का संदर्भ दिया। बाद में (जब अंत में ब्लॉक चलता है), संदर्भ को बदलकर स्टैक पर पहले से ही वापसी मूल्य के लिए कुछ भी नहीं करना चाहिए।

1
तो अगर मैं स्टिंगबफ़र लेता हूं तो स्टिंग को ओवरराइड किया जाएगा ??
देवेन्द्र

10
@देव - यदि आप खंड में बफर की सामग्री को बदलना चाहते थे finally, तो यह कॉलिंग कोड में देखा जाएगा। हालाँकि, यदि आपने एक नया स्ट्रिंगबफ़र सौंपा है s, तो व्यवहार अब जैसा होगा।
टेड हॉप

हाँ, अगर मैं अंत में ब्लॉक परिवर्तन में स्ट्रिंग बफर में संलग्न आउटपुट पर प्रतिबिंबित करता हूं।
देवेन्द्र

@ 0xCAFEBABE आप बहुत अच्छे उत्तर और अवधारणाएँ देते हैं, मैं आपको बहुत धन्यवाद देता हूँ।
देवेन्द्र

13

टेड की बात साबित करने के लिए मैंने आपका कोड थोड़ा बदल दिया।

जैसा कि आप देख सकते हैं कि आउटपुट sवास्तव में बदल गया है, लेकिन वापसी के बाद।

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

आउटपुट:

Entry in finally Block 
dev 
override variable s

ओवरराइड स्ट्रिंग क्यों नहीं लौटी।
देवेन्द्र

2
जैसा कि टेड और टोरडेक ने पहले ही कहा था "अंत में निष्पादित होने से पहले स्टैक पर रिटर्न वैल्यू डाल दी जाती है"
फ्रैंक

1
हालांकि यह अच्छी अतिरिक्त जानकारी है, मैं इसे उखाड़ने के लिए अनिच्छुक हूं, क्योंकि यह (अपने दम पर) सवाल का जवाब नहीं देता है।
जोकिम सॉयर

5

तकनीकी रूप से कहा जाए तो, returnब्लॉक में अगर finallyब्लॉक को परिभाषित किया गया है, तो केवल इस बात पर ध्यान नहीं दिया जाएगा कि अंत में ब्लॉक भी शामिल है return

यह एक संदिग्ध डिजाइन निर्णय है जो शायद पूर्वव्यापी में एक गलती थी (बहुत कुछ संदर्भों द्वारा डिफ़ॉल्ट रूप से अशक्त / पारस्परिक होने की तरह, और, कुछ के अनुसार, अपवादों को जांचा गया)। कई मायनों में यह व्यवहार वास्तव में क्या finallyहै की बोलचाल की समझ के अनुरूप है - " tryब्लॉक में पहले से कोई फर्क नहीं पड़ता , हमेशा इस कोड को चलाएं।" इसलिए यदि आप किसी finallyब्लॉक से सही रिटर्न करते हैं , तो समग्र प्रभाव हमेशा होना चाहिएreturn s , नहीं?

सामान्य तौर पर, यह शायद ही कभी एक अच्छा मुहावरा होता है, और आपको finallyसंसाधनों की सफाई / बंद करने के लिए उदारतापूर्वक ब्लॉकों का उपयोग करना चाहिए, लेकिन शायद ही कभी अगर उनसे कोई मूल्य वापस मिलता है।


यह संदिग्ध है क्यों? यह अन्य सभी संदर्भों में मूल्यांकन आदेश के अनुरूप है।
लोर्ने की

0

इसे आज़माएँ: यदि आप s के ओवरराइड मान को प्रिंट करना चाहते हैं।

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

1
यह चेतावनी देता है .. आखिरकार ब्लॉक सामान्य रूप से पूरा नहीं होता है
देवेंद्र
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.