Khai báo và khởi tạo các biến trong Java switch


99

Tôi có một câu hỏi điên rồ về công tắc Java.

int key = 2;

switch (key) {
    case 1:
        int value = 1;
        break;
    case 2:
        value = 2;
        System.out.println(value);
        break;
    default:
        break;
}

Tình huống 1 - Khi cả keyhai, nó in thành công giá trị là 2. Tình
huống 2 - Khi tôi định bình luận value = 2về case 2:nó, người ta kêu lên rằng Giá trị biến cục bộ có thể chưa được khởi tạo .

Câu hỏi: Tình

huống 1: Nếu luồng thực thi không đi đến case 1:(khi key = 2), thì làm thế nào nó biết kiểu của biến giá trị là int?

Tình huống 2: Nếu trình biên dịch biết kiểu của biến giá trị là int, thì nó phải đã truy cập vào int value = 1;biểu thức trong case 1:. (Khai báo và Khởi tạo). Sau đó, tại sao nó sqawrk Khi tôi sẽ bình luận value = 2trong case 2:, nói rằng giá trị biến cục bộ có thể không được khởi tạo .


13
Đó không phải là một câu hỏi điên rồ, đó là một câu hỏi rất hay.
biziclop


@PhilippeCarriere Thực ra, tôi nghĩ ngược lại - câu trả lời ở đây sẽ tốt hơn (ngay cả khi bài đăng mới hơn) vì có tham chiếu trực tiếp đến JLS và tóm tắt tốt vấn đề được đề cập trong các câu trả lời khác nhau trong bài đăng đó. Xem thêm .
Tunaki

@Tunaki Mô tả cho một bản sao bắt đầu bằng "Câu hỏi này đã được hỏi trước đây". Tôi đang đọc nó vì cái sau nên được đánh dấu là bản sao của cái trước đó. Nhưng tôi đồng ý rằng cái này có những yếu tố tốt. Có lẽ chúng nên được hợp nhất bằng cách nào đó?
Philippe Carriere

Ngoài ra, rất nhiều câu hỏi trên SO được đánh dấu là trùng lặp với câu hỏi ban đầu của tôi, vì vậy nếu bạn quyết định đánh dấu câu hỏi này là câu hỏi ban đầu mới, vui lòng sửa tất cả các liên kết để tham khảo câu hỏi này thay vì của tôi.
Philippe Carriere

Câu trả lời:


114

Về cơ bản, các câu lệnh Switch là kỳ lạ về phạm vi. Từ phần 6.3 của JLS :

Phạm vi khai báo biến cục bộ trong một khối (§14.4) là phần còn lại của khối trong đó khai báo xuất hiện, bắt đầu với bộ khởi tạo của chính nó và bao gồm bất kỳ bộ khai báo nào khác ở bên phải trong câu lệnh khai báo biến cục bộ.

Trong trường hợp của bạn, case 2nằm trong cùng một khối với case 1và xuất hiện sau nó, mặc dù case 1sẽ không bao giờ thực thi ... vì vậy biến cục bộ có trong phạm vi và có sẵn để viết mặc dù về mặt logic bạn không bao giờ "thực thi" khai báo. (Một khai báo không thực sự "thực thi được" mặc dù việc khởi tạo là như vậy.)

Nếu bạn nhận xét ra value = 2;bài tập, trình biên dịch vẫn biết bạn đang đề cập đến biến nào, nhưng bạn sẽ không đi qua bất kỳ đường dẫn thực thi nào gán giá trị cho nó, đó là lý do tại sao bạn gặp lỗi như khi bạn cố gắng đọc bất kỳ biến cục bộ không chắc chắn được chỉ định nào khác.

Tôi thực sự khuyên bạn không nên sử dụng các biến cục bộ được khai báo trong các trường hợp khác - nó dẫn đến mã rất khó hiểu, như bạn đã thấy. Khi tôi giới thiệu các biến cục bộ trong các câu lệnh switch (điều mà tôi cố gắng thực hiện hiếm khi - lý tưởng là các trường hợp phải rất ngắn), tôi thường thích giới thiệu một phạm vi mới:

case 1: {
    int value = 1;
    ...
    break;
}
case 2: {
    int value = 2;
    ...
    break;
}

Tôi tin rằng điều này là rõ ràng hơn.


11
+1 cho "Một khai báo không thực sự" thực thi được "mặc dù quá trình khởi tạo là.". Và cảm ơn bạn vì những lời khuyên quá Skeet.
namalfernandolk

1
Với việc tích hợp JEP-325, bức tranh về phạm vi của các biến cục bộ đã thay đổi và người ta có thể sử dụng cùng một tên trong các trường hợp thay vì các khối chuyển đổi. Mặc dù nó cũng dựa trên mã hóa khối tương tự. Ngoài ra, giá trị được gán cho một biến trên mỗi trường hợp chuyển đổi sẽ thuận tiện hơn nhiều với các biểu thức chuyển đổi.
Naman

Điểm để thêm một phạm vi mới với dấu ngoặc nhọn. Thậm chí không biết bạn có thể làm điều đó.
Cá Mặt Trời nổi

21

Biến đã được khai báo (dưới dạng int), nhưng chưa được khởi tạo (được gán giá trị ban đầu). Hãy nghĩ về dòng:

int value = 1;

Như:

int value;
value = 1;

Phần này int valuenói với trình biên dịch tại thời điểm biên dịch rằng bạn có một biến được gọi là giá trị là một int. Phần value = 1khởi tạo nó, nhưng điều đó xảy ra trong thời gian chạy và hoàn toàn không xảy ra nếu nhánh đó của công tắc không được nhập.


+1 cho lời giải thích hay về khai báo và khởi tạo trong thời gian biên dịch và thời gian chạy.
namalfernandolk

18

Từ http://www.coderanch.com/t/447381/java-programmer-SCJP/certification/variable-initialization-within-case-block

Các khai báo được xử lý tại thời điểm biên dịch và không phụ thuộc vào luồng thực thi mã của bạn. Vì valueđược khai báo trong phạm vi cục bộ của khối switch nên nó có thể sử dụng được ở bất kỳ đâu trong khối đó kể từ thời điểm khai báo.


1
tại sao câu trả lời này được ủng hộ? nó không trả lời câu hỏi, không giống như câu trả lời của paul hoặc xiên ...
Dhruv Gairola

7
Nó có. Vì vậy, +1, một xu, cũng từ phía tôi.
Ravinder Reddy

3

Với việc tích hợp JEP 325: Chuyển biểu thức (Xem trước) trong các bản dựng truy cập sớm JDK-12. Có những thay đổi nhất định có thể thấy từ câu trả lời của Jon -

  1. Phạm vi biến cục bộ - Các biến cục bộ trong trường hợp chuyển mạch giờ có thể là cục bộ cho chính trường hợp thay vì toàn bộ khối chuyển mạch . Một ví dụ (tương tự như những gì Jon cũng đã thử về mặt cú pháp) xem xétDaylớp enum để giải thích thêm:

    public enum Day {
        MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
    }
    
    // some another method implementation
    Day day = Day.valueOf(scanner.next());
    switch (day) {
        case MONDAY,TUESDAY -> {
            var temp = "mon-tue";
            System.out.println(temp);
        }
        case WEDNESDAY,THURSDAY -> {
            var temp = Date.from(Instant.now()); // same variable name 'temp'
            System.out.println(temp);
        }
        default ->{
            var temp = 0.04; // different types as well (not mandatory ofcourse)
            System.out.println(temp);
        }
    }
  2. Biểu thức chuyển đổi - Nếu mục đích là gán giá trị cho một biến và sau đó sử dụng nó, một lần có thể sử dụng biểu thức chuyển đổi. ví dụ

    private static void useSwitchExpression() {
        int key = 2;
        int value = switch (key) {
            case 1 ->  1;
            case 2 -> 2;
            default -> {break 0;}
        };
        System.out.println("value = " + value); // prints 'value = 2'
    }

0

Giải thích này có thể hữu ích.

    int id=1;

    switch(id){
        default: 
            boolean b= false; // all switch scope going down, because there is no scope tag

        case 1:
            b = false;
        case 2:{
            //String b= "test"; you can't declare scope here. because it's in the scope @top
            b=true; // b is still accessible
        }
        case 3:{
            boolean c= true; // case c scope only
            b=true; // case 3 scope is whole switch
        }
        case 4:{
            boolean c= false; // case 4 scope only
        }
    }

0

Đặc tả Java:

https://docs.oracle.com/javase/specs/jls/se12/html/jls-14.html#jls-14.11

Trường hợp hoàn thành đột ngột vì ngắt với nhãn được xử lý theo quy tắc chung cho các câu lệnh có nhãn (§14.7).

https://docs.oracle.com/javase/specs/jls/se12/html/jls-14.html#jls-14.7

Câu lệnh được gắn nhãn:

LabeledStatement: Identity: Statement

LabeledStatementNoShortIf: Định danh: StatementNoShortIf

Không giống như C và C ++, ngôn ngữ lập trình Java không có câu lệnh goto; nhãn câu lệnh định danh được sử dụng với câu lệnh break (§14.15) hoặc tiếp tục (§14.16) xuất hiện ở bất kỳ đâu trong câu lệnh được gắn nhãn.

Phạm vi nhãn của câu lệnh được gắn nhãn là Câu lệnh được chứa ngay lập tức.

Nói cách khác, trường hợp 1, trường hợp 2 là các nhãn trong câu lệnh switch. câu lệnh break và continue có thể được áp dụng cho các nhãn.

Bởi vì các nhãn chia sẻ phạm vi của câu lệnh, tất cả các biến được xác định trong các nhãn đều chia sẻ phạm vi của câu lệnh switch.

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.