Trở về từ một khối cuối cùng trong Java


176

Gần đây tôi đã rất ngạc nhiên khi thấy rằng có thể có một câu lệnh return trong một khối cuối cùng trong Java.

Có vẻ như nhiều người nghĩ rằng đó là một điều tồi tệ để làm như được mô tả trong ' Đừng trở lại trong một điều khoản cuối cùng '. Cào sâu hơn một chút, tôi cũng thấy ' Sự trở lại của Java không phải lúc nào ', cho thấy một số ví dụ khá kinh khủng về các loại điều khiển luồng khác trong các khối cuối cùng.

Vì vậy, câu hỏi của tôi là, bất cứ ai cũng có thể cho tôi một ví dụ trong đó một câu lệnh return (hoặc điều khiển luồng khác) trong một khối cuối cùng tạo ra mã tốt hơn / dễ đọc hơn không?

Câu trả lời:


89

Các ví dụ bạn cung cấp đủ lý do để không sử dụng kiểm soát luồng từ cuối cùng.

Ngay cả khi có một ví dụ giả định trong đó "tốt hơn", hãy xem xét nhà phát triển phải duy trì mã của bạn sau này và những người có thể không nhận thức được sự tinh tế. Nhà phát triển nghèo nàn đó thậm chí có thể là bạn ....


5
Chắc chắn rồi. Tôi đoán tôi đang hỏi trong trường hợp ai đó có thể cho tôi một ví dụ thực sự hấp dẫn về mặt tốt.
Matt Sheppard

@MattSheppard trong dao, tôi sẽ thường ghi nhật ký thoát truy vấn trong một lần thử cuối cùng
Blake

147

Tôi đã có một thời gian khó khăn để theo dõi một lỗi cách đây nhiều năm do nguyên nhân này. Mã này là một cái gì đó như:

Object problemMethod() {
    Object rtn = null;
    try {
        rtn = somethingThatThrewAnException();
    }
    finally {
        doSomeCleanup();
        return rtn;
    }
}

Điều gì đã xảy ra là ngoại lệ đã bị ném xuống trong một số mã khác. Nó đã bị bắt và đăng nhập và truy xuất lại trong somethingThatThrewAnException()phương thức. Nhưng ngoại lệ đã không được tuyên truyền trong quá khứ problemMethod(). Sau một thời gian dài xem xét điều này, cuối cùng chúng tôi đã theo dõi nó đến phương thức trả về. Phương thức trả về trong khối cuối cùng về cơ bản là ngăn chặn ngoại lệ xảy ra trong khối thử truyền lên mặc dù nó không bị bắt.

Giống như những người khác đã nói, mặc dù việc quay trở lại từ một khối cuối cùng theo thông số Java là hợp pháp, nhưng đó là điều BAD và không nên làm.


Nơi nào nên đặt sự trở lại sau đó?
Parsecer

@parsecer Tôi sẽ nói ngay sau khi gọi một cái gì đóThatThrewAnException () trong khối thử
Tiago Sippert

@parsecer, ?? Chỉ cần làm theo cách thông thường, sau khi cuối cùng.
Pacerier

21

javac sẽ cảnh báo sự trở lại cuối cùng nếu bạn sử dụng -Xlint: cuối cùng. Ban đầu javac không phát ra cảnh báo nào - nếu có gì đó không đúng với mã, nó sẽ không được biên dịch. Thật không may, khả năng tương thích ngược có nghĩa là sự ngu ngốc khéo léo không lường trước được có thể bị cấm.

Các ngoại lệ có thể được ném từ các khối cuối cùng, nhưng trong trường hợp đó, hành vi được trưng bày gần như chắc chắn là những gì bạn muốn.


13

Việc thêm các cấu trúc điều khiển và trả về các khối {} cuối cùng chỉ là một ví dụ khác về các hành vi lạm dụng "chỉ vì bạn có thể" nằm rải rác trong hầu như tất cả các ngôn ngữ phát triển. Jason đã đúng khi cho rằng nó có thể dễ dàng trở thành một cơn ác mộng bảo trì - các lập luận chống lại việc trả lại sớm từ các chức năng áp dụng nhiều hơn cho trường hợp "trả lại muộn" này.

Cuối cùng, các khối tồn tại cho một mục đích, để cho phép bạn dọn dẹp hoàn toàn sau chính mình, bất kể điều gì đã xảy ra trong tất cả các mã có sẵn. Về cơ bản, đây là đóng / giải phóng con trỏ tệp, kết nối cơ sở dữ liệu, v.v., mặc dù tôi có thể thấy nó được kéo dài để nói thêm vào kiểm toán bespoke.

Bất cứ điều gì ảnh hưởng đến sự trở lại của hàm nên nằm trong khối thử {}. Ngay cả khi bạn có một phương pháp theo đó bạn đã kiểm tra trạng thái bên ngoài, thực hiện thao tác tốn thời gian, sau đó kiểm tra lại trạng thái đó trong trường hợp nó trở nên không hợp lệ, bạn vẫn muốn kiểm tra lần thứ hai trong thử {} - nếu cuối cùng nó cũng ngồi trong {} và hoạt động lâu không thành công, sau đó bạn sẽ kiểm tra trạng thái đó lần thứ hai một cách không cần thiết.


6

Một thử nghiệm Groovy đơn giản:

public class Instance {

  List<String> runningThreads = new ArrayList<String>()

  void test(boolean returnInFinally) {

    println "\ntest(returnInFinally: $returnInFinally)"
    println "--------------------------------------------------------------------------"
    println "before execute"
    String result = execute(returnInFinally, false)
    println "after execute -> result: " + result
    println "--------------------------------------------------------------------------"

    println "before execute"
    try {
      result = execute(returnInFinally, true)
      println "after execute -> result: " + result
    } catch (Exception ex) {
      println "execute threw exception: " + ex.getMessage()
    }  
    println "--------------------------------------------------------------------------\n"

  }

  String execute(boolean returnInFinally, boolean throwError) {
      String thread = Thread.currentThread().getName()
      println "...execute(returnInFinally: $returnInFinally, throwError: $throwError) - thread: $thread"
      runningThreads.add(thread)
      try {
        if (throwError) {
          println "...error in execute, throw exception"
          throw new Exception("as you liked :-)")
        }
        println "...return 'OK' from execute"
        return "OK"
      } finally {
        println "...pass finally block"
        if (returnInFinally) return "return value from FINALLY ^^"
        // runningThreads.remove(thread)
      }
  }
}

Instance instance = new Instance()
instance.test(false)
instance.test(true)

Đầu ra:

test(returnInFinally: false)
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: false, throwError: false) - thread: Thread-116
...return 'OK' from execute
...pass finally block
after execute -> result: OK
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: false, throwError: true) - thread: Thread-116
...error in execute, throw exception
...pass finally block
execute threw exception: as you liked :-)
-----------------------------------------------------------------------------


test(returnInFinally: true)
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: true, throwError: false) - thread: Thread-116
...return 'OK' from execute
...pass finally block
after execute -> result: return value from FINALLY ^^
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: true, throwError: true) - thread: Thread-116
...error in execute, throw exception
...pass finally block
after execute -> result: return value from FINALLY ^^
-----------------------------------------------------------------------------

Câu hỏi:

Một điểm thú vị đối với tôi là xem Groovy xử lý các khoản hoàn trả ngầm. Trong Groovy, có thể "trả về" từ một phương thức chỉ đơn giản là để lại một giá trị ở cuối (không trả về). Bạn nghĩ điều gì sẽ xảy ra, nếu bạn bỏ qua dòng runningThreads.remove (..) trong câu lệnh cuối cùng - điều này sẽ ghi đè lên giá trị trả về thông thường ("OK") và bao gồm ngoại lệ?!


0

Trở về từ bên trong một finallykhối sẽ khiến exceptionsbị mất.

Một tuyên bố trả lại bên trong một khối cuối cùng sẽ khiến bất kỳ ngoại lệ nào có thể bị ném trong khối thử hoặc bắt bị loại bỏ.

Theo Đặc tả ngôn ngữ Java:

Nếu việc thực thi khối thử hoàn thành đột ngột vì bất kỳ lý do nào khác R, thì khối cuối cùng được thực thi và sau đó có một lựa chọn:

   If the finally block completes normally, then the try statement
   completes  abruptly for reason R.

   If the finally block completes abruptly for reason S, then the try
   statement  completes abruptly for reason S (and reason R is
   discarded).

Lưu ý: Theo JLS 14,17 - một câu lệnh return luôn hoàn thành đột ngột.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.