¿Esta funcionalidad se va a poner en una versión posterior de Java?
¿Alguien puede explicar por qué no puedo hacer esto, como en la forma técnica en que funciona la switch
declaración de Java ?
fuente
¿Esta funcionalidad se va a poner en una versión posterior de Java?
¿Alguien puede explicar por qué no puedo hacer esto, como en la forma técnica en que funciona la switch
declaración de Java ?
Las declaraciones de cambio con String
casos se han implementado en Java SE 7 , al menos 16 años después de que se solicitaron por primera vez. No se proporcionó una razón clara para la demora, pero probablemente tuvo que ver con el rendimiento.
La característica ahora se ha implementado javac
con un proceso de "des-azúcar"; una sintaxis limpia y de alto nivel que usa String
constantes en las case
declaraciones se expande en tiempo de compilación en un código más complejo siguiendo un patrón. El código resultante utiliza instrucciones JVM que siempre han existido.
A switch
con String
casos se traduce en dos interruptores durante la compilación. El primero asigna cada cadena a un número entero único: su posición en el interruptor original. Esto se hace activando primero el código hash de la etiqueta. El caso correspondiente es una if
declaración que prueba la igualdad de cadena; Si hay colisiones en el hash, la prueba es en cascada if-else-if
. El segundo conmutador refleja eso en el código fuente original, pero sustituye las etiquetas de los casos con sus posiciones correspondientes. Este proceso de dos pasos facilita la preservación del control de flujo del interruptor original.
Para obtener más detalles técnicos sobre switch
, puede consultar la Especificación JVM, donde se describe la compilación de las instrucciones del conmutador . En pocas palabras, hay dos instrucciones JVM diferentes que se pueden usar para un conmutador, dependiendo de la escasez de las 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: la tableswitch
instrucción.
Si las constantes son escasas, se realiza una búsqueda binaria del caso correcto: la lookupswitch
instrucción.
Al quitar el azúcar switch
a los String
objetos, es probable que se utilicen ambas instrucciones. El lookupswitch
es adecuado para el primer interruptor de códigos hash para encontrar la posición original de la caja. El ordinal resultante es un ajuste natural para a tableswitch
.
Ambas instrucciones requieren que las constantes enteras asignadas a cada caso se ordenen en tiempo de compilación. En tiempo de ejecución, aunque el O(1)
rendimiento de tableswitch
generalmente parece mejor que el O(log(n))
rendimiento de lookupswitch
, requiere un análisis para determinar si la tabla es lo suficientemente densa como para justificar la compensación espacio-tiempo. Bill Venners escribió un gran artículo que cubre esto con más detalle, junto con una mirada oculta a otras instrucciones de control de flujo de Java.
Antes de JDK 7, enum
podría aproximarse a un String
interruptor basado en. Esto utiliza elvalueOf
método estático generado por el compilador en cada enum
tipo. Por ejemplo:
Pill p = Pill.valueOf(str);
switch(p) {
case RED: pop(); break;
case BLUE: push(); break;
}
Pill
algunas medidas basadas en str
, diría que si no, es preferible ya que le permite manejarstr
valores fuera del rango ROJO, AZUL sin necesidad de detectar una excepción valueOf
o verificar manualmente una coincidencia con el nombre de cada tipo de enumeración que solo agrega una sobrecarga innecesaria. En mi experiencia, solo tiene sentido usarlo valueOf
para transformarlo en una enumeración si más adelante se necesitaba una representación segura del tipo de cadena.
(hash >> x) & ((1<<y)-1)
produciría valores distintos para cada cadena que hashCode
es diferente y (1<<y)
es menos del doble del número de cadenas (o en al menos no mucho mayor que eso).
Si tiene un lugar en su código donde puede activar una Cadena, entonces puede ser mejor refactorizar la Cadena para que sea una enumeración de los posibles valores, 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 deseables.
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;
}
El siguiente es un ejemplo completo basado en la publicación de JeeBee, utilizando la enumeración de Java en lugar de utilizar un método personalizado.
Tenga en cuenta que, en Java SE 7 y versiones posteriores, puede utilizar un objeto String en la expresión de la instrucción switch.
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
}
}
Los conmutadores basados en enteros se pueden optimizar para obtener un código muy eficiente. Los conmutadores basados en otro tipo de datos solo se pueden compilar en una serie de sentencias if ().
Por esa razón, C & C ++ solo permiten cambios en tipos enteros, ya que no tenía sentido con otros tipos.
Los diseñadores de C # decidieron que el estilo era importante, incluso si no había ventaja.
Los diseñadores de Java aparentemente pensaron como los diseñadores de C.
También String
se puede mostrar un ejemplo de uso directo desde 1.7:
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;
}
}
James Curran dice sucintamente: "Los interruptores basados en números enteros pueden optimizarse a un código muy eficiente. Los interruptores basados en otro tipo de datos solo pueden compilarse en una serie de declaraciones if (). Por esa razón, C & C ++ solo permiten cambios en tipos enteros, ya que no tenía sentido con otros tipos ".
Mi opinión, y es solo eso, es que tan pronto como comience a encender los no primitivos, debe comenzar a pensar en "igual" versus "==". En primer lugar, comparar dos cadenas puede ser un procedimiento bastante largo, que se suma a los problemas de rendimiento que se mencionan anteriormente. En segundo lugar, si se activan las cadenas, habrá demanda para activar las cadenas ignorando mayúsculas y minúsculas, activando las cadenas considerando / ignorando el entorno local, activando las cadenas basadas en expresiones regulares ... aprobaría una decisión que ahorró mucho tiempo para el desarrolladores de lenguaje a costa de una pequeña cantidad de tiempo para los programadores.
matched
y not matched
. (Sin embargo, sin tener en cuenta cosas como [nombre] grupos / etc.)
Además de los buenos argumentos anteriores, agregaré que mucha gente ve hoy switch
como un resto obsoleto del pasado procesal de Java (de vuelta a C veces).
No comparto completamente esta opinión, creo que 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 en cascada else if
que vi en algún código ...
Pero, de hecho, vale la pena mirar el caso en el que necesita un interruptor, y ver si no puede ser reemplazado por algo más OO. Por ejemplo, enumeraciones en Java 1.5+, tal vez HashTable o alguna otra colección (a veces lamento no tener funciones (anónimas) como ciudadanos de primera clase, como en Lua, que no tiene interruptor, o JavaScript) o incluso polimorfismo.
Si no está utilizando JDK7 o superior, puede usarlo hashCode()
para simularlo. Debido a que String.hashCode()
generalmente devuelve valores diferentes para cadenas diferentes y siempre devuelve valores iguales para cadenas iguales, es bastante confiable (Las cadenas diferentes pueden producir el mismo código hash que @Lii mencionado en un comentario, como "FB"
y "Ea"
) Consulte la 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 esa manera, técnicamente está activando un 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;
}
}
}
}
case
declaraciones tenían que, siempre pensé, ser valores constantes, y String.hashCode()
no lo son (incluso si en la práctica el cálculo nunca ha cambiado entre JVM).
case
valores de las declaraciones no tienen que ser determinables en tiempo de compilación, por lo que funciona finamente.
Durante años hemos estado utilizando un preprocesador (n de código abierto) para esto.
//#switch(target)
case "foo": code;
//#end
Los archivos preprocesados se denominan Foo.jpp y se procesan en Foo.java con un script de hormiga.
La ventaja es que se procesa en Java que se ejecuta en 1.0 (aunque normalmente solo admitimos 1.4). También fue mucho más fácil hacer esto (muchos modificadores de cadena) en comparación con falsificarlo 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 momento) 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 de flujo de trabajo (editar, procesar, compilar / probar) además de que un IDE se vinculará de nuevo con Java, que es un poco complicado (el interruptor se convierte en una serie de pasos lógicos if / else) y el orden de cambio de caja no se mantiene.
No lo recomendaría para 1.7+, pero es útil si desea programar Java que apunte a JVM anteriores (ya que Joe public rara vez tiene la última instalada).
Puede obtenerlo de SVN o buscar el código en línea . Necesitará EBuild para construirlo tal cual.
Otras respuestas han dicho que esto se agregó en Java 7 y dio soluciones para versiones anteriores. Esta respuesta trata de responder el "por qué"
Java fue una reacción a las sobrecomplejidades de C ++. Fue diseñado para ser un lenguaje simple y limpio.
String recibió un poco de manejo especial de mayúsculas y minúsculas en el lenguaje, pero me parece claro que los diseñadores estaban tratando de mantener al mínimo la cantidad de tripas especiales y azúcar sintáctica.
encender cadenas es bastante complejo bajo el capó ya que las cadenas no son simples tipos primitivos. No era una característica común en el momento en que Java fue diseñado y realmente no encaja bien con el diseño minimalista. Especialmente porque habían decidido no usar mayúsculas y minúsculas == para cadenas, sería (y es) un poco extraño que las mayúsculas y minúsculas funcionen donde == no lo hace.
Entre 1.0 y 1.4, el lenguaje en sí se mantuvo prácticamente igual. La mayoría de las mejoras a Java estaban en el lado de la biblioteca.
Todo eso cambió con Java 5, el lenguaje se amplió sustancialmente. Otras extensiones siguieron en las versiones 7 y 8. Espero que este cambio de actitud haya sido impulsado por el aumento de C #
JEP 354: Expresiones de cambio (Vista previa) en JDK-13 y JEP 361: Expresiones de cambio (Estándar) en JDK-14 extenderá la declaración de cambio para que pueda usarse como una expresión .
Ahora usted puede:
case L ->
):
El código a la derecha de una etiqueta de interruptor "caso L ->" está restringido a ser una expresión, un bloque o (por conveniencia) una instrucción throw.
Para obtener un valor de una expresión de cambio, la
break
declaración with value se descarta a favor de unayield
declaración.
Entonces, la demostración de las respuestas ( 1 , 2 ) podría verse así:
public static void main(String[] args) {
switch (args[0]) {
case "Monday", "Tuesday", "Wednesday" -> System.out.println("boring");
case "Thursday" -> System.out.println("getting better");
case "Friday", "Saturday", "Sunday" -> System.out.println("much better");
}
No es muy bonito, pero aquí hay otra forma para Java 6 y más 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);
Es una brisa en Groovy; Incrusto el jarro maravilloso y creo una groovy
clase de utilidad para hacer todas estas cosas y más que encuentro exasperante en Java (ya que estoy atascado usando Java 6 en la empresa).
it.'p'.each{
switch (it.@name.text()){
case "choclate":
myholder.myval=(it.text());
break;
}}...
Cuando uses intellij también mira:
Archivo -> Estructura del proyecto -> Proyecto
Archivo -> Estructura del proyecto -> Módulos
Cuando tenga varios módulos, asegúrese de establecer el nivel de idioma correcto en la pestaña del módulo.
public class StringSwitchCase {
public static void main(String args[]) {
visitIsland("Santorini");
visitIsland("Crete");
visitIsland("Paros");
}
public static void visitIsland(String island) {
switch(island) {
case "Corfu":
System.out.println("User wants to visit Corfu");
break;
case "Crete":
System.out.println("User wants to visit Crete");
break;
case "Santorini":
System.out.println("User wants to visit Santorini");
break;
case "Mykonos":
System.out.println("User wants to visit Mykonos");
break;
default:
System.out.println("Unknown Island");
break;
}
}
}
"Don't hold your breath."
lol, bugs.sun.com/bugdatabase/view_bug.do?bug_id=1223179