Neden bir String üzerinde switch deyimini kullanamıyorum?


1004

Bu işlev daha sonraki bir Java sürümüne eklenecek mi?

Birisi Java'nın switchifadesinin çalışma biçiminde olduğu gibi neden bunu yapamayacağımı açıklayabilir mi?


195

81
Sun değerlendirmelerinde dürüsttü: "Don't hold your breath."lol, bugs.sun.com/bugdatabase/view_bug.do?bug_id=1223179
rafya

3
@raffian Sanırım bunun nedeni iki kez iç çekmesi. Neredeyse 10 yıl sonra cevap vermek için biraz geç kalmışlardı. O zaman torunlarına öğle yemeği kutuları gönderiyor olabilirdi.
WeirdElfB0y

Yanıtlar:


1003

Vakalarla ilgili anahtar bildirimleri String, Java SE 7'de , ilk istendiklerinden en az 16 yıl sonra uygulanmıştır. Gecikmenin açık bir nedeni belirtilmedi, ancak muhtemelen performansla ilgisi vardı.

JDK 7'de uygulama

Bu özellik şimdi javac bir "şekersizleştirici" işlemle uygulanmaktadır; bildirimlerde Stringsabitleri kullanan temiz, yüksek düzeyli bir sözdizimi casederleme zamanında bir kalıbı izleyen daha karmaşık koda genişletilir. Ortaya çıkan kod, her zaman var olan JVM talimatlarını kullanır.

Kılıflı A switch, Stringderleme sırasında iki anahtara çevrilir. Birincisi, her bir dizeyi benzersiz bir tamsayı ile eşler - orijinal anahtardaki konumu. Bu, önce etiketin karma kodunu açarak yapılır. Karşılık gelen durum ifdize eşitliğini test eden bir ifadedir; karma üzerinde çarpışmalar varsa, test basamaklıdır if-else-if. İkinci anahtar orijinal kaynak kodundaki aynayı yansıtır, ancak büyük / küçük harf etiketlerini karşılık gelen konumlarıyla değiştirir. Bu iki aşamalı işlem, orijinal anahtarın akış kontrolünün korunmasını kolaylaştırır.

JVM'deki anahtarlar

Daha fazla teknik derinlik için switch, anahtar ifadelerinin derlenmesinin açıklandığı JVM Spesifikasyonuna başvurabilirsiniz . Özetle, vakalar tarafından kullanılan sabitlerin genişliğine bağlı olarak, bir anahtar için kullanılabilecek iki farklı JVM talimatı vardır. Her ikisi de verimli bir şekilde yürütmek için her durum için tamsayı sabitlerinin kullanılmasına bağlıdır.

Sabitler yoğunsa, bir yönerge işaretçileri tablosuna (yönerge) bir dizin olarak (en düşük değeri çıkardıktan sonra) kullanılırlar tableswitch.

Sabitler seyrekse, doğru durum için ikili bir arama yapılır - lookupswitchtalimat.

Nesnelerin switchüzerindeki şekerden arındırma Stringişleminde, her iki yönerge de kullanılabilir. lookupswitchKarma kodları ilk geçiş durumunda orijinal konumunu bulmak için uygundur. Ortaya çıkan ordinal, a tableswitch.

Her iki yönerge de derleme zamanında her bir duruma atanan tamsayı sabitlerinin sıralanmasını gerektirir. Çalışma zamanında, O(1)performansı tableswitchgenellikle performansından daha iyi görünse O(log(n))de lookupswitch, tablonun uzay-zaman dengesini haklı çıkarmak için yeterince yoğun olup olmadığını belirlemek için biraz analiz gerektirir. Bill Venners , diğer Java akış kontrol talimatlarına başlık altında bir bakışla birlikte, bunu daha ayrıntılı bir şekilde ele alan harika bir makale yazdı .

JDK 7'den önce

JDK 7'den önce, enumbir Stringtabanlı anahtara yaklaşabiliyordu . Bu , derleyici tarafından üretilen her türde statikvalueOf yöntemi kullanır enum. Örneğin:

Pill p = Pill.valueOf(str);
switch(p) {
  case RED:  pop();  break;
  case BLUE: push(); break;
}

26
Dize tabanlı bir anahtar için karma yerine If-Else-If kullanmak daha hızlı olabilir. Sözlükleri sadece birkaç eşyayı saklarken oldukça pahalı buldum.
Jonathan Allen

84
Bir if-elseif-elseif-elseif-else daha hızlı olabilir, ancak 100'den 99 kat daha temiz bir kod alırdım. Dizeler, değişmez, karma kodlarını önbelleğe alır, böylece karma hızlıdır. Kişinin ne fayda sağlayacağını belirlemek için kodun profiline alınması gerekir.
erickson

21
Switch (String) eklemeye karşı verilen neden, switch () ifadelerinden beklenen performans garantilerini karşılamamasıdır. Geliştiricileri “yanıltmak” istemediler. Açıkçası ben başlamak için switch () performansını garanti gerektiğini sanmıyorum.
Gili

2
Eğer sadece bir Pilleylemde strbulunmaya çalışıyorsanız, başka bir şey tercih ediyorsanız, strKIRMIZI, MAVİ aralığın dışındaki değerleri ele almanıza valueOfveya adıyla karşı bir eşleşmeyi manuel olarak kontrol etmenize izin vermez . gereksiz numaralar ekleyen her numaralandırma türü. Deneyimlerime göre, ancak valueOfdaha sonra String değerinin tipesafe bir temsili gerekiyorsa bir numaralandırmaya dönüştürmek için kullanmak mantıklı oldu.
MilesHampson

Derleyiciler, değer kümesinin farklı olan ve (hash >> x) & ((1<<y)-1)dize sayısının iki katından daha az olan (veya en azından bundan daha büyük değil). hashCode(1<<y)
supercat

125

Kodunuzda bir String'i açabileceğiniz bir yer varsa, String'i açabileceğiniz olası değerlerin bir numaralandırması olarak yeniden düzenlemek daha iyi olabilir. Tabii ki, sahip olabileceğiniz Dizelerin potansiyel değerlerini numaralandırmada istenebilecek veya istenmeyecek değerlerle sınırlandırırsınız.

Tabii ki numaralandırma 'other' için bir giriş ve bir fromString (String) yöntemi olabilir, o zaman

ValueEnum enumval = ValueEnum.fromString(myString);
switch (enumval) {
   case MILK: lap(); break;
   case WATER: sip(); break;
   case BEER: quaff(); break;
   case OTHER: 
   default: dance(); break;
}

4
Bu teknik aynı zamanda bir vaka duyarsızlığı, takma adlar, vb gibi konularda karar vermenizi sağlar. Bunun yerine, bir dil tasarımcısı ile "tek beden herkese uyar" çözümünü bulmak yerine.
Darron

2
JeeBee ile aynı fikirde, eğer dizeleri açıyorsanız muhtemelen bir enum gerekir. Dize genellikle gelecekte değişebilecek veya değişmeyecek bir arayüze (kullanıcı veya başka bir şekilde) giden bir şeyi temsil eder, bu yüzden enums ile değiştirir
hhafez

18
Bu yöntemin güzel bir şekilde yazılması için xefer.com/2006/12/switchonstring adresine bakın .
David Schmitt

@DavidSchmitt Yazının büyük bir kusuru var. Aslında yöntem tarafından atılanlar yerine tüm istisnaları yakalar .
M. Mimpen

91

Aşağıda, özel bir yöntem kullanmak yerine java enum's kullanarak JeeBee'nin gönderisine dayanan tam bir örnek verilmiştir.

Java SE 7 ve sonraki sürümlerinde, bunun yerine switch ifadesinin ifadesinde bir String nesnesi kullanabileceğinizi unutmayın.

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

26

Tamsayılara dayalı anahtarlar çok verimli koda göre optimize edilebilir. Diğer veri türüne dayalı anahtarlar yalnızca bir dizi if () ifadesine derlenebilir.

Bu nedenle C & C ++ yalnızca tamsayı türlerinde anahtarlara izin verir, çünkü diğer türlerle anlamsızdır.

C # tasarımcıları, hiçbir avantaj olmasa bile, stilin önemli olduğuna karar verdiler.

Java tasarımcıları görünüşe göre C tasarımcıları gibi düşündüler.


26
Herhangi bir yıkanabilir nesneye dayalı anahtarlar, karma tablo kullanılarak çok verimli bir şekilde uygulanabilir - bkz. .NET. Yani sebebiniz tamamen doğru değil.
Konrad Rudolph

Evet, anlamadığım şey bu. Hash nesnelerinin uzun vadede çok pahalı hale gelmesinden korkuyorlar mı?
Alex Beardsley

3
@Nalandial: aslında, derleyicinin biraz çaba sarf etmesiyle, hiç de pahalı değil çünkü dize kümesi biliniyorsa, mükemmel bir karma oluşturmak oldukça kolaydır (bu .NET tarafından yapılmaz; muhtemelen çabaya değmez, ya da).
Konrad Rudolph

3
@Nalandial & @Konrad Rudolph - Bir dize hashing (değişmez doğası nedeniyle) bu soruna bir çözüm gibi görünüyorken, tüm nihai olmayan Nesnelerin hashleme işlevlerini geçersiz kılabileceğini hatırlamanız gerekir. Bu, bir anahtarda tutarlılığın sağlanmasını derleme zamanında zorlaştırır.
martinatime

2
Dizeyle eşleşecek bir DFA da oluşturabilirsiniz (normal ifade motorlarının yaptığı gibi). Muhtemelen hash etmekten bile daha verimlidir.
Nate CK

19

String1.7'den beri doğrudan kullanım için bir örnek de gösterilebilir:

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

}

18

James Curran özünde şöyle diyor: "Tamsayılara dayalı anahtarlar çok verimli koda göre optimize edilebilir. Diğer veri türüne dayalı anahtarlar yalnızca if () ifadeleri dizisine derlenebilir. Bu nedenle C & C ++ yalnızca tamsayı türlerinde anahtarlara izin verir, çünkü diğer türlerle anlamsızdı. "

Benim düşüncem ve sadece bu, ilkel olmayanları açmaya başlar başlamaz "eşittir" e karşı = = "düşünmeye başlamanız gerektiğidir. İlk olarak, iki dizeyi karşılaştırmak, yukarıda belirtilen performans sorunlarına ek olarak oldukça uzun bir prosedür olabilir. İkincisi, dizeleri açma durumunda, durumu görmezden gelen dizeleri açma, yerel ayarları göz önünde bulundurarak / görmezden gelme, regex tabanlı dizeleri açma talepleri olacaktır .... Ben çok zaman kazandıran bir karar onaylamak dil geliştiricileri programcılar için küçük bir zaman pahasına.


Teknik olarak, regex'ler temelde sadece devlet makineleri oldukları için zaten "değiştirilir"; sadece iki "davaları" vardır matchedve not matched. (Yine de [adlandırılmış] gruplar / vb. Şeyleri hesaba katmamak.)
JAB

1
docs.oracle.com/javase/7/docs/technotes/guides/language/… durumları: Java derleyicisi, String nesnelerini kullanan switch deyimlerinden zincir halinde if-else deyimlerine göre genellikle daha verimli bayt kodu oluşturur.
Wim Deblauwe

12

Yukarıdaki iyi argümanların yanı sıra, bugün birçok insanın switchJava'nın yordamsal geçmişinin eski bir kalıntısı olarak göreceğini de ekleyeceğim (C zamanlarına kadar).

Bu görüşü tam olarak paylaşmıyorum switch, bazı durumlarda yararlı olabileceğini düşünüyorum , en azından hızı nedeniyle ve yine de else ifbazı kodlarda gördüğüm basamaklı sayısal serilerden daha iyi ...

Ama gerçekten, bir anahtara ihtiyacınız olan duruma bakmaya ve bunun daha fazla OO ile değiştirilip değiştirilemeyeceğine bakmaya değer. Örneğin, Java 1.5+, belki HashTable veya başka bir koleksiyondaki (bazen pişmanlık duyuyorum (anonim), Lua'da olduğu gibi, anahtar - veya JavaScript içermeyen), hatta polimorfizm gibi (anonim) işlevlerimiz yok.


"Bazen birinci sınıf vatandaş olarak (anonim) işlevimiz olmadığına üzülüyorum" Bu artık doğru değil.
user8397947

@dorukayhan Evet, elbette. Ancak, Java'nın daha yeni sürümlerine güncelleme yaparsak dünyaya sahip olabileceğimizi söylemek için son on yıldaki tüm cevaplara bir yorum eklemek ister misiniz? :-D
PhiLho

8

JDK7 veya üstünü kullanmıyorsanız hashCode(), simüle etmek için kullanabilirsiniz . Çünkü String.hashCode()genellikle farklı dizeleri için farklı değerler döndürür ve her zaman eşit dizeleri eşit değerleri döndürür, oldukça güvenilir (Farklı dizeleri olduğunu olabilir @Lii gibi bir açıklamada belirtildiği gibi aynı hash kodu üretmek "FB"ve "Ea"bakın) belgelerinde .

Yani, kod şöyle görünecektir:

String s = "<Your String>";

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

Bu şekilde, teknik olarak bir int.

Alternatif olarak, aşağıdaki kodu kullanabilirsiniz:

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

5
İki farklı dize aynı hashcode'a sahip olabilir, bu nedenle hashcode'ları açarsanız yanlış case-branch alınabilir.
Lii

@Lii Bunu işaret ettiğiniz için teşekkürler! Yine de pek olası değil, ama işe yaradığına güvenmem. "FB" ve "Ea" aynı hash koduna sahiptir, bu nedenle bir çarpışma bulmak imkansız değildir. İkinci kod muhtemelen daha güvenilirdir.
HyperNeutrino

Ben caseifadeler her zaman sabit değerler olması gibi, bu derler şaşırdım ve String.hashCode()böyle değil (pratikte JVM'ler arasında hiç değişmemiş olsa bile).
18:18, StaxMan

@StaxMan Hm ilginç, bunu gözlemlemek için hiç durmadım. Ancak evet, casedeyim değerlerinin derleme zamanında belirlenebilir olması gerekmez , bu yüzden iyi çalışır.
HyperNeutrino

4

Yıllardır bunun için (n açık kaynak) önişlemci kullanıyoruz.

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

Önceden işlenmiş dosyalar Foo.jpp olarak adlandırılır ve bir karınca komut dosyası ile Foo.java'ya işlenir.

Avantajı, 1.0'da çalışan Java'ya işlenmesidir (genellikle yalnızca 1.4'e kadar destekledik). Ayrıca bunu (dizi anahtarları) numaralandırma veya diğer geçici çözümlerle doldurmaya kıyasla yapmak çok daha kolaydı - kodun okunması, bakımı ve anlaşılması çok daha kolaydı. IIRC (bu noktada istatistik veya teknik akıl yürütme sağlayamaz), aynı zamanda doğal Java eşdeğerlerinden daha hızlıydı.

Dezavantajları Java'yı düzenlemiyor olmanızdır, bu yüzden biraz daha fazla iş akışı (düzenleme, işleme, derleme / test etme) artı bir IDE, biraz kıvrımlı olan Java'ya geri bağlanacaktır (anahtar, if / else mantık adımlarından oluşan bir dizi haline gelir) ve anahtar kutusu sırası korunmaz.

1.7+ için tavsiye etmem ama daha önce JVM'leri hedefleyen Java programlamak istiyorsanız yararlıdır (Joe public nadiren en son yüklü olduğundan).

SVN'den alabilir veya çevrimiçi kodlara göz atabilirsiniz . Olduğu gibi inşa etmek için EBuild'e ihtiyacınız olacak .


6
Bir String anahtarıyla kodu çalıştırmak için 1.7 JVM'ye ihtiyacınız yoktur. 1.7 derleyici, String anahtarını önceden varolan bayt kodunu kullanan bir şeye dönüştürür.
Dawood ibn Kareem

4

Diğer yanıtlar bunun Java 7'ye eklendiğini ve önceki sürümler için geçici çözümler sağladığını söyledi. Bu cevap "neden"

Java, C ++ 'ın aşırı karmaşıklıklarına bir tepkiydi. Basit ve temiz bir dil olacak şekilde tasarlanmıştır.

String, dilde biraz özel durum işleme aldı, ancak tasarımcıların özel kasa ve sözdizimsel şeker miktarını minimumda tutmaya çalıştığı açık.

telleri açmak basit ilkel tipler olmadığı için kaputun altında oldukça karmaşıktır. Java'nın tasarlandığı sırada ortak bir özellik değildi ve minimalist tasarıma gerçekten uymuyor. Özellikle dizeler için özel durum == 'a karar vermedikleri için, ==' ın olmadığı durumlarda davanın çalışması biraz garip olurdu.

1.0 ve 1.4 arasında dilin kendisi hemen hemen aynı kaldı. Java'daki geliştirmelerin çoğu kütüphane tarafındaydı.

Her şey Java 5 ile değişti, dil büyük ölçüde genişletildi. Ek uzantılar 7 ve 8 sürümlerinde izledi. Ben bu tutum değişikliği C # yükselişi tarafından yönlendirilmesi bekliyoruz


Switch (String) hakkındaki anlatı, geçmiş, zaman çizelgesi, bağlam cpp / cs ile uyumludur.
Espresso

Bu özelliği uygulamak büyük bir hataydı, her şey ucuz bir mazeret Java, ilerleme eksikliği ve tasarımcıların dili geliştirmemesinin inatçı olması nedeniyle yıllar boyunca birçok kullanıcıyı kaybetti. Neyse ki JDK7'den sonra yönünü ve tutumunu tamamen değiştirdiler
firephil

0

JEP 354: JDK-13'teki Anahtar İfadeleri (Önizleme) veJDK-14'teki Anahtar İfadeleri (Standart) , anahtar ifadesini ifade olarak kullanılabilmesi içingenişletir.

Şimdi yapabilirsin:

  • anahtar ifadesinden doğrudan değişken atayın ,
  • yeni bir switch label biçimi kullanın ( case L ->):

    Bir "case L ->" anahtar etiketinin sağındaki kod, bir ifade, bir blok ya da (kolaylık olması açısından) bir throw ifadesi ile sınırlıdır.

  • virgülle ayrılmış durumda her durumda birden çok sabit kullanın,
  • ve daha fazla değer sonu yok :

    Bir anahtar ifadesinden bir değer vermek için breakwith value ifadesi, bir yieldifade lehine bırakılır .

Cevaplardan ( 1 , 2 ) demo şu şekilde görünebilir:

  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");
    }

-2

Çok hoş değil, ama Java 6 ve feryat için başka bir yol:

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);

-3

Groovy'de bir esinti; Harika kavanozu gömdüm ve groovyJava'da yapmak için bıkkın bulduğum tüm bu şeyleri ve daha fazlasını yapmak için bir yardımcı program sınıfı oluşturdum (çünkü kuruluşta Java 6 kullanarak sıkışıp kaldım.)

it.'p'.each{
switch (it.@name.text()){
   case "choclate":
     myholder.myval=(it.text());
     break;
     }}...

9
@SSpoke Çünkü bu bir java sorusu ve Groovy cevabı konu dışı ve işe yaramaz bir fiş.
Martin

12
Muhafazakar büyük SW evlerinde bile Groovy, Java ile birlikte kullanılır. JVM, çözüm için en uygun programlama paradigmasını karıştırmak ve kullanmak için bir dilden agnostik bir ortam sunmaktadır. Belki de şimdi daha fazla oy toplamak için Clojure'a bir pasaj eklemeliyim :) ...
Alex Punnen

1
Ayrıca, sözdizimi nasıl çalışır? Sanırım Groovy farklı bir programlama dili ...? Afedersiniz. Groovy hakkında hiçbir şey bilmiyorum.
HyperNeutrino

-4

İntellij kullandığınızda şunlara da bakın:

Dosya -> Proje Yapısı -> Proje

Dosya -> Proje Yapısı -> Modüller

Birden fazla modülünüz olduğunda, modül sekmesinde doğru dil seviyesini ayarladığınızdan emin olun.


1
Cevabınızın soru ile nasıl alakalı olduğundan emin değilim. Neden aşağıdaki gibi bir string switch deyimlerinin kullanılamadığını sordu: String mystring = "something"; switch (gizem) {case "bir şey" sysout ("buraya var"); . . }
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; 
         } 
    } 

} 

8
OP bir dizeyi nasıl açacağınızı sormuyor. JDK7'den önceki sözdizimindeki kısıtlamalar nedeniyle neden yapamadığını soruyor.
HyperNeutrino
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.