Phương thức Java với các biên dịch kiểu trả về mà không có câu lệnh return


228

Câu hỏi 1:

Tại sao đoạn mã sau biên dịch mà không có câu lệnh return?

public int a() {
    while(true);
}

Lưu ý: Nếu tôi thêm trở lại sau một thời gian thì tôi nhận được một Unreachable Code Error.

Câu hỏi 2:

Mặt khác, tại sao đoạn mã sau lại biên dịch,

public int a() {
    while(0 == 0);
}

mặc dù sau đây không.

public int a(int b) {
    while(b == b);
}

2
Không phải là bản sao của stackoverflow.com/questions/16789832/ , nhờ nửa sau của câu hỏi thứ hai.
TJ Crowder

Câu trả lời:


274

Câu hỏi 1:

Tại sao đoạn mã sau biên dịch mà không có câu lệnh return?

public int a() 
{
    while(true);
}

Điều này được bao phủ bởi JLS §8.4.7 :

Nếu một phương thức được khai báo là có kiểu trả về (§8.4.5), thì lỗi thời gian biên dịch xảy ra nếu phần thân của phương thức có thể hoàn thành bình thường (§14.1).

Nói cách khác, một phương thức có kiểu trả về chỉ phải trả về bằng cách sử dụng câu lệnh return cung cấp giá trị trả về; phương pháp này không được phép "thả ra khỏi phần cuối của cơ thể". Xem §14.17 để biết các quy tắc chính xác về câu lệnh trả về trong phần thân phương thức.

Phương thức có thể có kiểu trả về và không chứa câu lệnh trả về. Đây là một ví dụ:

class DizzyDean {
    int pitch() { throw new RuntimeException("90 mph?!"); }
}

Vì trình biên dịch biết rằng vòng lặp sẽ không bao giờ chấm dứt ( truetất nhiên luôn luôn đúng), nên nó biết hàm không thể "trả lại bình thường" (thả ra khỏi phần cuối của cơ thể), và do đó không sao cả return.

Câu hỏi 2:

Mặt khác, tại sao đoạn mã sau lại biên dịch,

public int a() 
{
    while(0 == 0);
}

mặc dù sau đây không.

public int a(int b)
{
    while(b == b);
}

Trong 0 == 0trường hợp, trình biên dịch biết rằng vòng lặp sẽ không bao giờ kết thúc (điều đó 0 == 0sẽ luôn đúng). Nhưng nó không biết rằng cho b == b.

Tại sao không?

Trình biên dịch hiểu các biểu thức hằng (§15.28) . Trích dẫn §15.2 - Các hình thức biểu đạt (vì kỳ lạ câu này không nằm trong §15.28) :

Một số biểu thức có một giá trị có thể được xác định tại thời điểm biên dịch. Đây là các biểu thức không đổi (§15.28).

Trong b == bví dụ của bạn , vì có một biến liên quan, nó không phải là biểu thức hằng và không được chỉ định để được xác định tại thời điểm biên dịch. Chúng ta có thể thấy rằng nó luôn luôn đúng trong trường hợp này (mặc dù nếu bdouble, như QBrute đã chỉ ra , chúng ta có thể dễ dàng bị đánh lừa Double.NaN, không phải==chính nó ), nhưng JLS chỉ xác định rằng các biểu thức không đổi được xác định tại thời điểm biên dịch , nó không cho phép trình biên dịch thử đánh giá các biểu thức không hằng. bayou.io nêu lên một điểm tốt cho lý do tại sao không: Nếu bạn bắt đầu đi vào con đường cố gắng xác định các biểu thức liên quan đến các biến tại thời gian biên dịch, bạn sẽ dừng ở đâu? b == blà hiển nhiên (er, cho khôngNaNgiá trị), nhưng những gì về a + b == b + a? Hay là (a + b) * 2 == a * 2 + b * 2? Vẽ đường tại hằng số có ý nghĩa.

Vì vậy, vì nó không "xác định" biểu thức, trình biên dịch không biết rằng vòng lặp sẽ không bao giờ chấm dứt, vì vậy nó nghĩ rằng phương thức có thể trở lại bình thường - điều mà nó không được phép thực hiện, bởi vì nó bắt buộc phải sử dụng return. Vì vậy, nó phàn nàn về việc thiếu a return.


34

Thật thú vị khi nghĩ về kiểu trả về phương thức không phải là lời hứa trả về giá trị của loại đã chỉ định, nhưng như một lời hứa không trả về giá trị không thuộc loại đã chỉ định. Do đó, nếu bạn không bao giờ trả lại bất cứ điều gì, bạn sẽ không thất hứa, và vì vậy bất kỳ điều nào sau đây là hợp pháp:

  1. Vòng lặp mãi mãi:

    X foo() {
        for (;;);
    }
  2. Đệ quy mãi mãi:

    X foo() {
        return foo();
    }
  3. Ném ra một ngoại lệ:

    X foo() {
        throw new Error();
    }

(Tôi tìm ra đệ quy một niềm vui để suy nghĩ về: Trình biên dịch tin rằng phương pháp này sẽ trả về một giá trị của loại hình X(bất kể đó là), nhưng điều đó không đúng, bởi vì không có mã hiện có bất kỳ ý tưởng làm thế nào để tạo ra hoặc mua sắm X.)


8

Nhìn vào mã byte, nếu những gì đang được trả về không khớp với định nghĩa, bạn sẽ nhận được một lỗi biên dịch.

Thí dụ:

for(;;) sẽ hiển thị mã byte:

L0
    LINENUMBER 6 L0
    FRAME SAME
    GOTO L0

Lưu ý rằng không có bất kỳ mã byte trả về

Điều này không bao giờ đạt được một sự trở lại, và do đó không trả lại loại sai.

Để so sánh, một phương pháp như:

public String getBar() { 
    return bar; 
}

Sẽ trả về các mã byte sau:

public java.lang.String getBar();
    Code:
      0:   aload_0
      1:   getfield        #2; //Field bar:Ljava/lang/String;
      4:   areturn

Lưu ý "areturn" có nghĩa là "trả lại một tham chiếu"

Bây giờ nếu chúng ta làm như sau:

public String getBar() { 
    return 1; 
}

Sẽ trả về các mã byte sau:

public String getBar();
  Code:
   0:   iconst_1
   1:   ireturn

Bây giờ chúng ta có thể thấy rằng kiểu trong định nghĩa không khớp với kiểu trả về của ireturn, có nghĩa là return int.

Vì vậy, thực sự những gì nó đi xuống là nếu phương thức có đường dẫn trả về, đường dẫn đó phải phù hợp với kiểu trả về. Nhưng có những trường hợp trong mã byte, nơi không có đường dẫn trả lại nào được tạo ra, và do đó không vi phạm quy tắc.

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.