Mengapa saya tidak bisa menggunakan pernyataan switch pada sebuah String?

1004

Apakah fungsi ini akan dimasukkan ke dalam versi Java yang lebih baru?

Adakah yang bisa menjelaskan mengapa saya tidak bisa melakukan ini, seperti cara teknis switchpernyataan Java ?

Alex Beardsley
sumber
195
Ini di SE 7. 16 thn setelah permintaan itu. download.oracle.com/javase/tutorial/java/nutsandbolts/…
angryITguy
81
Sun jujur ​​dalam evaluasi mereka: "Don't hold your breath."lol, bugs.sun.com/bugdatabase/view_bug.do?bug_id=1223179
raffian
3
@ffffian kurasa itu karena dia menghela nafas dua kali. Mereka sedikit terlambat untuk menjawab juga, setelah hampir 10 tahun. Dia mungkin sedang mengepak kotak makan siang untuk cucunya.
WeirdElfB0y

Jawaban:

1004

Pergantian pernyataan dengan Stringkasus telah diimplementasikan di Java SE 7 , setidaknya 16 tahun setelah mereka pertama kali diminta. Alasan yang jelas untuk keterlambatan itu tidak disediakan, tetapi kemungkinan itu ada hubungannya dengan kinerja.

Implementasi di JDK 7

Fitur ini sekarang telah diimplementasikan javac dengan proses "de-sugaring"; sintaks tingkat tinggi yang bersih menggunakan Stringkonstanta dalam casedeklarasi diperluas pada waktu kompilasi menjadi kode yang lebih kompleks mengikuti suatu pola. Kode yang dihasilkan menggunakan instruksi JVM yang selalu ada.

A switchdengan Stringkasing diterjemahkan ke dalam dua sakelar selama kompilasi. Yang pertama memetakan setiap string ke integer unik — posisinya dalam sakelar asli. Ini dilakukan dengan terlebih dahulu menyalakan kode hash label. Kasing yang sesuai adalah ifpernyataan yang menguji kesetaraan string; jika ada tabrakan pada hash, tes ini adalah cascading if-else-if. Saklar kedua mencerminkan bahwa dalam kode sumber asli, tetapi mengganti label case dengan posisi yang sesuai. Proses dua langkah ini membuatnya mudah untuk mempertahankan kontrol aliran sakelar asli.

Beralih di JVM

Untuk kedalaman teknis lebih lanjut switch, Anda dapat merujuk ke Spesifikasi JVM, di mana kompilasi pernyataan switch dijelaskan. Singkatnya, ada dua instruksi JVM yang berbeda yang dapat digunakan untuk sakelar, tergantung pada tingkat konstanta yang digunakan oleh kasing. Keduanya bergantung pada penggunaan konstanta integer untuk setiap kasus untuk dieksekusi secara efisien.

Jika konstanta padat, mereka digunakan sebagai indeks (setelah mengurangi nilai terendah) ke dalam tabel petunjuk instruksi — tableswitchinstruksi.

Jika konstanta jarang, pencarian biner untuk kasus yang benar dilakukan — lookupswitchinstruksi.

Dalam de-sugaring sebuah switchpada Stringbenda-benda, baik instruksi yang mungkin untuk digunakan. The lookupswitchcocok untuk switch pertama pada kode hash untuk menemukan posisi asli dari kasus ini. Ordinal yang dihasilkan adalah cocok alami untuk a tableswitch.

Kedua instruksi membutuhkan konstanta integer yang ditugaskan untuk setiap kasus untuk diurutkan pada waktu kompilasi. Saat runtime, sementara O(1)kinerja tableswitchumumnya tampak lebih baik daripada O(log(n))kinerja lookupswitch, itu memerlukan beberapa analisis untuk menentukan apakah tabel cukup padat untuk membenarkan tradeoff ruang-waktu. Bill Venners menulis sebuah artikel hebat yang membahas hal ini secara lebih rinci, bersama dengan pandangan di bawah kap pada instruksi kontrol aliran Java lainnya.

Sebelum JDK 7

Sebelum ke JDK 7, enumbisa mendekati Stringswitch berbasis. Ini menggunakan metode statisvalueOf yang dihasilkan oleh kompiler pada setiap enumjenis. Sebagai contoh:

Pill p = Pill.valueOf(str);
switch(p) {
  case RED:  pop();  break;
  case BLUE: push(); break;
}
erickson
sumber
26
Mungkin lebih cepat menggunakan If-Else-If daripada hash untuk switch berbasis string. Saya menemukan kamus cukup mahal ketika hanya menyimpan beberapa item.
Jonathan Allen
84
If-elseif-elseif-elseif-else mungkin lebih cepat, tapi saya akan mengambil kode pembersih 99 kali dari 100. String, karena tidak berubah, cache kode hash mereka, jadi "komputasi" hash cepat. Seseorang harus memiliki kode profil untuk menentukan manfaat apa yang ada.
erickson
21
Alasan yang diberikan terhadap penambahan switch (String) adalah bahwa itu tidak memenuhi jaminan kinerja yang diharapkan dari pernyataan switch (). Mereka tidak ingin "menyesatkan" pengembang. Terus terang saya tidak berpikir mereka harus menjamin kinerja switch () untuk memulai.
Gili
2
Jika Anda hanya menggunakan Pilluntuk mengambil beberapa tindakan berdasarkan pada strsaya berpendapat jika-lain lebih disukai karena memungkinkan Anda untuk menangani strnilai-nilai di luar kisaran MERAH, BIRU tanpa perlu menangkap pengecualian dari valueOfatau secara manual memeriksa kecocokan dengan nama setiap jenis enumerasi yang hanya menambahkan overhead yang tidak perlu. Dalam pengalaman saya itu hanya masuk akal untuk digunakan valueOfuntuk mengubah menjadi enumerasi jika representasi typesafe dari nilai String diperlukan nanti.
MilesHampson
Saya bertanya-tanya apakah penyusun berusaha untuk menguji apakah ada pasangan angka (x, y) yang set nilai (hash >> x) & ((1<<y)-1)akan menghasilkan nilai yang berbeda untuk setiap string yang hashCodeberbeda, dan (1<<y)kurang dari dua kali jumlah string (atau pada Setidaknya tidak lebih besar dari itu).
supercat
125

Jika Anda memiliki tempat di kode Anda di mana Anda dapat mengaktifkan String, maka mungkin lebih baik untuk refactor String menjadi enumerasi dari nilai yang mungkin, yang dapat Anda aktifkan. Tentu saja, Anda membatasi nilai potensial dari Strings yang dapat Anda miliki dengan yang ada dalam penghitungan, yang mungkin atau mungkin tidak diinginkan.

Tentu saja enumerasi Anda dapat memiliki entri untuk 'lain', dan metode fromString (String), maka Anda bisa memiliki

ValueEnum enumval = ValueEnum.fromString(myString);
switch (enumval) {
   case MILK: lap(); break;
   case WATER: sip(); break;
   case BEER: quaff(); break;
   case OTHER: 
   default: dance(); break;
}
Astaga
sumber
4
Teknik ini juga memungkinkan Anda memutuskan masalah-masalah seperti ketidaksensitifan huruf, alias, dll. Alih-alih bergantung pada perancang bahasa untuk menghasilkan solusi "satu ukuran cocok untuk semua".
Darron
2
Setuju dengan JeeBee, jika Anda mengaktifkan string mungkin perlu enum. String biasanya mewakili sesuatu yang pergi ke antarmuka (pengguna atau sebaliknya) yang mungkin atau tidak berubah di masa depan jadi lebih baik menggantinya dengan enums
hhafez
18
Lihat xefer.com/2006/12/switchonstring untuk mendapatkan artikel yang bagus tentang metode ini.
David Schmitt
@ DavidSchmitt Penulisan ini memiliki satu kelemahan utama. Ini menangkap semua pengecualian, bukan yang sebenarnya dilemparkan oleh metode.
M. Mimpen
91

Berikut ini adalah contoh lengkap berdasarkan posting JeeBee, menggunakan java enum alih-alih menggunakan metode kustom.

Perhatikan bahwa di Java SE 7 dan yang lebih baru, Anda bisa menggunakan objek String dalam ekspresi statement switch.

public class Main {

    /**
    * @param args the command line arguments
    */
    public static void main(String[] args) {

      String current = args[0];
      Days currentDay = Days.valueOf(current.toUpperCase());

      switch (currentDay) {
          case MONDAY:
          case TUESDAY:
          case WEDNESDAY:
              System.out.println("boring");
              break;
          case THURSDAY:
              System.out.println("getting better");
          case FRIDAY:
          case SATURDAY:
          case SUNDAY:
              System.out.println("much better");
              break;

      }
  }

  public enum Days {

    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
  }
}
Thulani Chivandikwa
sumber
26

Switch berdasarkan bilangan bulat dapat dioptimalkan menjadi kode yang sangat efisien. Switch berdasarkan tipe data lain hanya dapat dikompilasi ke serangkaian pernyataan if ().

Untuk alasan itu, C&C ++ hanya mengizinkan sakelar pada tipe integer, karena tidak ada gunanya dengan tipe lain.

Para desainer C # memutuskan bahwa gaya itu penting, bahkan jika tidak ada keuntungan.

Para perancang Jawa rupanya berpikir seperti perancang C.

James Curran
sumber
26
Switch berdasarkan objek hashable dapat diimplementasikan dengan sangat efisien menggunakan tabel hash - lihat .NET. Jadi alasanmu tidak sepenuhnya benar.
Konrad Rudolph
Ya, dan ini adalah hal yang saya tidak mengerti. Apakah mereka takut benda hashing, dalam jangka panjang, akan menjadi terlalu mahal?
Alex Beardsley
3
@Nalandial: sebenarnya, dengan sedikit usaha di bagian compiler, itu tidak mahal sama sekali karena ketika set string diketahui, sangat mudah untuk menghasilkan hash yang sempurna (ini tidak dilakukan oleh .NET, meskipun; mungkin tidak sepadan dengan usaha, baik).
Konrad Rudolph
3
@Nalandial & @Konrad Rudolph - Sambil mem-hashing sebuah String (karena sifatnya yang tidak dapat berubah) sepertinya merupakan solusi untuk masalah ini, Anda harus ingat bahwa semua Objek non-final dapat memiliki fungsi hashing yang dikesampingkan. Ini membuatnya sulit pada waktu kompilasi untuk memastikan konsistensi dalam sebuah saklar.
martinatime
2
Anda juga dapat membuat DFA agar sesuai dengan string (seperti yang dilakukan mesin ekspresi reguler). Mungkin bahkan lebih efisien daripada hashing.
Nate CK
19

Contoh Stringpenggunaan langsung sejak 1.7 juga dapat ditampilkan:

public static void main(String[] args) {

    switch (args[0]) {
        case "Monday":
        case "Tuesday":
        case "Wednesday":
            System.out.println("boring");
            break;
        case "Thursday":
            System.out.println("getting better");
        case "Friday":
        case "Saturday":
        case "Sunday":
            System.out.println("much better");
            break;
    }

}
Gunnar Forsgren - Mobimation
sumber
18

James Curran dengan singkat mengatakan: "Switch berbasis integer dapat dioptimalkan menjadi kode yang sangat efisien. Switch berdasarkan tipe data lain hanya dapat dikompilasi ke serangkaian pernyataan if (). Untuk alasan itu, C&C ++ hanya mengizinkan switch pada tipe integer, karena tidak ada gunanya dengan tipe lain. "

Pendapat saya, dan hanya itu, adalah bahwa begitu Anda mulai beralih pada non-primitif Anda harus mulai berpikir tentang "sama dengan" versus "==". Pertama membandingkan dua string bisa menjadi prosedur yang cukup panjang, menambah masalah kinerja yang disebutkan di atas. Kedua jika ada mengaktifkan string akan ada permintaan untuk beralih pada kasus mengabaikan string, beralih pada string mempertimbangkan / mengabaikan lokal, beralih pada string berdasarkan regex .... Saya akan menyetujui keputusan yang menghemat banyak waktu untuk pengembang bahasa dengan biaya sejumlah kecil waktu untuk programmer.

DJClayworth
sumber
Secara teknis, regex sudah "beralih", karena mereka pada dasarnya hanya mesin negara; mereka hanya memiliki dua "kasus", matcheddan not matched. (Meskipun tidak memperhitungkan hal-hal seperti kelompok [yang dinamai] / etc.)
JAB
1
docs.oracle.com/javase/7/docs/technotes/guides/language/… menyatakan: Kompiler Java menghasilkan bytecode yang umumnya lebih efisien dari pernyataan switch yang menggunakan objek String daripada dari dirantai pernyataan if-then-else.
Wim Deblauwe
12

Selain argumen yang baik di atas, saya akan menambahkan bahwa banyak orang hari ini melihat switchsebagai sisa masa lalu prosedural Jawa (kembali ke C kali).

Saya tidak sepenuhnya berbagi pendapat ini, saya pikir switchdapat memiliki kegunaannya dalam beberapa kasus, setidaknya karena kecepatannya, dan lagi pula itu lebih baik daripada beberapa seri cascading numerik yang else ifsaya lihat dalam beberapa kode ...

Tetapi memang, ada baiknya melihat kasus di mana Anda perlu beralih, dan melihat apakah itu tidak dapat diganti oleh sesuatu yang lebih OO. Misalnya enum di Java 1.5+, mungkin HashTable atau koleksi lain (kadang saya menyesal kami tidak memiliki fungsi (anonim) sebagai warga negara kelas satu, seperti di Lua - yang tidak memiliki saklar - atau JavaScript) atau bahkan polimorfisme.

PhiLho
sumber
"Kadang aku menyesal kami tidak memiliki fungsi (anonim) sebagai warga negara kelas satu" Itu tidak lagi benar.
user8397947
@dorukayhan Ya, tentu saja. Tetapi apakah Anda ingin menambahkan komentar di semua jawaban dari sepuluh tahun terakhir untuk memberi tahu dunia bahwa kami dapat memilikinya jika kami memperbarui ke versi Java yang lebih baru? :-D
PhiLho
8

Jika Anda tidak menggunakan JDK7 atau lebih tinggi, Anda bisa menggunakannya hashCode()untuk mensimulasikannya. Karena String.hashCode()biasanya mengembalikan nilai yang berbeda untuk string yang berbeda dan selalu mengembalikan nilai yang sama untuk string yang sama, itu cukup dapat diandalkan (String yang berbeda dapat menghasilkan kode hash yang sama seperti @Lii yang disebutkan dalam komentar, seperti "FB"dan "Ea") Lihat dokumentasi .

Jadi, kodenya akan terlihat seperti ini:

String s = "<Your String>";

switch(s.hashCode()) {
case "Hello".hashCode(): break;
case "Goodbye".hashCode(): break;
}

Dengan begitu, Anda secara teknis mengaktifkan int.

Atau, Anda dapat menggunakan kode berikut:

public final class Switch<T> {
    private final HashMap<T, Runnable> cases = new HashMap<T, Runnable>(0);

    public void addCase(T object, Runnable action) {
        this.cases.put(object, action);
    }

    public void SWITCH(T object) {
        for (T t : this.cases.keySet()) {
            if (object.equals(t)) { // This means that the class works with any object!
                this.cases.get(t).run();
                break;
            }
        }
    }
}
HyperNeutrino
sumber
5
Dua string yang berbeda dapat memiliki kode hash yang sama, jadi jika Anda mengaktifkan kode hash cabang kasus yang salah dapat diambil.
Lii
@ Lii Terima kasih telah menunjukkan ini! Itu tidak mungkin, tapi saya tidak percaya itu berhasil. "FB" dan "Ea" memiliki kode hash yang sama, jadi bukan tidak mungkin untuk menemukan tabrakan. Kode kedua mungkin lebih dapat diandalkan.
HyperNeutrino
Saya terkejut kompilasi ini, karena casepernyataan harus, saya pikir, selalu nilai konstan, dan String.hashCode()tidak seperti itu (bahkan jika dalam praktiknya perhitungan tidak pernah berubah antara JVM).
StaxMan
@StaxMan Hm menarik, saya tidak pernah berhenti untuk mengamati itu. Tapi ya, casenilai-nilai pernyataan tidak harus ditentukan pada saat kompilasi jadi itu berfungsi dengan baik.
HyperNeutrino
4

Selama bertahun-tahun kami telah menggunakan preprosesor (n open source) untuk ini.

//#switch(target)
case "foo": code;
//#end

File yang telah diproses diberi nama Foo.jpp dan diproses menjadi Foo.java dengan skrip semut.

Keuntungannya adalah ia diproses menjadi Java yang berjalan pada 1.0 (walaupun biasanya kami hanya mendukung kembali ke 1.4). Juga jauh lebih mudah untuk melakukan ini (banyak switch string) dibandingkan dengan fudging dengan enum atau solusi lain - kode jauh lebih mudah untuk dibaca, dipelihara, dan dipahami. IIRC (tidak dapat memberikan statistik atau alasan teknis pada saat ini) juga lebih cepat dari padanan Java alami.

Kerugiannya adalah Anda tidak mengedit Java sehingga alur kerjanya sedikit lebih (edit, proses, kompilasi / tes) ditambah IDE akan menghubungkan kembali ke Java yang sedikit berbelit-belit (sakelar menjadi serangkaian langkah logika if / else) dan urutan sakelar tidak dipertahankan.

Saya tidak akan merekomendasikan ini untuk 1.7+ tetapi ini berguna jika Anda ingin memprogram Java yang menargetkan JVM sebelumnya (karena Joe public jarang memiliki yang terbaru diinstal).

Anda bisa mendapatkannya dari SVN atau menelusuri kode secara online . Anda akan membutuhkan EBuild untuk membangunnya apa adanya.

Charles Goodwin
sumber
6
Anda tidak memerlukan 1,7 JVM untuk menjalankan kode dengan sakelar String. Kompiler 1,7 mengubah string Switch menjadi sesuatu yang menggunakan kode byte yang sudah ada sebelumnya.
Dawood ibn Kareem
4

Jawaban lain mengatakan ini ditambahkan di Java 7 dan diberikan solusi untuk versi sebelumnya. Jawaban ini mencoba menjawab "mengapa"

Java adalah reaksi terhadap kompleksitas C ++ yang berlebihan. Itu dirancang untuk menjadi bahasa bersih yang sederhana.

String mendapat sedikit penanganan kasus khusus dalam bahasa ini, tetapi tampaknya jelas bagi saya bahwa perancang berusaha untuk menjaga jumlah casing khusus dan gula sintaksis seminimal mungkin.

menyalakan string cukup rumit di bawah kap karena string bukan tipe primitif sederhana. Itu bukan fitur umum pada saat Java dirancang dan tidak benar-benar cocok dengan desain minimalis. Terutama karena mereka telah memutuskan untuk tidak memberikan case khusus == untuk string, itu akan (dan) agak aneh untuk case untuk bekerja di mana == tidak.

Antara 1,0 dan 1,4 bahasa itu sendiri tetap hampir sama. Sebagian besar perangkat tambahan ke Jawa berada di sisi perpustakaan.

Itu semua berubah dengan Java 5, bahasanya diperluas secara substansial. Perpanjangan lebih lanjut diikuti dalam versi 7 dan 8. Saya berharap bahwa perubahan sikap ini didorong oleh munculnya C #

plugwash
sumber
Narasi tentang sakelar (String) cocok dengan riwayat, garis waktu, konteks cpp / cs.
Espresso
Itu adalah kesalahan besar untuk tidak mengimplementasikan fitur ini, yang lainnya adalah alasan murah Java kehilangan banyak pengguna selama bertahun-tahun karena kurangnya kemajuan dan keras kepala desainer untuk tidak mengembangkan bahasa. Untungnya mereka benar-benar mengubah arah dan sikap setelah JDK7
firephil
0

JEP 354: Alihkan Ekspresi (Pratinjau) di JDK-13 dan JEP 361: Alihkan Ekspresi (Standar) di JDK-14 akan memperluas pernyataan sakelar sehingga dapat digunakan sebagai ekspresi .

Sekarang kamu bisa:

  • langsung menetapkan variabel dari ekspresi switch ,
  • gunakan bentuk baru label sakelar ( case L ->):

    Kode di sebelah kanan label sakelar "case L ->" dibatasi menjadi ekspresi, blok, atau (untuk kenyamanan) pernyataan melempar.

  • gunakan beberapa konstanta per kasus, dipisahkan dengan koma,
  • dan juga tidak ada lagi pemutusan nilai :

    Untuk menghasilkan nilai dari ekspresi switch, breakpernyataan nilai dengan dijatuhkan demi suatu yieldpernyataan.

Jadi demo dari jawaban ( 1 , 2 ) mungkin terlihat seperti ini:

  public static void main(String[] args) {
    switch (args[0]) {
      case "Monday", "Tuesday", "Wednesday" ->  System.out.println("boring");
      case "Thursday" -> System.out.println("getting better");
      case "Friday", "Saturday", "Sunday" -> System.out.println("much better");
    }
Iskuskov Alexander
sumber
-2

Tidak terlalu cantik, tapi di sini ada cara lain untuk Java 6 dan di bawah:

String runFct = 
        queryType.equals("eq") ? "method1":
        queryType.equals("L_L")? "method2":
        queryType.equals("L_R")? "method3":
        queryType.equals("L_LR")? "method4":
            "method5";
Method m = this.getClass().getMethod(runFct);
m.invoke(this);
Conete Cristian
sumber
-3

Sangat mudah di Groovy; Saya menanamkan toples asyik dan membuat groovykelas utilitas untuk melakukan semua hal ini dan banyak lagi yang menurut saya menjengkelkan untuk dilakukan di Jawa (karena saya terjebak menggunakan Java 6 di perusahaan.)

it.'p'.each{
switch (it.@name.text()){
   case "choclate":
     myholder.myval=(it.text());
     break;
     }}...
Alex Punnen
sumber
9
@Spoke Karena ini adalah pertanyaan java dan jawaban Groovy adalah off-topic dan plug yang tidak berguna.
Martin
12
Bahkan di rumah SW besar yang konservatif Groovy digunakan bersama dengan Jawa. JVM memberikan lingkungan agnostik bahasa lebih dari satu bahasa sekarang, untuk mencampur dan menggunakan paradigma pemrograman yang paling relevan untuk solusi. Jadi mungkin sekarang saya harus menambahkan cuplikan di Clojure untuk mengumpulkan lebih banyak downvotes :) ...
Alex Punnen
1
Juga, bagaimana cara kerja sintaks? Saya menduga Groovy adalah bahasa pemrograman yang berbeda ...? Maaf. Saya tidak tahu apa-apa tentang Groovy.
HyperNeutrino
-4

Saat Anda menggunakan intellij, lihat juga:

File -> Struktur Proyek -> Proyek

File -> Struktur Proyek -> Modul

Ketika Anda memiliki banyak modul, pastikan Anda mengatur level bahasa yang benar di tab modul.

botenvouwer
sumber
1
Tidak yakin bagaimana jawaban Anda relevan dengan pertanyaan. Dia bertanya mengapa pernyataan pergantian string seperti berikut tidak tersedia: String mystring = "something"; switch (mystring) {case "something" sysout ("got here"); . . }
Deepak Agarwal
-8
public class StringSwitchCase { 

    public static void main(String args[]) {

        visitIsland("Santorini"); 
        visitIsland("Crete"); 
        visitIsland("Paros"); 

    } 

    public static void visitIsland(String island) {
         switch(island) {
          case "Corfu": 
               System.out.println("User wants to visit Corfu");
               break; 
          case "Crete": 
               System.out.println("User wants to visit Crete");
               break; 
          case "Santorini": 
               System.out.println("User wants to visit Santorini");
               break; 
          case "Mykonos": 
               System.out.println("User wants to visit Mykonos");
               break; 
         default: 
               System.out.println("Unknown Island");
               break; 
         } 
    } 

} 
Issac Balaji
sumber
8
OP tidak menanyakan cara mengaktifkan string. Dia bertanya mengapa dia tidak bisa, karena pembatasan sintaksis sebelum JDK7.
HyperNeutrino