Por que não posso usar a instrução switch em uma String?

Esta funcionalidade será colocada em uma versão posterior do Java?

Alguém pode explicar por que eu não posso fazer isso, como no modo técnico como a instrução switch do Java funciona?

As instruções de String com casos de String foram implementadas no Java SE 7 , pelo menos 16 anos depois de terem sido solicitadas pela primeira vez. Um motivo claro para o atraso não foi fornecido, mas provavelmente teve a ver com o desempenho.

Implementação no JDK 7

O recurso agora foi implementado no javac com um processo de “de-sugaring”; uma syntax limpa e de alto nível usando constantes String nas declarações de case é expandida em tempo de compilation em código mais complexo seguindo um padrão. O código resultante usa instruções da JVM que sempre existiram.

Um switch com casos String é traduzido em dois switches durante a compilation. O primeiro mapeia cada string para um inteiro único – sua posição no switch original. Isso é feito primeiro ligando o código hash do label. O caso correspondente é uma instrução if que testa a igualdade da cadeia de caracteres; se houver colisões no hash, o teste será em cascata if-else-if . O segundo switch espelha isso no código-fonte original, mas substitui os labels de maiúsculas e minúsculas pelas posições correspondentes. Este processo de duas etapas facilita a preservação do controle de stream do switch original.

Switches na JVM

Para obter mais detalhes técnicos sobre o switch , consulte a Especificação da JVM, na qual a compilation de instruções de opção é descrita. Em suma, existem duas instruções diferentes da JVM que podem ser usadas para um switch, dependendo da dispersão das constantes usadas pelos casos. Ambos dependem do uso de constantes inteiras para cada caso para serem executadas de forma eficiente.

Se as constantes forem densas, elas serão usadas como um índice (depois de subtrair o valor mais baixo) em uma tabela de pointers de instrução – a instrução tableswitch .

Se as constantes forem esparsas, uma pesquisa binária para o caso correto será executada – a instrução lookupswitch .

Ao descodificar um switch em objects String , ambas as instruções provavelmente serão usadas. O lookupswitch é adequado para o primeiro interruptor em códigos hash para encontrar a posição original do case. O ordinal resultante é um ajuste natural para um tableswitch .

Ambas as instruções requerem que as constantes inteiras atribuídas a cada caso sejam ordenadas em tempo de compilation. Em tempo de execução, enquanto o desempenho O(1) do tableswitch geralmente parece melhor que o desempenho O(log(n)) do lookupswitch , ele requer algumas análises para determinar se a tabela é densa o suficiente para justificar o tradeoff de espaço-tempo. Bill Venners escreveu um ótimo artigo que aborda isso com mais detalhes, juntamente com uma visão sob o capô de outras instruções de controle de stream de Java.

Antes do JDK 7

Antes do JDK 7, o enum poderia aproximar um switch baseado em String . Isso usa o método estático valueOf gerado pelo compilador em cada tipo de enum . Por exemplo:

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

Se você tem um lugar no seu código onde você pode ativar uma String, então pode ser melhor refatorar a String para ser uma enumeração dos valores possíveis, que você pode ativar. Claro, você limita os valores potenciais de Strings que você pode ter para aqueles na enumeração, que podem ou não ser desejados.

Claro que sua enumeração poderia ter uma input para ‘outro’, e um método fromString (String), então você poderia ter

 ValueEnum enumval = ValueEnum.fromString(myString); switch (enumval) { case MILK: lap(); break; case WATER: sip(); break; case BEER: quaff(); break; case OTHER: default: dance(); break; } 

A seguir, um exemplo completo baseado no post de JeeBee, usando enum de java em vez de usar um método personalizado.

Observe que no Java SE 7 e posterior você pode usar um object String na expressão da instrução 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 } } 

Switches baseados em inteiros podem ser otimizados para um código muito eficiente. Switches baseados em outro tipo de dados só podem ser compilados para uma série de instruções if ().

Por esse motivo, o C & C ++ apenas permite opções de tipos inteiros, uma vez que era inútil para outros tipos.

Os designers do C # decidiram que o estilo era importante, mesmo que não houvesse vantagem.

Os designers de Java aparentemente pensaram como os designers de C.

James Curran diz sucintamente: “Switches baseados em números inteiros podem ser otimizados para código muito eficiente. Switches baseados em outros tipos de dados só podem ser compilados para uma série de declarações if (). Por esse motivo, C & C ++ apenas permite opções em tipos inteiros, desde que foi inútil com outros tipos “.

Minha opinião, e é apenas isso, é que assim que você começar a ligar os não-primitivos, você precisa começar a pensar em “iguais” versus “==”. Em primeiro lugar, comparar duas strings pode ser um procedimento bastante demorado, aumentando os problemas de desempenho mencionados acima. Em segundo lugar, se há comutação em seqüências haverá demanda para ligar strings ignorar caso, ligar strings considerando / ignorando locale, ligar cordas com base em regex …. Eu aprovaria uma decisão que poupou muito tempo para o desenvolvedores de linguagem ao custo de uma pequena quantidade de tempo para programadores.

Um exemplo de uso direto de String desde 1.7 também pode ser mostrado:

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

Além dos bons argumentos acima, eu acrescentarei que muitas pessoas hoje vêem a switch como um remanescente obsoleto do passado processual do Java (de volta ao tempo C).

Eu não compartilho completamente esta opinião, acho que switch pode ter sua utilidade em alguns casos, pelo menos por causa de sua velocidade, e de qualquer maneira é melhor que algumas séries de números em cascata else if eu vi em algum código …

Mas, na verdade, vale a pena olhar para o caso em que você precisa de um switch e ver se ele não pode ser substituído por algo mais OO. Por exemplo, enums em Java 1.5+, talvez HashTable ou alguma outra coleção (às vezes lamento que não tenhamos funções (anônimas) como cidadão de primeira class, como em Lua – que não possui switch – ou JavaScript) ou mesmo polymorphism.

Se você não estiver usando o JDK7 ou superior, poderá usar o hashCode() para simulá-lo. Como String.hashCode() geralmente retorna valores diferentes para strings diferentes e sempre retorna valores iguais para strings iguais, é bastante confiável (Strings diferentes podem produzir o mesmo código hash que @Lii mencionado em um comentário, como "FB" e "Ea" ) Veja a documentação .

Então, o código ficaria assim:

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

Dessa forma, você está tecnicamente ligando um int .

Como alternativa, você pode usar o seguinte código:

 public final class Switch { private final HashMap cases = new HashMap(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; } } } } 

Há anos que estamos usando um pré-processador (n open source) para isso.

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

Os arquivos pré-processados ​​são denominados Foo.jpp e processados ​​em Foo.java com um script ant.

A vantagem é que é processada em Java que é executado em 1.0 (embora normalmente só tenhamos suporte para 1.4). Também foi muito mais fácil fazer isso (muitos switches de string) em comparação com falsificação de enums ou outras soluções alternativas – o código era muito mais fácil de ler, manter e entender. IIRC (não pode fornecer statistics ou raciocínio técnico neste momento) também foi mais rápido que os equivalentes naturais de Java.

As desvantagens são que você não está editando Java, portanto, é um pouco mais de stream de trabalho (editar, processar, compilar / testar) e um IDE se conectará ao Java, que é um pouco complicado (o switch se torna uma série de etapas lógicas if / else) e a ordem de troca de checkbox não é mantida.

Eu não recomendaria isso para 1,7+, mas é útil se você quiser programar Java que segmente as JVMs anteriores (já que Joe public raramente tem o último instalado).

Você pode obtê-lo a partir do SVN ou navegar pelo código online . Você precisará do EBuild para construí-lo como está.

Outras respostas disseram que isso foi adicionado no Java 7 e recebeu soluções alternativas para versões anteriores. Esta resposta tenta responder ao “porquê”

Java foi uma reação às complexidades excessivas do C ++. Foi projetado para ser uma linguagem simples e limpa.

O String tem um pouco de manipulação especial de maiúsculas e minúsculas na linguagem, mas parece claro para mim que os designers estavam tentando manter a quantidade de invólucro especial e açúcar sintático ao mínimo.

a ativação de strings é bastante complexa sob o capô, pois as strings não são tipos primitivos simples. Não era uma característica comum no momento em que Java foi projetado e não se encheckbox bem com o design minimalista. Especialmente porque eles decidiram não usar um caso especial == para strings, seria (e é) um pouco estranho para o caso funcionar onde == não.

Entre 1.0 e 1.4, a linguagem em si permaneceu praticamente a mesma. A maioria dos aprimoramentos para Java estava no lado da biblioteca.

Tudo isso mudou com o Java 5, a linguagem foi substancialmente estendida. Outras extensões seguidas nas versões 7 e 8. Espero que esta mudança de atitude foi impulsionada pelo aumento de C #

Não é muito bonito, mas aqui está outra maneira para o Java 6 e abaixo:

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

É uma brisa no Groovy; Eu embuti o groovy jar e criei uma class de utilitários groovy para fazer todas essas coisas e mais, o que acho irritante fazer em Java (já que estou preso usando o Java 6 na empresa).

 it.'p'.each{ switch (it.@name.text()){ case "choclate": myholder.myval=(it.text()); break; }}... 

Quando você usa o intellij, veja também:

Arquivo -> Estrutura do Projeto -> Projeto

Arquivo -> Estrutura do Projeto -> Módulos

Quando você tiver vários módulos, certifique-se de definir o nível de idioma correto na guia do 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; } } }