เหตุใดการเปลี่ยนแปลงตัวแปรที่ส่งคืนในบล็อกในที่สุดจึงไม่เปลี่ยนค่าส่งคืน


146

ฉันมีคลาส Java ง่ายๆดังที่แสดงด้านล่าง:

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
คุณควรใส่คำแถลงการกลับคืนมาในบล็อกสุดท้ายทำซ้ำกับฉันในที่สุดบล็อกจะถูกดำเนินการเสมอ
Juan Antonio Gomez Moriano

1
ในการลองบล็อกมันจะส่งคืน s
Devendra

6
คำสั่งของงบมีความสำคัญ คุณจะกลับมาsก่อนที่จะเปลี่ยนค่า
Peter Lawrey

6
แต่คุณสามารถส่งคืนค่าใหม่ในfinallyบล็อกซึ่งต่างจากใน C # (ซึ่งคุณไม่สามารถทำได้)
Alvin Wong

คำตอบ:


167

tryเสร็จสมบูรณ์บล็อกที่มีการดำเนินการของreturnคำสั่งและความคุ้มค่าของsในขณะที่returnคำสั่งดำเนินการเป็นค่าส่งกลับโดยวิธีการ ความจริงที่ว่าfinallyข้อในภายหลังเปลี่ยนค่าของs(หลังจากreturnคำสั่งเสร็จสมบูรณ์) ไม่ได้ (ณ จุดนั้น) เปลี่ยนค่าตอบแทน

โปรดทราบว่าข้อตกลงด้านบนเกี่ยวข้องกับการเปลี่ยนแปลงมูลค่าของsตัวเองในfinallyบล็อกไม่ใช่วัตถุที่sอ้างอิง หากsมีการอ้างอิงถึงวัตถุที่ไม่แน่นอน (ซึ่งStringไม่ใช่) และเนื้อหาของวัตถุถูกเปลี่ยนแปลงในfinallyบล็อกการเปลี่ยนแปลงเหล่านั้นจะถูกมองเห็นในค่าที่ส่งคืน

กฎรายละเอียดสำหรับวิธีการดำเนินการทั้งหมดนี้สามารถพบได้ในมาตรา 14.20.2 ของ Java Language ข้อกำหนด โปรดทราบว่าการประมวลผลreturnคำสั่งจะนับเป็นการยกเลิกtryบล็อกอย่างกะทันหัน(ส่วนที่เริ่มต้น " หากการดำเนินการบล็อกลองเสร็จสมบูรณ์ในทันทีด้วยเหตุผลอื่นใดก็ตาม R .... " ใช้) ดูส่วนที่ 14.17 ของ JLSเพราะเหตุใดreturnคำสั่งจึงสิ้นสุดการบล็อกโดยทันที

โดยรายละเอียดเพิ่มเติม: หากทั้งtryบล็อกและfinallyบล็อกของtry-finallyคำสั่งสิ้นสุดลงอย่างกะทันหันเนื่องจากreturnคำสั่งให้ใช้กฎต่อไปนี้ตั้งแต่ 14.20.2:

หากการดำเนินการtryบล็อกเสร็จสมบูรณ์ในทันทีด้วยเหตุผลอื่นใด R [นอกเหนือจากการโยนข้อยกเว้น] finallyบล็อกนั้นจะถูกดำเนินการและจากนั้นก็มีทางเลือก:

  • หากfinallyบล็อกดำเนินการตามปกติtryคำสั่งจะเสร็จสมบูรณ์โดยทันทีด้วยเหตุผล R
  • หากfinallyบล็อกเสร็จสมบูรณ์อย่างกะทันหันด้วยเหตุผล S tryคำสั่งนั้นจะเสร็จสมบูรณ์ทันทีสำหรับเหตุผล S (และเหตุผล R ถูกยกเลิก)

ผลลัพธ์คือreturnคำสั่งในfinallyบล็อกจะกำหนดค่าส่งคืนของtry-finallyคำสั่งทั้งหมดและค่าที่ส่งคืนจากtryบล็อกนั้นจะถูกยกเลิก สิ่งที่คล้ายกันเกิดขึ้นในtry-catch-finallyคำสั่งถ้าtryบล็อกส่งข้อยกเว้นมันถูกcatchบล็อกโดยบล็อกและทั้งcatchบล็อกและfinallyบล็อกมีreturnคำสั่ง


ถ้าฉันใช้คลาส StringBuilder แทนที่จะเป็นสตริงมากกว่าผนวกค่าบางอย่างในที่สุดบล็อกสุดท้ายจะเปลี่ยนค่าส่งคืนทำไม?
Devendra

6
@dev - ฉันพูดถึงว่าในวรรคสองของคำตอบของฉัน ในสถานการณ์ที่คุณอธิบายfinallyบล็อกจะไม่เปลี่ยนแปลงสิ่งที่ส่งคืนวัตถุ ( StringBuilder) แต่สามารถเปลี่ยนภายในของวัตถุได้ finallyรันบล็อกก่อนที่วิธีการที่จริงผลตอบแทน (แม้ว่าreturnคำสั่งเสร็จเรียบร้อยแล้ว) ดังนั้นการเปลี่ยนแปลงเหล่านั้นเกิดขึ้นก่อนที่จะมีรหัสโทรเห็นค่าที่ส่งกลับ
Ted Hopp

ลองกับรายการคุณจะได้รับพฤติกรรมเช่นเดียวกับ StringBuilder
Yogesh Prajapati

1
@yogeshprajapati - อ๋อ เช่นเดียวกับที่เป็นจริงกับค่าตอบแทนใด ๆ ที่ไม่แน่นอน ( StringBuilder, List, Set, nauseum โฆษณา): ถ้าคุณเปลี่ยนเนื้อหาในfinallyบล็อกแล้วการเปลี่ยนแปลงเหล่านั้นจะเห็นในรหัสโทรเมื่อวิธีการในที่สุดก็ออก
Ted Hopp

สิ่งนี้บอกว่าเกิดอะไรขึ้นมันไม่ได้บอกว่าทำไม (จะมีประโยชน์เป็นพิเศษหากมันชี้ไปที่สิ่งที่อยู่ใน JLS)
TJ Crowder

65

เพราะค่าส่งคืนจะถูกวางลงบนสแต็กก่อนที่จะโทรหา


3
เป็นเรื่องจริง แต่ไม่ได้ตอบคำถามของ OP ว่าเหตุใดสตริงที่ส่งคืนจึงไม่เปลี่ยนแปลง ที่เกี่ยวข้องกับสตริงที่ไม่เปลี่ยนแปลงและการอ้างอิงกับวัตถุมากกว่าการผลักดันค่าส่งคืนบนสแต็ก
templatetypedef

ปัญหาทั้งสองเกี่ยวข้องกัน
Tordek

2
@templatetypedef แม้ว่า String จะไม่แน่นอนการใช้=จะไม่กลายพันธุ์
โอเว่น

1
@Saul - จุดของโอเว่น (ซึ่งถูกต้อง) คือความไม่สามารถเปลี่ยนแปลงได้ซึ่งไม่เกี่ยวข้องกับสาเหตุที่finallyบล็อกของ OP ไม่ส่งผลกระทบต่อค่าส่งคืน ฉันคิดว่าสิ่งที่ templatetypedef อาจได้รับที่ (แม้ว่าจะไม่ชัดเจน) ก็คือเพราะค่าที่ส่งคืนคือการอ้างอิงถึงวัตถุที่ไม่เปลี่ยนรูปแม้การเปลี่ยนรหัสในfinallyบล็อก (นอกเหนือจากการใช้returnคำสั่งอื่น) ไม่สามารถส่งผลกระทบต่อ ค่าที่ส่งคืนจากเมธอด
Ted Hopp

1
@templatetypedef ไม่มันไม่ได้ ไม่มีอะไรเกี่ยวข้องกับ String immutability แต่อย่างใด เหมือนกันจะเกิดขึ้นกับประเภทอื่น ๆ มันเกี่ยวข้องกับการประเมิน return-expression ก่อนที่จะเข้าไปในที่สุดบล็อกในคำอื่น ๆ exacfly 'ผลักดันค่าตอบแทนใน stack'
มาร์ควิสแห่ง Lorne

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

และ 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 เก็บรักษาสตริง "dev" จากการถูกเปลี่ยนก่อนที่จะส่งคืน ในความเป็นจริงที่นี่ไม่มีการปิดกั้นในที่สุด


นี่ไม่ใช่การเพิ่มประสิทธิภาพ นี่เป็นเพียงการนำไปใช้ของซีแมนทิกส์ที่ต้องการ
มาร์ควิสแห่ง Lorne

22

มี 2 ​​สิ่งที่น่าสังเกตที่นี่:

  • เงื่อนไขไม่เปลี่ยนรูป เมื่อคุณตั้งค่า s เป็น "override variable s" คุณได้ตั้งค่า s เพื่ออ้างถึง String แบบอินไลน์โดยไม่เปลี่ยนบัฟเฟอร์ char โดยธรรมชาติของวัตถุ s เพื่อเปลี่ยนเป็น "override variable s"
  • คุณใส่การอ้างอิงถึง s บนสแต็คเพื่อกลับไปที่รหัสการโทร หลังจากนั้น (เมื่อบล็อกสุดท้ายรัน) การแก้ไขการอ้างอิงไม่ควรทำอะไรเลยสำหรับค่าส่งคืนที่มีอยู่แล้วในสแต็ก

1
ดังนั้นถ้าฉันใช้ stringbuffer กว่าต่อยจะถูกแทนที่?
Devendra

10
@dev - หากคุณต้องการเปลี่ยนเนื้อหาของบัฟเฟอร์ในfinallyข้อที่จะเห็นในรหัสโทร อย่างไรก็ตามหากคุณมอบหมายให้ stringbuffer ตัวใหม่sพฤติกรรมนั้นจะเหมือนเดิม
Ted Hopp

ใช่ถ้าฉันผนวกในบัฟเฟอร์สตริงในที่สุดบล็อกการเปลี่ยนแปลงสะท้อนให้เห็นถึงการส่งออก
Devendra

@ 0xCAFEBABE คุณให้คำตอบและแนวคิดที่ดีมากฉันขอขอบคุณทุกท่าน
Devendra

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

เหตุใดสตริงที่ถูกเขียนทับจึงไม่กลับมา
Devendra

2
ในฐานะที่เป็นเท็ดและ Tordek แล้วพูดว่า "ค่าส่งกลับจะถูกวางบนสแต็กก่อนที่จะดำเนินการในที่สุด"
Frank

1
ในขณะที่นี่เป็นข้อมูลเพิ่มเติมที่ดีฉันไม่เต็มใจที่จะถอนรากถอนโคนเพราะมันไม่ได้ตอบคำถามด้วยตัวเอง
Joachim Sauer

5

ในทางเทคนิคการพูดreturnในการลองบล็อกจะไม่ถูกเพิกเฉยหากมีการfinallyกำหนดบล็อกเฉพาะในที่สุดว่าบล็อกนั้นรวมถึงreturnบล็อกถูกกำหนดไว้เฉพาะในกรณีที่ในที่สุดก็ปิดกั้นนอกจากนี้ยังมี

เป็นการตัดสินใจออกแบบที่น่าสงสัยซึ่งอาจเป็นความผิดพลาดในการหวนกลับ (เหมือนการอ้างอิงที่เป็นโมฆะ / ไม่แน่นอนโดยค่าเริ่มต้นและตามข้อยกเว้นบางอย่างที่ตรวจสอบแล้ว) ในหลายวิธีพฤติกรรมนี้สอดคล้องกับความเข้าใจทางการพูดของสิ่งที่finallyหมายถึง - "ไม่ว่าจะเกิดอะไรขึ้นก่อนในtryบล็อกให้เรียกใช้รหัสนี้เสมอ" ดังนั้นหากคุณกลับมาจริงจากfinallyบล็อกผลกระทบโดยรวมจะต้องเป็นเสมอreturn sไม่ใช่หรือ

โดยทั่วไปนี่เป็นสำนวนที่ดีและคุณควรใช้finallyบล็อกอย่างอิสระสำหรับการล้าง / ปิดทรัพยากร แต่ไม่ค่อยมีคุณค่าถ้าพวกเขากลับมาจากพวกเขา


มันน่าสงสัยทำไม มันสอดคล้องกับลำดับการประเมินผลในบริบทอื่น ๆ ทั้งหมด
มาร์ควิสแห่ง Lorne

0

ลองสิ่งนี้: หากคุณต้องการพิมพ์ค่าแทนที่ของ s

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

1
มันให้คำเตือน .. ในที่สุดการบล็อกก็ไม่สมบูรณ์ตามปกติ
Devendra
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.