webentwicklung-frage-antwort-db.com.de

Warum kann ich die switch-Anweisung nicht für einen String verwenden?

Wird diese Funktionalität in eine spätere Java Version übernommen?

Kann jemand erklären, warum ich das nicht kann, wie in der technischen Art und Weise, wie Javas switch -Anweisung funktioniert?

972
Alex Beardsley

Switch-Anweisungen mit String Fällen wurden in Java SE 7 , mindestens 16 Jahre nachdem sie zum ersten Mal angefordert wurden implementiert. Ein eindeutiger Grund für die Verzögerung wurde nicht angegeben , aber es hing wahrscheinlich mit der Leistung zusammen.

Implementierung in JDK 7

Die Funktion wurde jetzt in javacmit einem "De-Sugaring" -Prozess; eine saubere Syntax auf hoher Ebene unter Verwendung von String -Konstanten in case -Deklarationen implementiert Wird zur Kompilierungszeit nach einem Muster zu komplexerem Code erweitert. Der resultierende Code verwendet JVM-Anweisungen, die es immer gegeben hat.

Ein switch mit String Fällen wird beim Kompilieren in zwei Schalter übersetzt. Der erste ordnet jede Zeichenfolge einer eindeutigen Ganzzahl zu - ihrer Position im ursprünglichen Schalter. Dies geschieht, indem Sie zuerst den Hash-Code des Etiketts einschalten. Der entsprechende Fall ist eine if -Anweisung, die die String-Gleichheit testet. Wenn der Hash kollidiert, ist der Test ein kaskadierender if-else-if. Der zweite Schalter entspricht dem ursprünglichen Quellcode, ersetzt jedoch die Gehäusebezeichnungen durch die entsprechenden Positionen. Durch diesen zweistufigen Vorgang kann die Flusskontrolle des ursprünglichen Schalters auf einfache Weise beibehalten werden.

Schaltet die JVM ein

Weitere technische Informationen zu switch finden Sie in der JVM-Spezifikation, in der Zusammenstellung von switch-Anweisungen beschrieben wird. Kurz gesagt, es gibt zwei verschiedene JVM-Anweisungen, die für einen Switch verwendet werden können, abhängig von der Dichte der von den Fällen verwendeten Konstanten. Beide hängen von der Verwendung von Integer-Konstanten ab, damit jeder Fall effizient ausgeführt werden kann.

Wenn die Konstanten dicht sind, werden sie (nach dem Subtrahieren des niedrigsten Werts) als Index in eine Tabelle von Befehlszeigern verwendet - den Befehl tableswitch.

Wenn die Konstanten dünn sind, wird eine binäre Suche nach dem richtigen Fall durchgeführt - die Anweisung lookupswitch.

Beim Entzuckern eines switch auf String Objekten werden wahrscheinlich beide Anweisungen verwendet. Das lookupswitch eignet sich für den ersten Einschalt-Hashcode, um die ursprüngliche Position des Gehäuses zu ermitteln. Die resultierende Ordnungszahl ist eine natürliche Anpassung für tableswitch.

Beide Anweisungen erfordern, dass die jedem Fall zugewiesenen Ganzzahlkonstanten zur Kompilierzeit sortiert werden. Während zur Laufzeit die O(1) -Leistung von tableswitch im Allgemeinen besser erscheint als die O(log(n)) -Leistung von lookupswitch, ist eine Analyse erforderlich, um festzustellen, ob die Tabelle dicht genug ist, um die zu rechtfertigen Raum-Zeit-Kompromiss. Bill Venners schrieb einen großartigen Artikel , der dies ausführlicher behandelt, zusammen mit einem Blick unter die Haube auf andere Java Anweisungen zur Flusskontrolle.

Vor dem JDK 7

Vor JDK 7 konnte enum einen String-basierten Switch approximieren. Dies verwendet die statische valueOf Methode, die vom Compiler für jeden enum Typ generiert wird. Zum Beispiel:

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

Wenn Sie in Ihrem Code eine Stelle haben, an der Sie einen String einschalten können, ist es möglicherweise besser, den String so umzugestalten, dass er eine Aufzählung der möglichen Werte ist, die Sie einschalten können. Natürlich beschränken Sie die möglichen Werte von Strings auf diejenigen in der Aufzählung, die möglicherweise erwünscht sind oder nicht.

Natürlich könnte Ihre Aufzählung einen Eintrag für 'other' und eine fromString (String) -Methode haben, dann könnten Sie haben

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

Das Folgende ist ein vollständiges Beispiel, das auf JeeBees Beitrag basiert und Java Aufzählungen anstelle einer benutzerdefinierten Methode verwendet.

Beachten Sie, dass Sie in Java SE 7 und höher stattdessen ein String-Objekt im Ausdruck der switch-Anweisung verwenden können.

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

Schalter, die auf Ganzzahlen basieren, können für sehr effizienten Code optimiert werden. Schalter, die auf einem anderen Datentyp basieren, können nur zu einer Reihe von if () -Anweisungen kompiliert werden.

Aus diesem Grund erlaubt C & C++ nur das Umschalten von Integer-Typen, da dies bei anderen Typen sinnlos war.

Die Designer von C # waren der Meinung, dass der Stil wichtig ist, auch wenn es keinen Vorteil gibt.

Die Designer von Java dachten anscheinend wie die Designer von C.

25
James Curran

James Curran fasst zusammen: "Schalter, die auf Ganzzahlen basieren, können zu sehr effizientem Code optimiert werden. Schalter, die auf anderen Datentypen basieren, können nur zu einer Reihe von if () - Anweisungen kompiliert werden. Aus diesem Grund erlaubt C & C++ nur Schalter für Ganzzahltypen. da es bei anderen typen sinnlos war. "

Meine Meinung ist, und es ist nur so, dass, sobald Sie anfangen, Nicht-Primitive einzuschalten, Sie anfangen müssen, über "gleich" gegen "==" nachzudenken. Erstens kann das Vergleichen zweier Zeichenfolgen ein ziemlich langwieriger Vorgang sein, der zu den oben genannten Leistungsproblemen beiträgt. Zweitens, wenn Strings eingeschaltet werden, wird es erforderlich sein, Strings einzuschalten, die Groß - und Kleinschreibung zu ignorieren, Strings einzuschalten, die das Gebietsschema berücksichtigen/ignorieren, auf Regex basierende Strings einzuschalten Sprachentwickler auf Kosten eines kleinen Zeitaufwands für Programmierer.

18
DJClayworth

Ein Beispiel für die direkte String Verwendung seit 1.7 kann ebenfalls gezeigt werden:

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

}

Neben den obigen guten Argumenten möchte ich hinzufügen, dass viele Leute heute switch als veralteten Rest der prozeduralen Vergangenheit von Java (zurück zu C-Zeiten) ansehen.

Ich teile diese Meinung nicht ganz, ich denke, switch kann in einigen Fällen nützlich sein, zumindest wegen seiner Geschwindigkeit, und es ist sowieso besser als einige Serien von numerischen Kaskaden else if, die ich gesehen habe etwas Code ...

In der Tat lohnt es sich, den Fall zu betrachten, in dem Sie einen Schalter benötigen, und zu prüfen, ob er nicht durch etwas mehr OO ersetzt werden kann. Zum Beispiel Aufzählungen in Java 1.5+, vielleicht HashTable oder eine andere Sammlung (manchmal bedaure ich, dass wir keine (anonymen) Funktionen als erstklassiger Bürger haben, wie in Lua - das keinen Schalter hat - oder JavaScript) oder sogar Polymorphismus.

12
PhiLho

Wenn Sie kein JDK7 oder höher verwenden, können Sie es mit hashCode() simulieren. Da String.hashCode() normalerweise unterschiedliche Werte für unterschiedliche Zeichenfolgen und immer gleiche Werte für gleiche Zeichenfolgen zurückgibt, ist dies ziemlich zuverlässig (verschiedene Zeichenfolgen) kann Erzeugen Sie den gleichen Hash-Code wie @Lii, der in einem Kommentar erwähnt wird, wie z. B. "FB" und "Ea") Siehe Dokumentation .

Der Code würde also so aussehen:

String s = "<Your String>";

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

Auf diese Weise schalten Sie technisch ein int ein.

Alternativ können Sie den folgenden Code verwenden:

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

Seit Jahren verwenden wir dafür einen (n Open Source) Präprozessor.

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

Vorverarbeitete Dateien heißen Foo.jpp und werden mit einem Ant-Skript in Foo.Java verarbeitet.

Vorteil ist, dass es in Java verarbeitet wird, das auf 1.0 ausgeführt wird (obwohl wir normalerweise nur bis 1.4 unterstützt haben). Es war auch viel einfacher, dies zu tun (viele Zeichenfolgenwechsel), als es mit Aufzählungen oder anderen Problemumgehungen zu fummeln - Code war viel einfacher zu lesen, zu warten und zu verstehen. IIRC (kann zu diesem Zeitpunkt keine Statistiken oder technischen Argumente liefern) war auch schneller als die natürlichen Java Äquivalente.

Der Nachteil ist, dass Sie Java nicht bearbeiten. Daher ist der Workflow etwas umfangreicher (Bearbeiten, Verarbeiten, Kompilieren/Testen). Außerdem wird mit IDE eine Verknüpfung zu Java hergestellt. Dies ist etwas kompliziert (der Schalter wird zu einer Folge von if/else-Logikschritten) und die Schaltergehäusereihenfolge wird nicht beibehalten.

Ich würde es nicht für 1.7+ empfehlen, aber es ist nützlich, wenn Sie Java programmieren möchten, das auf frühere JVMs abzielt (da Joe public selten die neueste Version installiert hat).

Sie können es bekommen von SVN oder durchsuchen Sie die Code online . Sie benötigen EBuild , um es so zu erstellen, wie es ist.

4
Charles Goodwin

Andere Antworten sagten, dass dies in Java 7 hinzugefügt wurde und Problemumgehungen für frühere Versionen gegeben wurden. Diese Antwort versucht das "Warum" zu beantworten

Java war eine Reaktion auf die Überkomplexität von C++. Es wurde entworfen, um eine einfache, saubere Sprache zu sein.

Die Sprache von String hat ein wenig mit Sonderfällen zu tun, aber es scheint mir klar, dass die Designer versucht haben, die Menge an Sonderfällen und syntaktischem Zucker auf ein Minimum zu beschränken.

das Einschalten von Strings ist unter der Haube ziemlich komplex, da Strings keine einfachen primitiven Typen sind. Dies war zu der Zeit keine übliche Funktion. Java wurde entworfen und passte nicht wirklich zum minimalistischen Design. Vor allem, weil sie beschlossen hatten, für Zeichenfolgen nicht den Sonderfall == zu verwenden, wäre es (und ist) ein bisschen seltsam, wenn der Fall dort funktioniert, wo == nicht funktioniert.

Zwischen 1.0 und 1.4 blieb die Sprache selbst ziemlich gleich. Die meisten Verbesserungen an Java waren bibliothekarisch.

Da sich mit Java 5 alles geändert hat, wurde die Sprache wesentlich erweitert. Weitere Erweiterungen folgten in den Versionen 7 und 8. Ich gehe davon aus, dass diese Änderung der Einstellung auf den Aufstieg von C # zurückzuführen ist.

4
plugwash