Question Pourquoi ne puis-je pas utiliser l'instruction switch sur une chaîne?


Cette fonctionnalité va-t-elle être mise dans une version ultérieure de Java?

Quelqu'un peut-il expliquer pourquoi je ne peux pas le faire, comme dans la façon technique de Java switch déclaration fonctionne?


931
2017-12-03 18:23


origine


Réponses:


Changer les instructions avec String cas ont été mis en œuvre dans Java SE 7, au moins 16 ans après leur première demande. Une raison évidente du retard n'a pas été fournie, mais elle était probablement liée à la performance.

Implémentation dans JDK 7

La fonctionnalité a maintenant été implémentée dans javac  avec un processus de "dé-sucre"; une syntaxe propre et de haut niveau en utilisant String constantes dans case Les déclarations sont développées au moment de la compilation en code plus complexe suivant un modèle. Le code résultant utilise les instructions JVM qui ont toujours existé.

UNE switch avec String cas est traduit en deux commutateurs lors de la compilation. Le premier mappe chaque chaîne à un entier unique - sa position dans le commutateur d'origine. Ceci est fait en allumant d'abord le code de hachage de l'étiquette. Le cas correspondant est un if déclaration qui teste l'égalité des chaînes; s'il y a des collisions sur le hachage, le test est une cascade if-else-if. Le deuxième commutateur reflète cela dans le code source d'origine, mais remplace les étiquettes de cas par leurs positions correspondantes. Ce processus en deux étapes facilite la préservation du contrôle de flux du commutateur d'origine.

Commutateurs dans la machine virtuelle Java

Pour plus de profondeur technique sur switch, vous pouvez vous référer à la spécification JVM, où compilation d'instructions de commutation est décrit. En un mot, il existe deux instructions JVM différentes qui peuvent être utilisées pour un commutateur, en fonction de la parcimonie des constantes utilisées par les cas. Les deux dépendent de l'utilisation de constantes entières pour chaque cas à exécuter efficacement.

Si les constantes sont denses, elles sont utilisées comme index (après avoir soustrait la valeur la plus faible) dans une table de pointeurs d'instructions - le tableswitch instruction.

Si les constantes sont clairsemées, une recherche binaire pour le cas correct est effectuée. lookupswitch instruction.

En dé-sucre un switch sur String objets, les deux instructions sont susceptibles d'être utilisées. le lookupswitch est adapté pour le premier commutateur sur les codes de hachage pour trouver la position d'origine de l'affaire. L'ordinal résultant est un ajustement naturel pour un tableswitch.

Les deux instructions nécessitent que les constantes entières affectées à chaque cas soient triées au moment de la compilation. Au moment de l'exécution, alors que le O(1) performance de tableswitch apparaît généralement mieux que le O(log(n)) performance de lookupswitch, il nécessite une analyse pour déterminer si la table est suffisamment dense pour justifier le compromis espace-temps. Bill Venners a écrit un bon article cela couvre cela plus en détail, avec un aperçu sous le capot d'autres instructions de contrôle de flux Java.

Avant le JDK 7

Avant JDK 7, enum pourrait se rapprocher d'un Stringcommutateur basé sur. Cela utilise le statique valueOf méthode générée par le compilateur sur chaque enumtype. Par exemple:

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

961
2017-12-03 18:30



Si vous avez une place dans votre code où vous pouvez activer une chaîne, il peut être préférable de refactoriser la chaîne en une énumération des valeurs possibles, que vous pouvez activer. Bien sûr, vous limitez les valeurs potentielles de Strings que vous pouvez avoir à celles de l'énumération, qui peuvent ou non être désirées.

Bien sûr, votre énumération pourrait avoir une entrée pour 'other', et une méthode fromString (String), alors vous pourriez avoir

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



Ce qui suit est un exemple complet basé sur la publication de JeeBee, en utilisant java enum au lieu d'utiliser une méthode personnalisée.

Notez que dans Java SE 7 et versions ultérieures, vous pouvez utiliser un objet String dans l'expression de l'instruction switch à la place.

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



Les commutateurs basés sur des entiers peuvent être optimisés pour un code très efficace. Les commutateurs basés sur d'autres types de données peuvent uniquement être compilés avec une série d'instructions if ().

Pour cette raison, C & C ++ n'autorise que les commutateurs sur les types entiers, car il était inutile avec les autres types.

Les concepteurs de C # ont décidé que le style était important, même s'il n'y avait pas d'avantage.

Les concepteurs de Java ont apparemment pensé comme les concepteurs de C.


26
2017-12-03 18:32



James Curran explique succinctement: «Les commutateurs basés sur des entiers peuvent être optimisés pour un code très efficace Les commutateurs basés sur d'autres types de données ne peuvent être compilés qu'avec une série d'instructions if () Pour cette raison, C & C ++ autorise uniquement les commutateurs sur les types entiers, car il était inutile avec d'autres types. "

Mon opinion, et c'est seulement cela, c'est que dès que vous commencez à allumer des non-primitives, vous devez commencer à penser à "égal" par rapport à "==". Tout d'abord comparer deux chaînes peut être une procédure assez longue, ajoutant aux problèmes de performance mentionnés ci-dessus. Deuxièmement, s'il y a des chaînes de commutation, il y aura une demande pour activer les chaînes en ignorant le cas, activer les chaînes considérant / ignorer les paramètres régionaux, activer les chaînes basées sur regex .... J'approuverais une décision qui a économisé beaucoup de temps pour développeurs de langue au prix d'une petite quantité de temps pour les programmeurs.


18
2017-12-03 20:49



Un exemple de direct String l'utilisation depuis le 1.7 peut également être montrée:

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



Outre les bons arguments ci-dessus, j'ajouterai que beaucoup de gens voient aujourd'hui switch comme un reste obsolète du passé procédural de Java (retour aux temps C).

Je ne partage pas entièrement cette opinion, je pense switch peut avoir son utilité dans certains cas, du moins à cause de sa rapidité, et de toute façon c'est mieux que certaines séries de cascades numériques else if J'ai vu dans du code ...

Mais en effet, il est intéressant de regarder le cas où vous avez besoin d'un commutateur, et voir si elle ne peut pas être remplacé par quelque chose de plus OO. Par exemple enums dans Java 1.5+, peut-être HashTable ou une autre collection (parfois je regrette de ne pas avoir de fonctions (anonymes) comme citoyen de première classe, comme dans Lua - qui n'a pas de switch - ni de JavaScript) ou même de polymorphisme.


12
2017-12-03 21:45



Si vous n'utilisez pas JDK7 ou supérieur, vous pouvez utiliser hashCode() pour le simuler. Car String.hashCode() renvoie généralement des valeurs différentes pour des chaînes différentes et renvoie toujours des valeurs égales pour des chaînes égales, il est assez fiable (Différentes chaînes pouvez produire le même code de hachage que @Lii mentionné dans un commentaire, comme "FB" et "Ea") Voir Documentation.

Donc, le code ressemblerait à ceci:

String s = "<Your String>";

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

De cette façon, vous mettez en marche techniquement un int.

Vous pouvez également utiliser le code suivant:

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



Pendant des années, nous avons utilisé un préprocesseur (n open source) pour cela.

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

Les fichiers pré-traités sont nommés Foo.jpp et sont traités dans Foo.java avec un script ant.

Avantage est qu'il est traité en Java qui s'exécute sur 1.0 (bien que typiquement nous seulement soutenus en arrière à 1.4). En outre, il était beaucoup plus facile de faire cela (beaucoup de commutateurs de chaîne) par rapport au fudging avec des énumérations ou d'autres solutions de contournement - le code était beaucoup plus facile à lire, à maintenir et à comprendre. IIRC (ne peut pas fournir de statistiques ou de raisonnement technique à ce stade), il était également plus rapide que les équivalents Java naturels.

Les désavantages sont que vous n'éditez pas Java, donc c'est un peu plus de workflow (éditer, traiter, compiler / tester) plus un EDI retournera vers le Java qui est un peu compliqué (le switch devient une série d'étapes logiques if / else) et l'ordre de commutation n'est pas conservé.

Je ne le recommanderais pas pour la version 1.7+, mais c'est utile si vous voulez programmer Java qui cible les JVM précédentes (puisque Joe public a rarement les dernières installées).

Tu peux l'avoir de SVN ou parcourez le code en ligne. Tu auras besoin EBuild pour le construire tel quel.


4
2017-11-15 15:43



D'autres réponses ont dit que cela a été ajouté dans Java 7 et donné des solutions de contournement pour les versions antérieures. Cette réponse tente de répondre au "pourquoi"

Java était une réaction aux complexités de C ++. Il a été conçu pour être un langage simple et propre.

String a eu un peu de manipulation de cas spéciaux dans la langue mais il me semble clair que les concepteurs essayaient de garder la quantité de sucre spéciale et de sucre syntaxique au minimum.

l'activation des chaînes est assez complexe sous le capot puisque les chaînes ne sont pas de simples types primitifs. Ce n'était pas une caractéristique commune au moment où Java a été conçu et ne s'intègre pas vraiment bien avec le design minimaliste. D'autant qu'ils avaient décidé de ne pas le cas particulier == pour les chaînes, ce serait (et c'est) un peu étrange pour que casse fonctionne où == ne le fait pas.

Entre 1.0 et 1.4 la langue elle-même est restée à peu près la même chose. La plupart des améliorations apportées à Java concernaient la bibliothèque.

Tout cela a changé avec Java 5, le langage a été considérablement étendu. D'autres extensions ont suivi dans les versions 7 et 8. Je m'attends à ce que ce changement d'attitude soit motivé par la montée de C #


4
2018-01-19 06:09



Pas très joli, mais voici un autre moyen pour Java 6 et ci-dessous:

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

-1
2018-06-01 06:33