Mengapa pernyataan saklar String tidak mendukung kasus null?

125

Saya hanya bertanya-tanya mengapa pernyataan Java 7 switchtidak mendukung nullkasus dan malah melempar NullPointerException? Lihat baris komentar di bawah ini (contoh diambil dari artikel Tutorial Javaswitch ):

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

Ini akan menghindari ifkondisi untuk pemeriksaan nol sebelum setiap switchpenggunaan.

Prashant Bhate
sumber
12
Tidak ada jawaban pasti untuk ini, karena kami bukanlah orang yang membuat bahasa. Semua jawaban akan menjadi dugaan murni.
asteri
2
Upaya untuk mengaktifkan nullakan menyebabkan pengecualian. Lakukan ifpemeriksaan null, lalu masuk ke switchpernyataan.
gparyani
28
Dari JLS : Dalam penilaian desainer bahasa pemrograman Java, [melempar NullPointerExceptionjika ekspresi mengevaluasi ke nullsaat runtime] adalah hasil yang lebih baik daripada melewatkan seluruh pernyataan switch atau memilih untuk mengeksekusi pernyataan (jika ada) setelah label default (jika ada).
gparyani
3
@gparyani: Jadikan itu jawaban. Kedengarannya sangat resmi dan pasti.
Thilo
7
@JeffGohlke: "Tidak ada cara untuk menjawab pertanyaan mengapa kecuali Anda adalah orang yang membuat keputusan." ... yah, komentar gparyani membuktikan sebaliknya
pengguna541686

Jawaban:

145

Sebagai damryfbfnetsi poin di komentar, JLS §14.11 memiliki catatan berikut:

Larangan penggunaan nullsebagai label sakelar mencegah seseorang menulis kode yang tidak akan pernah bisa dijalankan. Jika switchekspresi dari jenis referensi, yaitu, Stringatau jenis primitif kotak atau jenis enum, maka kesalahan waktu proses akan terjadi jika ekspresi mengevaluasi ke nullpada waktu proses. Dalam penilaian desainer bahasa pemrograman Java, ini adalah hasil yang lebih baik daripada melewatkan seluruh switchpernyataan atau memilih untuk mengeksekusi pernyataan (jika ada) setelah defaultlabel (jika ada).

(penekanan saya)

Sementara kalimat terakhir mengabaikan kemungkinan penggunaan case null:, tampaknya masuk akal dan menawarkan pandangan tentang niat desainer bahasa.

Jika kita lebih suka melihat detail implementasi, posting blog oleh Christian Hujer ini memiliki beberapa spekulasi yang mendalam tentang mengapa nulltidak diizinkan di sakelar (meskipun itu berpusat pada enumsakelar daripada Stringsakelar):

Di bawah tenda, switchpernyataan tersebut biasanya akan dikompilasi ke kode byte tablesswitch. Dan argumen "fisik" switchserta kasusnya adalah ints. Nilai int untuk diaktifkan ditentukan dengan menjalankan metode Enum.ordinal(). Ordinal [...] dimulai dari nol.

Artinya, memetakan nullke 0bukanlah ide yang bagus. Tombol pada nilai enum pertama tidak dapat dibedakan dari nol. Mungkin itu ide yang bagus untuk mulai menghitung ordinal untuk enum di 1. Namun itu belum didefinisikan seperti itu, dan definisi ini tidak dapat diubah.

Sementara Stringsakelar diimplementasikan secara berbeda , enumsakelar datang lebih dulu dan mengatur preseden tentang bagaimana pengaktifan tipe referensi harus berperilaku ketika referensi tersebut null.

Paul Bellora
sumber
1
Ini akan menjadi perbaikan besar untuk memungkinkan penanganan null sebagai bagian dari case null:jika itu akan diterapkan secara eksklusif untuk String. Saat ini semua Stringpemeriksaan memerlukan pemeriksaan null bagaimanapun juga jika kita ingin melakukannya dengan benar, meskipun sebagian besar secara implisit dengan meletakkan string konstan terlebih dahulu seperti pada "123test".equals(value). Sekarang kami dipaksa untuk menulis pernyataan sakelar kami seperti padaif (value != null) switch (value) {...
YoYo
1
Re "memetakan null ke 0 tidak akan menjadi ide yang baik": itu meremehkan karena nilai "" .hashcode () adalah 0! Ini berarti bahwa string nol dan string panjang nol harus diperlakukan secara identik dalam pernyataan switch, yang jelas-jelas tidak layak.
skomisa
Untuk kasus enum, apa yang menghentikannya dari memetakan null ke -1?
krispy
31

Secara umum nullburuk untuk ditangani; mungkin bahasa yang lebih baik bisa hidup tanpanya null.

Masalah Anda mungkin diselesaikan dengan

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

    }
ZhongYu
sumber
Bukan ide yang baik jika itu monthadalah string kosong: ini akan memperlakukannya dengan cara yang sama seperti string null.
gparyani
13
Dalam banyak kasus, memperlakukan null sebagai string kosong bisa jadi sangat masuk akal
Eric Woodruff
23

Ini tidak bagus, tetapi String.valueOf()memungkinkan Anda untuk menggunakan String null dalam sebuah saklar. Jika ditemukan null, ia akan mengubahnya menjadi "null", jika tidak ia hanya mengembalikan String yang sama dengan yang Anda berikan. Jika Anda tidak menangani "null"secara eksplisit, maka ini akan masuk ke default. Satu-satunya peringatan adalah bahwa tidak ada cara untuk membedakan antara String "null"dan nullvariabel aktual .

    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;
krispy
sumber
2
Saya percaya melakukan sesuatu seperti ini di java adalah antipattern.
Łukasz Rzeszotarski
2
@ ŁukaszRzeszotarski Itulah yang saya maksud dengan "tidak cantik"
krispy
15

Ini adalah upaya untuk menjawab mengapa ia melempar NullPointerException

Output dari perintah javap di bawah ini mengungkapkan yang casedipilih berdasarkan kode hash dari switchstring argumen dan karenanya melempar NPE ketika .hashCode()dipanggil pada string nol.

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

Artinya, berdasarkan jawaban untuk hashCode Java dapat menghasilkan nilai yang sama untuk string yang berbeda? , meski jarang, masih ada kemungkinan dua kasus dicocokkan (dua string dengan kode hash yang sama) Lihat contoh di bawah ini

    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 yang

  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

Seperti yang Anda lihat, hanya satu kasus yang dibuat untuk "Ea"dan "FB"tetapi dengan dua ifkondisi untuk memeriksa kecocokan dengan setiap string kasus. Cara yang sangat menarik dan rumit untuk mengimplementasikan fungsi ini!

Prashant Bhate
sumber
5
Itu bisa saja diterapkan dengan cara lain.
Thilo
2
Menurut saya ini adalah bug desain.
Pemburu Rusa
6
Saya ingin tahu apakah ini terkait dengan mengapa beberapa aspek yang meragukan dari fungsi hash string belum diubah: secara umum kode tidak boleh bergantung pada hashCodemengembalikan nilai yang sama pada jalan yang berbeda dari suatu program, tetapi karena hash string dimasukkan ke dalam file yang dapat dieksekusi oleh kompiler, metode hash string akhirnya menjadi bagian dari spesifikasi bahasa.
supercat
5

Singkat cerita ... (dan semoga cukup menarik !!!)

Enum pertama kali diperkenalkan di Java1.5 ( Sep'2004 ) dan bug yang meminta pengaktifan String telah diajukan sejak lama ( Okt'95 ). Jika Anda melihat komentar yang diposting pada bug itu pada Jun'2004 , dikatakan Don't hold your breath. Nothing resembling this is in our plans.Sepertinya mereka menunda ( mengabaikan ) bug ini dan akhirnya meluncurkan Java 1.5 pada tahun yang sama di mana mereka memperkenalkan 'enum' dengan ordinal mulai dari 0 dan memutuskan ( terlewat ) tidak mendukung null untuk enum. Kemudian di Jawa1.7 ( Jul'2011 ) mereka mengikuti ( dipaksa) filosofi yang sama dengan String (yaitu saat membuat bytecode tidak ada pemeriksaan null yang dilakukan sebelum memanggil metode hashcode ()).

Jadi saya pikir itu bermuara pada fakta enum datang pertama dan diimplementasikan dengan ordinal dimulai pada 0 karena mereka tidak dapat mendukung nilai null di blok saklar dan kemudian dengan String mereka memutuskan untuk memaksakan filosofi yang sama yaitu nilai null tidak diizinkan di blok sakelar.

TL; DR Dengan String mereka dapat menangani NPE (disebabkan oleh upaya untuk menghasilkan kode hash untuk null) saat mengimplementasikan kode java ke konversi kode byte tetapi akhirnya memutuskan untuk tidak melakukannya.

Ref: TheBUG , JavaVersionHistory , JavaCodeToByteCode , SO

sactiw
sumber
1

Menurut Java Docs:

Sebuah switch bekerja dengan tipe data primitif byte, short, char, dan int. Ia juga bekerja dengan jenis enumerasi (dibahas dalam Jenis Enum), kelas String, dan beberapa kelas khusus yang membungkus jenis primitif tertentu: Karakter, Byte, Pendek, dan Integer (dibahas dalam Bilangan dan String).

Karena nulltidak memiliki tipe, dan bukan merupakan turunan dari apapun, itu tidak akan bekerja dengan pernyataan switch.

BlackHatSamurai
sumber
4
Namun nulladalah nilai yang valid untuk String, Character, Byte, Short, atau Integerreferensi.
asteri tanggal
0

Jawabannya sederhana jika Anda menggunakan sakelar dengan tipe referensi (seperti tipe primitif berkotak), kesalahan waktu proses akan terjadi jika ekspresi null karena membuka kotak itu akan membuang NPE.

jadi case null (yang ilegal) tidak akan pernah bisa dijalankan;)

amrith
sumber
1
Itu bisa saja diterapkan dengan cara lain.
Thilo
Oke @Thilo, orang yang lebih pintar dari saya terlibat dalam penerapan ini. Jika Anda mengetahui cara lain untuk menerapkannya, saya ingin tahu apa itu [dan saya yakin ada yang lain] jadi berbagilah ...
amrith
3
String bukanlah tipe primitif yang dikotakkan, dan NPE tidak terjadi karena seseorang mencoba untuk "membuka kotak" -nya.
Thilo
@thilo, cara lain untuk mengimplementasikan ini adalah, apa?
amrith
3
if (x == null) { // the case: null part }
Thilo
0

Saya setuju dengan komentar berwawasan (Di bawah tenda ....) di https://stackoverflow.com/a/18263594/1053496 dalam jawaban @Paul Bellora.

Saya menemukan satu alasan lagi dari pengalaman saya.

Jika 'case' bisa null itu berarti switch (variabel) null maka selama developer menyediakan case 'null' yang cocok maka kita bisa membantahnya. Tapi apa yang akan terjadi jika pengembang tidak menyediakan kasus 'null' yang cocok. Kemudian kita harus mencocokkannya dengan kasus 'default' yang mungkin bukan yang dimaksudkan oleh pengembang untuk ditangani dalam kasus default. Oleh karena itu, mencocokkan 'null' ke default dapat menyebabkan 'perilaku mengejutkan'. Karenanya melempar 'NPE' akan membuat pengembang menangani setiap kasus secara eksplisit. Saya menemukan melempar NPE dalam kasus ini sangat bijaksana.

nantitv
sumber
0

Gunakan kelas Apache StringUtils

String month = null;
switch (StringUtils.trimToEmpty(month)) {
    case "xyz":
        monthNumber=1;  
    break;
    default:
       monthNumber=0;
    break;
}
bhagat
sumber