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?
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.
Die Funktion wurde jetzt in javac
mit 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.
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 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;
}
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;
}
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
}
}
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.
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.
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.
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;
}
}
}
}
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.
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.