2013-04-16 36 views
144

Tôi có một lớp Java đơn giản như hình dưới đây:Tại sao thay đổi biến trả lại trong một khối cuối cùng không thay đổi giá trị trả về?

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

Và đầu ra của mã này là thế này:

Entry in finally Block 
dev 

Tại sao s không ghi đè trong khối finally, chưa kiểm soát được in ra?

+0

obj.foo() trả về giá trị của s; – Devendra

+11

bạn nên đặt câu lệnh trả về trên khối cuối cùng, lặp lại với tôi, cuối cùng khối luôn được thực hiện –

+1

khi thử chặn trả lại số – Devendra

Trả lời

163

Khối try hoàn thành với việc thực thi câu lệnh return và giá trị s tại thời điểm thực hiện câu lệnh return là giá trị được trả về bởi phương thức. Thực tế là mệnh đề finally sau này thay đổi giá trị s (sau khi hoàn thành câu lệnh return) không (tại thời điểm đó) thay đổi giá trị trả lại.

Lưu ý rằng các giao dịch trên có các thay đổi đối với giá trị của s chính nó trong khối finally, không phải đối tượng mà tham chiếu s. Nếu s là tham chiếu đến đối tượng có thể thay đổi (không String không) và nội dung của đối tượng đã được thay đổi trong khối finally, thì những thay đổi đó sẽ được nhìn thấy trong giá trị trả về.

Quy tắc chi tiết về cách tất cả các thao tác này có thể được tìm thấy trong Section 14.20.2 of the Java Language Specification. Lưu ý rằng việc thực thi câu lệnh return được tính là chấm dứt đột ngột khối try (phần bắt đầu "Nếu việc thực hiện khối thử hoàn tất đột ngột vì bất kỳ lý do nào khác R ...." được áp dụng). Xem Section 14.17 of the JLS vì sao câu hỏi return là chấm dứt đột ngột một khối.

Bằng cách chi tiết hơn nữa: nếu cả try khối và finally khối của một tuyên bố try-finally chấm dứt đột ngột vì return báo cáo, sau đó các quy tắc sau đây từ §14.20.2 áp dụng:

Nếu thực hiện của khối try hoàn thành đột ngột vì bất kỳ lý do nào khác R [ngoài việc ném một ngoại lệ], thì khối finally được thực hiện và sau đó có một lựa chọn:

  • Nếu khối finally hoàn bình thường, sau đó báo cáo kết quả try hoàn đột ngột vì lý do R.
  • Nếu khối finally hoàn đột ngột vì lý do S, sau đó báo cáo kết quả try hoàn đột ngột vì lý do S (và lý trí R được loại bỏ).

Kết quả là các return tuyên bố trong khối finally xác định giá trị trở lại của toàn bộ câu lệnh try-finally, và giá trị trả về từ khối try được loại bỏ.Một điều tương tự xảy ra trong câu lệnh try-catch-finally nếu khối try ném ngoại lệ, nó bị chặn bởi khối catch và cả khối catch và khối finally có các câu hỏi return.

+0

Nếu tôi sử dụng StringBuilder lớp thay vì String hơn nối thêm một số giá trị trong khối cuối cùng, nó thay đổi giá trị trả về.why? – Devendra

+6

@dev - Tôi thảo luận rằng trong đoạn thứ hai của câu trả lời của tôi. Trong tình huống bạn mô tả, khối 'finally' không thay đổi đối tượng được trả về (' StringBuilder') nhưng nó có thể thay đổi nội bộ của đối tượng. Khối 'finally' thực thi trước khi phương thức thực sự trả về (mặc dù câu lệnh' return' đã hoàn thành), vì vậy những thay đổi đó xảy ra trước khi mã gọi thấy giá trị trả về. –

+0

thử với Danh sách, bạn sẽ có được hành vi tương tự như StringBuilder. –

62

Vì giá trị trả lại được đặt trên ngăn xếp trước khi cuộc gọi đến cuối cùng.

+3

Đó là sự thật, nhưng nó không giải quyết câu hỏi của OP là tại sao chuỗi trả lại không thay đổi. Điều đó liên quan đến sự bất biến chuỗi và các tham chiếu so với các đối tượng hơn là đẩy giá trị trả về trên stack. – templatetypedef

+0

Cả hai vấn đề đều liên quan. – Tordek

+0

@templatetypedef ngay cả khi Chuỗi có thể thay đổi, bằng cách sử dụng '=' sẽ không biến đổi nó. – Owen

0

Hãy thử cách này: Nếu bạn muốn in giá trị ghi đè của s.

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

nó cho cảnh báo .. cuối cùng khối không hoàn thành bình thường – Devendra

22

Có 2 điều đáng chú ý ở đây:

  • Strings là không thay đổi. Khi bạn đặt s để "ghi đè biến s", bạn đặt s để tham chiếu đến chuỗi nội tuyến, không thay đổi bộ đệm char vốn có của đối tượng s để thay đổi thành "ghi đè biến s".
  • Bạn đặt tham chiếu đến các s trên ngăn xếp để quay lại mã gọi. Sau đó (khi khối cuối cùng chạy), việc thay đổi tham chiếu sẽ không làm bất cứ điều gì cho giá trị trả về đã có trong ngăn xếp.
+1

vì vậy nếu tôi lấy stringbuffer hơn sting sẽ được ghi đè? – Devendra

+10

@dev - Nếu bạn thay đổi _content_ của bộ đệm trong mệnh đề 'finally', nó sẽ được nhìn thấy trong mã gọi. Tuy nhiên, nếu bạn đã gán một xâu chuỗi mới cho 's', thì hành vi sẽ giống như bây giờ. –

+0

có nếu tôi nối thêm vào bộ đệm chuỗi trong các thay đổi khối cuối cùng phản ánh trên đầu ra. – Devendra

12

Tôi thay đổi mã của bạn một chút để chứng minh quan điểm của Ted.

Như bạn có thể thấy trong đầu ra s thực sự bị thay đổi nhưng sau khi trả lại.

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

Output:

Entry in finally Block 
dev 
override variable s 
+0

lý do tại sao các chuỗi ghi đè không trả lại. – Devendra

+2

Như Ted và Tordek đã nói "giá trị trả về được đặt trên ngăn xếp trước khi cuối cùng được thực hiện" – Frank

+1

Trong khi đây là thông tin bổ sung tốt, tôi miễn cưỡng để upvote nó, bởi vì nó không (một mình) trả lời câu hỏi. –

6

Về mặt kỹ thuật, các return trong khối try sẽ không thể bỏ qua nếu một khối finally được định nghĩa, chỉ khi đó khối finally cũng bao gồm một return.

Đó là một quyết định thiết kế đáng ngờ có lẽ là một sai lầm khi nhìn lại (giống như các tham chiếu là nullable/mutable theo mặc định, và, theo một số trường hợp ngoại lệ đã kiểm tra). Theo nhiều cách, hành vi này hoàn toàn phù hợp với sự hiểu biết thông tục về ý nghĩa của finally - "bất kể điều gì xảy ra trước trong khối try, luôn chạy mã này". Do đó nếu bạn trả về true từ khối finally, hiệu ứng tổng thể phải luôn là return s, không?

Nói chung, điều này hiếm khi là thành ngữ tốt và bạn nên sử dụng các khối finally tự do để dọn dẹp/đóng tài nguyên nhưng hiếm khi trả lại giá trị từ chúng.

33

Nếu chúng ta nhìn vào bên trong bytecode, chúng ta sẽ nhận thấy rằng JDK đã làm cho một tối ưu hóa đáng kể, và foo() phương pháp trông giống như:

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

Và 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 chuỗi "dev" được lưu giữ bị thay đổi trước khi trở về. Trong thực tế ở đây là không có khối cuối cùng ở tất cả.

Các vấn đề liên quan