Tại sao câu lệnh String switch không hỗ trợ trường hợp null?


125

Tôi chỉ tự hỏi tại sao switchcâu lệnh Java 7 không hỗ trợ một nulltrường hợp và thay vào đó ném NullPointerException? Xem dòng nhận xét bên dưới (ví dụ được lấy từ bài viết Hướng dẫn Java trênswitch ):

{
    String month = null;
    switch (month) {
        case "january":
            monthNumber = 1;
            break;
        case "february":
            monthNumber = 2;
            break;
        case "march":
            monthNumber = 3;
            break;
        //case null:
        default: 
            monthNumber = 0;
            break;
    }

    return monthNumber;
}

Điều này sẽ tránh được một ifđiều kiện để kiểm tra null trước mỗi lần switchsử dụng.


12
Không có câu trả lời kết luận cho điều này, vì chúng tôi không phải là người tạo ra ngôn ngữ. Tất cả các câu trả lời sẽ là phỏng đoán thuần túy.
asteri

2
Một nỗ lực để bật nullsẽ gây ra một ngoại lệ. Thực hiện ifkiểm tra null, sau đó đi vào switchtuyên bố.
gparyani

28
Từ JLS : Theo đánh giá của các nhà thiết kế ngôn ngữ lập trình Java, [ném một NullPointerExceptionnếu biểu thức ước lượng nullvào thời gian chạy] là kết quả tốt hơn là âm thầm bỏ qua toàn bộ câu lệnh chuyển đổi hoặc chọn thực thi các câu lệnh (nếu có) sau nhãn mặc định (nếu có).
gparyani

3
@gparyani: Làm cho nó một câu trả lời. Nghe có vẻ rất chính thức và dứt khoát.
Thilo

7
@JeffGohlke: "Không có cách nào để trả lời một câu hỏi tại sao trừ khi bạn là người đưa ra quyết định." ... tốt, bình luận của gparyani chứng minh điều ngược lại
user541686

Câu trả lời:


145

Như damryfbfnetsi chỉ ra trong các bình luận, JLS §14.11 có ghi chú sau:

Việc cấm sử dụng nulllàm nhãn chuyển đổi sẽ ngăn người ta viết mã không bao giờ được thực thi. Nếu switchbiểu thức là kiểu tham chiếu, nghĩa là Stringhoặc kiểu nguyên thủy được đóng hộp hoặc kiểu enum, thì lỗi thời gian chạy sẽ xảy ra nếu biểu thức ước lượng nullvào thời gian chạy. Theo đánh giá của các nhà thiết kế ngôn ngữ lập trình Java, đây là kết quả tốt hơn là âm thầm bỏ qua toàn bộ switchcâu lệnh hoặc chọn thực thi các câu lệnh (nếu có) sau defaultnhãn (nếu có).

(nhấn mạnh của tôi)

Trong khi câu cuối cùng bỏ qua khả năng sử dụng case null:, nó có vẻ hợp lý và đưa ra quan điểm về ý định của các nhà thiết kế ngôn ngữ.

Nếu chúng ta xem xét chi tiết triển khai, bài đăng trên blog này của Christian Hujer có một số suy đoán sâu sắc về lý do tại sao nullkhông được phép trong các công tắc (mặc dù nó tập trung vào công enumtắc thay vì công Stringtắc):

Trong phần mềm này, switchcâu lệnh thường sẽ biên dịch thành mã byte không cần thẻ. Và đối số "vật lý" switchcũng như các trường hợp của nó là ints. Giá trị int để bật được xác định bằng cách gọi phương thức Enum.ordinal(). Các lệnh [...] bắt đầu từ 0.

Điều đó có nghĩa, lập bản đồ nullđể 0sẽ không phải là một ý tưởng tốt. Một công tắc trên giá trị enum đầu tiên sẽ không thể phân biệt được với null. Có lẽ đó là một ý tưởng tốt để bắt đầu đếm số thứ tự cho enum ở mức 1. Tuy nhiên, nó không được định nghĩa như thế và định nghĩa này không thể thay đổi.

Trong khi các Stringcông tắc được triển khai khác nhau , thì enumcông tắc xuất hiện đầu tiên và đặt tiền lệ cho cách chuyển đổi trên loại tham chiếu nên hoạt động khi tham chiếu null.


1
Nó sẽ là một ngẫu hứng lớn để cho phép xử lý null như là một phần của case null:nếu nó sẽ được thực hiện độc quyền cho String. Hiện tại tất cả các Stringkiểm tra đều yêu cầu kiểm tra null dù thế nào đi nữa nếu chúng ta muốn thực hiện đúng, mặc dù chủ yếu là ngầm định bằng cách đặt chuỗi không đổi đầu tiên như trong "123test".equals(value). Bây giờ chúng tôi buộc phải viết tuyên bố chuyển đổi của mình như trongif (value != null) switch (value) {...
YoYo

1
Re "ánh xạ null thành 0 sẽ không phải là một ý tưởng hay": đó là một cách đánh giá thấp vì giá trị của "" .hashcode () là 0! Điều đó có nghĩa là một chuỗi null và một chuỗi có độ dài bằng 0 sẽ phải được xử lý giống hệt nhau trong một câu lệnh chuyển đổi, điều này rõ ràng là không khả thi.
skomisa

Đối với trường hợp của enum, điều gì đã ngăn họ ánh xạ null thành -1?
krispy

31

Nói chung nulllà khó chịu để xử lý; có lẽ một ngôn ngữ tốt hơn có thể sống mà không có null.

Vấn đề của bạn có thể được giải quyết bằng

    switch(month==null?"":month)
    {
        ...
        //case "":
        default: 
            monthNumber = 0;

    }

Không phải là một ý tưởng tốt nếu monthlà một chuỗi rỗng: điều này sẽ coi nó giống như một chuỗi rỗng.
gparyani

13
Đối với nhiều trường hợp coi null là chuỗi rỗng có thể hoàn toàn hợp lý
Eric Woodruff

23

Nó không đẹp, nhưng String.valueOf()cho phép bạn sử dụng Chuỗi null trong một công tắc. Nếu nó tìm thấy null, nó chuyển đổi nó thành "null", nếu không nó sẽ trả về cùng một Chuỗi mà bạn đã truyền nó. Nếu bạn không xử lý "null"rõ ràng, thì nó sẽ đi đến default. Nhắc nhở duy nhất là không có cách phân biệt giữa Chuỗi "null"và một nullbiến thực tế .

    String month = null;
    switch (String.valueOf(month)) {
        case "january":
            monthNumber = 1;
            break;
        case "february":
            monthNumber = 2;
            break;
        case "march":
            monthNumber = 3;
            break;
        case "null":
            monthNumber = -1;
            break;
        default: 
            monthNumber = 0;
            break;
    }
    return monthNumber;

2
Tôi tin rằng làm một cái gì đó như thế này trong java là một antipotype.
Łukasz Rzeszotarski

2
@ UkaszRzeszotarski Điều đó khá giống với ý của tôi là "nó không đẹp"
krispy

15

Đây là một nỗ lực để trả lời tại sao nó ném NullPointerException

Đầu ra của lệnh javap bên dưới cho thấy caseđược chọn dựa trên mã băm của switchchuỗi đối số và do đó ném NPE khi .hashCode()được gọi trên chuỗi null.

6: invokevirtual #18                 // Method java/lang/String.hashCode:()I
9: lookupswitch  { // 3
    -1826660246: 44
     -263893086: 56
      103666243: 68
        default: 95
   }

Điều này có nghĩa, dựa trên các câu trả lời cho hashCode của Java có thể tạo ra cùng một giá trị cho các chuỗi khác nhau không? , mặc dù hiếm, vẫn có khả năng hai trường hợp được khớp (hai chuỗi có cùng mã băm) Xem ví dụ này bên dưới

    int monthNumber;
    String month = args[0];

    switch (month) {
    case "Ea":
        monthNumber = 1;
        break;
    case "FB":
        monthNumber = 2;
        break;
    // case null:
    default:
        monthNumber = 0;
        break;
    }
    System.out.println(monthNumber);

javap mà

  10: lookupswitch  { // 1
              2236: 28
           default: 59
      }
  28: aload_3       
  29: ldc           #22                 // String Ea
  31: invokevirtual #24                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
  34: ifne          49
  37: aload_3       
  38: ldc           #28                 // String FB
  40: invokevirtual #24                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
  43: ifne          54
  46: goto          59 //Default

Như bạn có thể thấy chỉ có một trường hợp được tạo cho "Ea""FB"với hai ifđiều kiện để kiểm tra sự trùng khớp với từng chuỗi trường hợp. Cách rất thú vị và phức tạp để thực hiện chức năng này!


5
Điều đó có thể đã được thực hiện theo một cách khác, mặc dù.
Thilo

2
Tôi muốn nói rằng đây là một lỗi thiết kế.
Deer Hunter

6
Tôi tự hỏi liệu điều này có liên quan đến lý do tại sao một số khía cạnh đáng ngờ của hàm băm chuỗi không bị thay đổi hay không: nói chung mã không nên dựa vào việc hashCodetrả về cùng các giá trị trên các lần chạy khác nhau của chương trình, nhưng vì băm chuỗi được đưa vào thực thi bởi trình biên dịch, phương thức băm chuỗi kết thúc là một phần của đặc tả ngôn ngữ.
supercat

5

Câu chuyện dài ... (và hy vọng đủ thú vị !!!)

Enum được giới thiệu lần đầu tiên trong Java1.5 ( tháng 9 năm2004 ) và lỗi yêu cầu cho phép chuyển đổi trên Chuỗi đã được gửi lại từ lâu ( tháng 10 năm 95 ). Nếu bạn xem bình luận được đăng trên lỗi đó vào tháng 6 năm2004 , có Don't hold your breath. Nothing resembling this is in our plans.vẻ như họ đã trì hoãn ( bỏ qua ) lỗi này và cuối cùng đã khởi chạy Java 1.5 trong cùng năm mà họ giới thiệu 'enum' với thứ tự bắt đầu từ 0 và quyết định ( bỏ lỡ ) không hỗ trợ null cho enum. Sau đó vào Java1.7 ( tháng 7 năm 2011 ) họ đã theo dõi ( bắt buộc) cùng một triết lý với String (tức là trong khi tạo mã byte, không có kiểm tra null nào được thực hiện trước khi gọi phương thức hashcode ()).

Vì vậy, tôi nghĩ rằng nó thực sự bắt đầu với thực tế enum xuất hiện đầu tiên và được triển khai với thứ tự bắt đầu từ 0 do họ không thể hỗ trợ giá trị null trong khối chuyển đổi và sau đó với String, họ đã quyết định ép buộc cùng một triết lý, ví dụ như giá trị null được phép trong khối chuyển đổi.

TL; DR Với String, họ có thể xử lý NPE (do cố gắng tạo mã băm cho null) trong khi thực hiện chuyển đổi mã java sang mã byte nhưng cuối cùng quyết định không.

Tham chiếu: TheBUG , JavaVersionHistory , JavaCodeToByteCode , SO


1

Theo Tài liệu Java:

Một switch hoạt động với các kiểu dữ liệu byte, short, char và int nguyên thủy. Nó cũng hoạt động với các kiểu liệt kê (được thảo luận trong Enum Type), lớp String và một vài lớp đặc biệt bao bọc một số kiểu nguyên thủy nhất định: Ký tự, Byte, Short và Integer (được thảo luận trong Số và Chuỗi).

nullkhông có loại và không phải là một thể hiện của bất cứ thứ gì, nó sẽ không hoạt động với câu lệnh switch.


4
Tuy nhiên nulllà một giá trị hợp lệ cho một String, Character, Byte, Short, hoặc Integertham khảo.
asteri

0

Câu trả lời đơn giản là nếu bạn sử dụng một công tắc có loại tham chiếu (chẳng hạn như loại nguyên thủy được đóng hộp), lỗi thời gian chạy sẽ xảy ra nếu biểu thức là null vì bỏ hộp thư sẽ ném NPE.

vì vậy trường hợp null (không hợp lệ) dù sao cũng không thể được thực thi;)


1
Điều đó có thể đã được thực hiện theo một cách khác, mặc dù.
Thilo

OK @Thilo, những người thông minh hơn tôi đã tham gia vào việc thực hiện này. Nếu bạn biết những cách khác có thể được thực hiện, tôi rất muốn biết đó là những gì [và tôi chắc chắn có những cách khác] vì vậy hãy chia sẻ ...
trong

3
Các chuỗi không được đóng hộp theo kiểu nguyên thủy và NPE không xảy ra do ai đó đang cố gắng "bỏ hộp" chúng.
Thilo

@thilo, những cách khác để thực hiện điều này là gì?
vào

3
if (x == null) { // the case: null part }
Thilo

0

Tôi đồng ý với những nhận xét sâu sắc (Dưới mui xe ....) trong https://stackoverflow.com/a/18263594/1053496 trong câu trả lời của @Paul Bellora.

Tôi tìm thấy một lý do nữa từ kinh nghiệm của tôi.

Nếu 'case' có thể là null nghĩa là switch (biến) là null thì miễn là nhà phát triển cung cấp trường hợp 'null' phù hợp thì chúng ta có thể lập luận rằng nó ổn. Nhưng điều gì sẽ xảy ra nếu nhà phát triển không cung cấp bất kỳ trường hợp 'null' nào phù hợp. Sau đó, chúng ta phải khớp nó với trường hợp 'mặc định', đây có thể không phải là điều mà nhà phát triển dự định xử lý trong trường hợp mặc định. Do đó, việc khớp 'null' với mặc định có thể gây ra 'hành vi đáng ngạc nhiên'. Do đó, việc ném 'NPE' sẽ khiến nhà phát triển xử lý mọi trường hợp một cách rõ ràng. Tôi thấy ném NPE trong trường hợp này rất chu đáo.


0

Sử dụng lớp StringUtils của Apache

String month = null;
switch (StringUtils.trimToEmpty(month)) {
    case "xyz":
        monthNumber=1;  
    break;
    default:
       monthNumber=0;
    break;
}
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.