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


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

Kann mir jemand erklären, warum ich das nicht so machen kann, wie in der technischen Art von Java switch Aussage funktioniert?


931
2017-12-03 18:23


Ursprung


Antworten:


Anweisungen mit ändern String Fälle wurden in implementiert Java SE 7mindestens 16 Jahre nachdem sie zuerst angefordert wurden. Ein klarer Grund für die Verspätung wurde nicht genannt, wohl aber mit der Leistung.

Implementierung in JDK 7

Die Funktion wurde jetzt in implementiert javac  mit einem "Entzuckerungs" -Prozess; eine saubere, High-Level-Syntax mit String Konstanten in case Deklarationen werden zur Kompilierungszeit in komplexeren Code nach einem Muster erweitert. Der resultierende Code verwendet JVM-Anweisungen, die schon immer vorhanden waren.

EIN switch mit String Fälle werden während der Kompilierung in zwei Schalter übersetzt. Die erste weist jeder Zeichenfolge eine eindeutige ganze Zahl zu - ihre Position im ursprünglichen Schalter. Dies geschieht, indem zuerst der Hash-Code des Etiketts eingeschaltet wird. Der entsprechende Fall ist ein if Anweisung, die String-Gleichheit testet; Wenn es Kollisionen auf dem Hash gibt, ist der Test eine Kaskadierung if-else-if. Der zweite Schalter spiegelt diesen im ursprünglichen Quellcode wieder, ersetzt aber die Fallbeschriftungen durch ihre entsprechenden Positionen. Dieser zweistufige Prozess macht es einfach, die Flusssteuerung des ursprünglichen Switches zu erhalten.

Schaltet die JVM ein

Für mehr technische Tiefe auf switchkönnen Sie auf die JVM-Spezifikation verweisen, in der die Zusammenstellung von Switch-Anweisungen wird beschrieben. Kurz gesagt, gibt es zwei verschiedene JVM-Anweisungen, die für einen Schalter verwendet werden können, abhängig von der Seltenheit der von den Fällen verwendeten Konstanten. Beide hängen davon ab, dass für jeden Fall Integer-Konstanten zur effizienten Ausführung verwendet werden.

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

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

Beim Entzuckern a switch auf String Objekte, beide Anweisungen werden wahrscheinlich verwendet werden. Das lookupswitch eignet sich für den ersten Einschalten von Hash-Codes, um die ursprüngliche Position des Falles zu finden. Die resultierende Ordinalzahl ist eine natürliche Anpassung für ein tableswitch.

Beide Anweisungen erfordern, dass die jedem Fall zugewiesenen Integer-Konstanten zur Kompilierungszeit sortiert werden. Zur Laufzeit, während der O(1) Leistung von tableswitch erscheint in der Regel besser als die O(log(n)) Leistung von lookupswitchEs bedarf einiger Analyse, um zu bestimmen, ob die Tabelle dicht genug ist, um den Raum-Zeit-Kompromiss zu rechtfertigen. Bill Venners schrieb ein toller Artikel Dies deckt dies detaillierter ab, zusammen mit einem Blick auf andere Java-Flow-Control-Anweisungen unter der Haube.

Vor JDK 7

Vor JDK 7, enum könnte annähern a String-basierter Schalter. Dies verwendet die statische valueOf Methode, die vom Compiler auf jedem generiert wird enumArt. Beispielsweise:

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

961
2017-12-03 18:30



Wenn Sie in Ihrem Code einen Platz haben, an dem Sie einen String einschalten können, ist es möglicherweise besser, den String als eine Aufzählung der möglichen Werte zu definieren, die Sie einschalten können. Natürlich begrenzen Sie die möglichen Werte von Strings, die Sie für diejenigen in der Enumeration haben können, die möglicherweise erwünscht sind oder nicht.

Natürlich könnte Ihre Enumeration einen Eintrag für 'andere' 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;
}

120
2017-12-03 18:44



Das Folgende ist ein vollständiges Beispiel, das auf dem Beitrag von JeeBee basiert, wobei java enum's anstelle einer benutzerdefinierten Methode verwendet wird.

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

90
2017-09-16 13:11



Ganzzahlige Switches können zu sehr effizientem 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 Switches in Integer-Typen, da es bei anderen Typen sinnlos war.

Die Designer von C # entschieden, dass der Stil wichtig war, auch wenn es keinen Vorteil gab.

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


26
2017-12-03 18:32



James Curran sagt kurz und bündig: "Switches, die auf Ganzzahlen basieren, können zu sehr effizientem Code optimiert werden. Switches, die auf anderen Datentypen basieren, können nur zu einer Reihe von if () -Anweisungen kompiliert werden. Aus diesem Grund erlaubt C & C ++ nur Switches da es bei anderen Typen sinnlos war. "

Meine Meinung, und nur das ist, dass, sobald Sie anfangen, Nicht-Primitive einzuschalten, Sie anfangen müssen, über "gleich" gegen "==" nachzudenken. Erstens kann das Vergleichen zweier Strings eine ziemlich langwierige Prozedur sein, was zu den oben erwähnten Leistungsproblemen führt. Zweitens, wenn es Strings gibt, wird es eine Nachfrage nach dem Einschalten von Strings ohne Groß- / Kleinschreibung geben, das Einschalten von Strings unter Berücksichtigung / Ignorieren von Locale, das Einschalten von Strings basierend auf Regex .... Ich würde einer Entscheidung zustimmen, die viel Zeit für die Sprachentwickler auf Kosten von wenig Zeit für Programmierer.


18
2017-12-03 20:49



Ein Beispiel für direkt String Verwendung seit 1.7 kann ebenfalls angezeigt 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;
    }

}

18
2018-04-09 06:30



Neben den obigen guten Argumenten werde ich hinzufügen, dass viele Leute heute sehen switch als veralteter Rest der prozeduralen Vergangenheit von Java (zurück zu C-Zeiten).

Ich teile diese Meinung nicht vollständig, denke ich switch kann in einigen Fällen seine Nützlichkeit haben, zumindest wegen seiner Geschwindigkeit, und auf jeden Fall ist es besser als einige Reihen von numerischen Kaskaden else if Ich sah in irgendeinem Code ...

Aber in der Tat lohnt es sich, den Fall zu betrachten, in dem Sie einen Schalter brauchen, und zu sehen, ob er nicht durch etwas mehr OO ersetzt werden kann. Zum Beispiel Enums in Java 1.5+, vielleicht HashTable oder eine andere Sammlung (manchmal bedauere ich, dass wir keine (anonymen) Funktionen als erstklassiger Bürger haben, wie in Lua - die keinen Switch hat - oder JavaScript) oder sogar Polymorphismus.


12
2017-12-03 21:45



Wenn Sie JDK7 oder höher nicht verwenden, können Sie verwenden hashCode() um es zu simulieren. weil String.hashCode() gibt normalerweise unterschiedliche Werte für verschiedene Strings zurück und gibt immer gleiche Werte für gleiche Strings zurück, es ist ziemlich zuverlässig (verschiedene Strings kann produzieren den gleichen Hash-Code wie @ Liii in einem Kommentar erwähnt, wie z "FB" und "Ea") Sehen Dokumentation.

Also würde der Code so aussehen:

String s = "<Your String>";

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

So schalten Sie technisch ein an int.

Alternativ könnten 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
2018-05-23 20:16



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 zu Foo.java verarbeitet.

Vorteil ist, dass es in Java verarbeitet wird, das auf 1.0 ausgeführt wird (obwohl wir normalerweise nur auf Version 1.4 unterstützt wurden). Außerdem war es viel einfacher, dies zu tun (viele String-Switches), als wenn man es mit Enums oder anderen Umgehungen umging - Code war viel einfacher zu lesen, zu warten und zu verstehen. IIRC (kann zu diesem Zeitpunkt keine Statistiken oder technischen Überlegungen liefern) war auch schneller als die natürlichen Java-Entsprechungen.

Nachteile sind, dass Sie Java nicht bearbeiten, also ist es ein bisschen mehr Workflow (bearbeiten, verarbeiten, kompilieren / testen) plus eine IDE wird zurück zu dem Java verbunden, das ein wenig verschachtelt ist (der Schalter wird zu einer Reihe von if / else Logikschritten) und die Switch-Case-Reihenfolge 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).

Du kannst es bekommen von SVN oder durchsuchen Sie die Code online. Du brauchst EBuild um es so wie es ist zu bauen.


4
2017-11-15 15:43