Pregunta ¿Por qué no puedo usar la declaración de cambio en una cadena?


¿Se va a poner esta funcionalidad en una versión posterior de Java?

¿Alguien puede explicar por qué no puedo hacer esto, como en la forma técnica en que Java switch declaración funciona?


931
2017-12-03 18:23


origen


Respuestas:


Cambiar declaraciones con String casos se han implementado en Java SE 7, por lo menos 16 años después de que se solicitaron por primera vez. No se proporcionó un motivo claro para la demora, pero probablemente tenía que ver con el rendimiento.

Implementación en JDK 7

La característica ahora se ha implementado en javac  con un proceso de "eliminación de azúcares"; una sintaxis limpia de alto nivel usando String constantes en case las declaraciones se expanden en tiempo de compilación en un código más complejo siguiendo un patrón. El código resultante usa instrucciones JVM que siempre han existido.

UN switch con String casos se traduce en dos conmutadores durante la compilación. El primero asigna cada cadena a un entero único: su posición en el interruptor original. Esto se hace al encender primero el código hash de la etiqueta. El caso correspondiente es un if declaración que prueba la igualdad de cadenas; si hay colisiones en el hash, la prueba es una cascada if-else-if. El segundo interruptor refleja eso en el código fuente original, pero sustituye las etiquetas de la caja con sus posiciones correspondientes. Este proceso de dos pasos hace que sea fácil conservar el control de flujo del interruptor original.

Cambia en la JVM

Para mayor profundidad técnica en switch, puede consultar la especificación de JVM, donde compilación de instrucciones de cambio es descrito. En pocas palabras, hay dos instrucciones diferentes de JVM que se pueden utilizar para un conmutador, dependiendo de la cantidad de constantes utilizadas por los casos. Ambos dependen del uso de constantes enteras para que cada caso se ejecute de manera eficiente.

Si las constantes son densas, se usan como un índice (después de restar el valor más bajo) en una tabla de punteros de instrucción: el tableswitch instrucción.

Si las constantes son dispersas, se realiza una búsqueda binaria para el caso correcto: el lookupswitch instrucción.

Al eliminar azúcar switch en String objetos, es probable que se usen ambas instrucciones. los lookupswitch es adecuado para los primeros códigos de hash de encendido para encontrar la posición original de la caja. El ordinal resultante es un ajuste natural para un tableswitch.

Ambas instrucciones requieren que las constantes enteras asignadas a cada caso se clasifiquen en tiempo de compilación. En tiempo de ejecución, mientras O(1) rendimiento de tableswitch generalmente parece mejor que el O(log(n)) rendimiento de lookupswitch, requiere algún análisis para determinar si la tabla es lo suficientemente densa como para justificar la compensación de espacio-tiempo. Bill Venners escribió un gran artículo que cubre esto con más detalle, junto con un vistazo bajo el capó a otras instrucciones de control de flujo de Java.

Antes de JDK 7

Antes de JDK 7, enum podría aproximar una Stringinterruptor basado en Esto usa la estática valueOf método generado por el compilador en cada enumtipo. Por ejemplo:

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

961
2017-12-03 18:30



Si tiene un lugar en su código donde puede encender una Cadena, entonces puede ser mejor refactorizar la Cadena para que sea una enumeración de los valores posibles, que puede activar. Por supuesto, limita los valores potenciales de las cadenas que puede tener a los de la enumeración, que pueden o no ser deseados.

Por supuesto, su enumeración podría tener una entrada para 'otro' y un método fromString (String), entonces podría tener

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



El siguiente es un ejemplo completo basado en la publicación de JeeBee, usando Java Enum en lugar de usar un método personalizado.

Tenga en cuenta que en Java SE 7 y posterior puede usar un objeto String en la expresión de la instrucción switch en su lugar.

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



Los conmutadores basados ​​en enteros se pueden optimizar a un código muy eficiente. Los conmutadores basados ​​en otro tipo de datos solo se pueden compilar a una serie de sentencias if ().

Por ese motivo, C & C ++ solo permite el cambio de tipos enteros, ya que no tiene sentido con otros tipos.

Los diseñadores de C # decidieron que el estilo era importante, incluso si no había ninguna ventaja.

Los diseñadores de Java aparentemente pensaron como los diseñadores de C.


26
2017-12-03 18:32



James Curran dice sucintamente: "Los conmutadores basados ​​en enteros se pueden optimizar a un código muy eficiente. Los conmutadores basados ​​en otro tipo de datos solo se pueden compilar a una serie de sentencias if (). Por esa razón, C & C ++ solo permite el cambio en tipos enteros, ya que no tiene sentido con otros tipos ".

Mi opinión, y es solo eso, es que tan pronto como comienzas a conectarte con personas no primitivas necesitas empezar a pensar en "iguales" versus "==". En primer lugar, la comparación de dos cadenas puede ser un procedimiento bastante largo, que se suma a los problemas de rendimiento mencionados anteriormente. En segundo lugar, si hay un cambio en las cadenas, habrá demanda para activar las cadenas ignorando mayúsculas y minúsculas, activando cadenas que tengan en cuenta / ignoren la configuración regional, activando cadenas basadas en expresiones regulares ... Aprobaría una decisión que ahorrara mucho tiempo para el desarrolladores de lenguaje a costa de una pequeña cantidad de tiempo para programadores.


18
2017-12-03 20:49



Un ejemplo de directo String el uso desde 1.7 puede mostrarse también:

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



Además de los buenos argumentos anteriores, agregaré que mucha gente hoy ve switch como un resto obsoleto del pasado procedimental de Java (de vuelta a C veces).

No comparto completamente esta opinión, creo switch puede tener su utilidad en algunos casos, al menos debido a su velocidad, y de todos modos es mejor que algunas series de números numéricos en cascada else if Vi en algún código ...

Pero, de hecho, vale la pena mirar el caso donde necesita un interruptor, y ver si no puede ser reemplazado por algo más OO. Por ejemplo enumeraciones en Java 1.5+, quizás HashTable o alguna otra colección (algunas veces me arrepiento de no tener funciones (anónimas) como ciudadano de primera clase, como en Lua, que no tiene conmutador, o JavaScript) o incluso polimorfismo.


12
2017-12-03 21:45



Si no está utilizando JDK7 o superior, puede usar hashCode() simularlo Porque String.hashCode() generalmente devuelve diferentes valores para diferentes cadenas y siempre devuelve valores iguales para cadenas iguales, es bastante confiable (diferentes cadenas poder producir el mismo código hash como @Lii mencionado en un comentario, como "FB" y "Ea") Ver documentación.

Entonces, el código se vería así:

String s = "<Your String>";

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

De esta manera, estás técnicamente encendiendo una int.

Alternativamente, puede usar el siguiente código:

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



Durante años hemos estado usando un preprocesador (de código abierto) para esto.

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

Los archivos preprocesados ​​se llaman Foo.jpp y se procesan en Foo.java con un script ant.

La ventaja es que se procesa en Java que se ejecuta en 1.0 (aunque típicamente solo admitimos de nuevo a 1.4). Además, era mucho más fácil hacer esto (muchos modificadores de cadena) en comparación con manipularlo con enumeraciones u otras soluciones: el código era mucho más fácil de leer, mantener y comprender. IIRC (no puede proporcionar estadísticas o razonamiento técnico en este punto) también fue más rápido que los equivalentes naturales de Java.

Las desventajas son que no está editando Java por lo que es un poco más flujo de trabajo (edición, proceso, compilación / prueba) más un IDE se vinculará a Java que es un poco intrincado (el cambio se convierte en una serie de pasos lógicos if / else) y el orden de la caja del interruptor no se mantiene.

No lo recomendaría para 1.7+, pero es útil si desea programar Java que se dirija a JVM anteriores (ya que Joe public raramente tiene la última versión instalada).

Puedes conseguirlo de SVN o navega por el código en línea. Necesitarás EBuild para construirlo tal como es.


4
2017-11-15 15:43



Otras respuestas han dicho que esto fue agregado en Java 7 y provisto de soluciones provisionales para versiones anteriores. Esta respuesta intenta responder al "por qué"

Java fue una reacción a las complejidades excesivas de C ++. Fue diseñado para ser un lenguaje simple y limpio.

String recibió un poco de manejo especial de casos en el idioma, pero me parece claro que los diseñadores estaban tratando de mantener al mínimo la cantidad de envoltura especial y azúcar sintáctico.

la activación de cadenas es bastante compleja bajo el capó, ya que las cadenas no son simples tipos primitivos. No era una característica común en el momento en que se diseñó Java y realmente no encaja bien con el diseño minimalista. Especialmente porque habían decidido no tener casos especiales == para cadenas, sería (y es) un poco extraño que el caso funcione donde == no lo hace.

Entre 1.0 y 1.4 el idioma en sí se mantuvo más o menos igual. La mayoría de las mejoras a Java estaban en el lado de la biblioteca.

Todo eso cambió con Java 5, el idioma se amplió sustancialmente. En las versiones 7 y 8 siguieron otras extensiones. Espero que este cambio de actitud haya sido impulsado por el aumento de C #


4
2018-01-19 06:09



No es muy bonito, pero aquí hay otra forma para Java 6 y abajo:

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