Java switch com Setas Java 14+

Na aula switch você viu o switch clássico, com case, : e break, e o perigo do fall-through. Desde o Java 14, existe uma forma nova: case valor -> …, com uma seta. Ela não tem fall-through, não precisa de break e, o mais importante, pode ser usada como uma expressão, que devolve um valor.

Você vai aprender
  • A sintaxe com seta case 1 -> e por que ela dispensa o break.
  • Vários rótulos num caso: case 1, 7 ->.
  • O switch como expressão: String nome = switch (dia) { … };.
  • Blocos com várias linhas e o yield, que devolve o valor do bloco.
  • Exaustividade: por que o javac exige que todos os valores sejam tratados (e o erro real quando não são).
  • A comparação entre o clássico e o moderno, e uma prévia dos padrões no switch.

A ideia: cada caso é uma regra completa

No switch clássico, os case eram pontos de entrada numa escada: você entra num degrau e desce até um break. No switch com setas, cada case é uma regra independente: "se for 1, faça isto" e acabou. Pense na diferença entre um corredor com portas (você entra por uma e pode atravessar as outras) e um painel de interruptores, em que cada um liga só a sua lâmpada.


A seta ->: sem break, sem fall-through

public class Main {
    public static void main(String[] args) {
        int dia = 3;
        switch (dia) {
            case 1 -> System.out.println("Domingo");
            case 2 -> System.out.println("Segunda");
            case 3 -> System.out.println("Terça");
            case 4 -> System.out.println("Quarta");
            case 5 -> System.out.println("Quinta");
            case 6 -> System.out.println("Sexta");
            case 7 -> System.out.println("Sábado");
            default -> System.out.println("Dia inválido");
        }
        System.out.println("Fim");
    }
}
$ java Main.java
Terça     « só o caso 3: nenhum break e nenhum fall-through
Fim
case 3
O valor do caso, como antes.
->
A seta: um hífen seguido de "maior que", sem espaço entre eles. Lê-se "então faça".
System.out.println("Terça");
O que fazer nesse caso: uma instrução, um bloco { … } ou (como você vai ver) um valor. Depois dela, o switch termina. Não há como "cair" no caso de baixo.
default ->
O "senão", também com seta.

Compare com o programa da aula anterior: 8 linhas de break a menos e nenhuma chance de esquecer uma delas.

Vários rótulos e blocos

Para vários valores com a mesma regra, separe-os por vírgula: case 1, 7 ->. Para várias linhas, use chaves; cada bloco tem as suas próprias variáveis (no clássico, todos os casos dividiam o mesmo bloco):

public class Main {
    public static void main(String[] args) {
        int dia = 2;
        switch (dia) {
            case 1 -> {
                System.out.println("Domingo");
                System.out.println("Dia de descanso");
            }
            case 2 -> {
                String msg = "Segunda";
                System.out.println(msg);
            }
            case 3 -> {
                String msg = "Terça";
                System.out.println(msg);
            }
            default -> {}
        }
    }
}
$ java Main.java
Segunda

As duas variáveis msg convivem sem erro, porque cada uma mora no seu bloco (no switch clássico isso dava variable msg is already defined). default -> {} é um bloco vazio: "nos outros casos, não faça nada".


O switch como expressão

Aqui está a grande novidade. Muitas vezes o switch existe só para escolher um valor: o nome do dia, o preço do produto, a quantidade de dias do mês. Em vez de atribuir a variável dentro de cada caso, o switch inteiro pode ser o valor, como um ternário com muitas opções:

public class Main {
    public static void main(String[] args) {
        int dia = 7;
        String nome = switch (dia) {
            case 1 -> "Domingo";
            case 2 -> "Segunda";
            case 3 -> "Terça";
            case 4 -> "Quarta";
            case 5 -> "Quinta";
            case 6 -> "Sexta";
            case 7 -> "Sábado";
            default -> "Dia inválido";
        };
        System.out.println("Hoje é " + nome);

        String tipo = switch (dia) {
            case 1, 7 -> "fim de semana";
            case 2, 3, 4, 5, 6 -> "dia útil";
            default -> "?";
        };
        System.out.println("É " + tipo);
        System.out.println(switch (dia) { case 1, 7 -> "descanso"; default -> "trabalho"; });
    }
}
$ java Main.java
Hoje é Sábado
É fim de semana
descanso
String nome = switch (dia) {
O switch aparece do lado direito do =: o valor que ele produzir vai para nome.
case 1 -> "Domingo";
Depois da seta vem o valor daquele caso, seguido de ;.
default -> "Dia inválido";
O valor para todos os outros casos. Num switch de expressão, alguma regra tem de servir para cada valor possível (veja "Exaustividade").
};
O ; depois da chave é obrigatório: a linha inteira String nome = switch … ; é uma instrução de atribuição.
System.out.println(switch …)
Como é um valor, cabe em qualquer lugar que aceite um valor, até dentro do println (mas não exagere na legibilidade).

Compare com o equivalente clássico, que precisava declarar String nome; antes, atribuir em cada case e não esquecer nenhum break. Aqui, a variável nasce pronta, e o javac garante que ela sempre recebe um valor.

Missão: transforme o switch clássico num switch de expressão, com setas: String bebida = switch (opcao) { … };. As opções: 1 = Café, 2 = Chá, 3 = Suco, outras = Água. A saída deve continuar Bebida: Chá.

public class Main {
    public static void main(String[] args) {
        int opcao = 2;
        String bebida;
        switch (opcao) {
            case 1:
                bebida = "Café";
                break;
            case 2:
                bebida = "Chá";
                break;
            case 3:
                bebida = "Suco";
                break;
            default:
                bebida = "Água";
        }
        System.out.println("Bebida: " + bebida);
    }
}
public class Main {
    public static void main(String[] args) {
        int opcao = 2;
        String bebida = switch (opcao) {
            case 1 -> "Café";
            case 2 -> "Chá";
            case 3 -> "Suco";
            default -> "Água";
        };
        System.out.println("Bebida: " + bebida);
    }
}

Blocos que devolvem valor: yield

E se, para calcular o valor de um caso, você precisar de várias linhas? Use um bloco { … } e, no fim, yield ("render", "produzir") com o valor que o bloco devolve:

public class Main {
    public static void main(String[] args) {
        int mes = 2;
        int ano = 2028;
        int dias = switch (mes) {
            case 4, 6, 9, 11 -> 30;
            case 2 -> {
                boolean bissexto = (ano % 4 == 0 && ano % 100 != 0) || ano % 400 == 0;
                System.out.println("Fevereiro de " + ano + (bissexto ? " é bissexto" : " não é bissexto"));
                yield bissexto ? 29 : 28;
            }
            default -> 31;
        };
        System.out.println("Dias: " + dias);
    }
}
$ java Main.java
Fevereiro de 2028 é bissexto
Dias: 29
case 2 -> { … }
Um bloco com várias instruções: calcula se o ano é bissexto (a regra do calendário: divisível por 4, exceto os séculos que não são divisíveis por 400) e mostra uma mensagem.
yield bissexto ? 29 : 28;
"Este bloco produz o valor 29 ou 28". O yield encerra o bloco e entrega o valor ao switch, que o entrega a dias.
yield × return

O yield devolve um valor do switch. O return (que você vai ver em Valores de Retorno) sai do método inteiro, e é proibido dentro de um switch de expressão (veja "Quando dá errado"). Nos casos de uma linha (case 4 -> 30;) o yield fica implícito; ele só aparece dentro de blocos.

yield com a sintaxe antiga

O switch de expressão também aceita os dois-pontos do estilo clássico; nesse caso, todo caso usa yield, e o fall-through volta a existir (um caso sem yield cai no de baixo). É raro ver isso; prefira as setas.

public class Main {
    public static void main(String[] args) {
        char conceito = 'B';
        String texto = switch (conceito) {
            case 'A':
                yield "Excelente";
            case 'B':
                yield "Bom";
            case 'C':
                yield "Regular";
            default:
                yield "Insuficiente";
        };
        System.out.println(texto);
    }
}
$ java Main.java
Bom

Exaustividade: todos os casos cobertos

Um switch de expressão precisa produzir um valor sempre, para qualquer valor do seletor. Se existir algum valor sem regra, o que a variável receberia? Por isso o javac exige que o switch seja exaustivo (cubra todas as possibilidades). Este código tem um erro de propósito.

public class Main {
    public static void main(String[] args) {
        int dia = 3;
        String nome = switch (dia) {
            case 1 -> "Domingo";
            case 2 -> "Segunda";
            case 3 -> "Terça";
        };
        System.out.println(nome);
    }
}
$ javac Main.java
Main.java:4: error: the switch expression does not cover all possible input values
        String nome = switch (dia) {
                      ^
1 error

"A expressão switch não cobre todos os valores de entrada possíveis." Não adianta que dia seja 3 agora: o javac olha o tipo, e um int pode valer qualquer coisa entre -2 bilhões e +2 bilhões. Com um default, todos ficam cobertos.

Com enum, o default é dispensável

Um enum tem uma lista fechada de valores. Se você trata todos, o switch já é exaustivo, sem default:

public class Main {
    enum Semaforo { VERDE, AMARELO, VERMELHO }

    public static void main(String[] args) {
        Semaforo sinal = Semaforo.VERMELHO;
        String acao = switch (sinal) {
            case VERDE -> "Pode seguir";
            case AMARELO -> "Atenção";
            case VERMELHO -> "Pare!";
        };
        System.out.println(acao);
    }
}
$ java Main.java
Pare!

E aqui está a grande vantagem: se você esquecer um valor (ou se alguém, um dia, acrescentar PISCANDO ao enum), o javac avisa na hora. Com case VERMELHO removido:

$ javac Main.java
Main.java:6: error: the switch expression does not cover all possible input values
        String acao = switch (sinal) {
                      ^
1 error

Por isso, com enums, é melhor não colocar default: ele "cobriria" o valor novo em silêncio, e o javac não teria como avisar.

E o switch com setas que não devolve valor?

Usado como instrução (como no primeiro exemplo, só com println), o switch com setas sobre int, String ou enum não precisa ser exaustivo: se nenhum caso bater, nada acontece, como no clássico. A exigência vale para o switch de expressão (e para o switch com padrões).


Clássico × com setas

Clássico (case x:)Com setas (case x ->)
DesdeJava 1.0Java 14
breakNecessário em cada casoNão existe
Fall-throughSim (sem break, cai no próximo)Não
Vários valorescase 1: case 7: ou case 1, 7:case 1, 7 ->
Variáveis nos casosTodos dividem um blocoCada bloco tem as suas
Como expressão (devolve valor)Só com yield em cada casoSim: case 1 -> "valor";
Exaustividade verificadaNão (como instrução)Sim, quando é expressão
Misturar os dois estilosProibido no mesmo switch (erro different case kinds used in the switch)

Em código novo, prefira as setas: são mais curtas, mais seguras e deixam claro, caso a caso, o que acontece. O clássico continua importante para ler código antigo e para os raros casos em que o fall-through é desejado.


Prévia: padrões no switch Java 21+

O switch moderno faz ainda mais: ele pode testar o tipo de um valor, dar nome a ele e acrescentar condições com when. Só para você reconhecer quando encontrar:

public class Main {
    public static void main(String[] args) {
        Object[] coisas = {42, "Ana", 3.5, null};
        for (Object o : coisas) {
            String descricao = switch (o) {
                case null -> "nada";
                case Integer i when i > 40 -> "inteiro grande: " + i;
                case Integer i -> "inteiro: " + i;
                case String s -> "texto com " + s.length() + " letras";
                default -> "outra coisa: " + o;
            };
            System.out.println(descricao);
        }
    }
}
$ java Main.java
inteiro grande: 42
texto com 3 letras
outra coisa: 3.5
nada

case Integer i quer dizer "se for um inteiro, chame-o de i"; when i > 40 acrescenta uma condição; case null trata o null sem NullPointerException. Isso depende de conceitos do Nível 2 e 3 (objetos, Object, for-each), e é explicado em detalhe em Pattern Matching.


Quando dá errado

error: the switch expression does not cover all possible input values

O que aconteceu: o switch de expressão não tem regra para algum valor possível do seletor (veja "Exaustividade").

Como resolver: com int ou String, acrescente default -> …;. Com enum, acrescente o caso que falta.

error: different case kinds used in the switch

Este código tem um erro de propósito.

public class Main {
    public static void main(String[] args) {
        int dia = 1;
        switch (dia) {
            case 1 -> System.out.println("Domingo");
            case 2:
                System.out.println("Segunda");
                break;
        }
    }
}
$ javac Main.java
Main.java:6: error: different case kinds used in the switch
            case 2:
            ^
1 error

O que aconteceu: "tipos de case diferentes usados no switch": um caso com seta e outro com dois-pontos. Cada switch tem de usar um estilo só.

Como resolver: passe todos para setas (e tire os break).

error: ';' expected depois do }

Este código tem um erro de propósito.

public class Main {
    public static void main(String[] args) {
        int dia = 1;
        String nome = switch (dia) {
            case 1 -> "Domingo";
            default -> "Outro";
        }
        System.out.println(nome);
    }
}
$ javac Main.java
Main.java:7: error: ';' expected
        }
         ^
1 error

O que aconteceu: um switch de expressão é parte de uma atribuição, e toda atribuição termina com ;.

Como resolver: };.

error: switch rule completes without providing a value

Este código tem um erro de propósito.

public class Main {
    public static void main(String[] args) {
        int mes = 2;
        int dias = switch (mes) {
            case 2 -> {
                System.out.println("Fevereiro");
            }
            default -> 31;
        };
        System.out.println(dias);
    }
}
$ javac Main.java
Main.java:7: error: switch rule completes without providing a value
            }
            ^
  (switch rules in switch expressions must either provide a value or throw)
1 error

O que aconteceu: "a regra do switch termina sem fornecer um valor". O bloco do case 2 chegou ao } sem yield. A linha entre parênteses explica: num switch de expressão, cada regra tem de fornecer um valor ou lançar uma exceção (throw, veja throw e throws).

Como resolver: termine o bloco com yield 28; (ou o valor certo).

error: attempt to return out of a switch expression

Este código tem um erro de propósito.

public class Main {
    public static void main(String[] args) {
        String cmd = "sair";
        int codigo = switch (cmd) {
            case "novo" -> 1;
            case "sair" -> {
                return;
            }
            default -> 0;
        };
        System.out.println(codigo);
    }
}
$ javac Main.java
Main.java:7: error: attempt to return out of a switch expression
                return;
                ^
1 error

O que aconteceu: "tentativa de retornar de dentro de uma expressão switch". O return sairia do método no meio de uma atribuição, e codigo ficaria sem valor.

Como resolver: use yield para devolver um valor do switch; se precisa sair do método, decida isso depois, com um if sobre o valor.


Referência

FormaSignificado
case 1 -> instrução;Regra com uma instrução (sem break)
case 1 -> { … }Regra com um bloco
case 1, 7 ->Vários valores na mesma regra
default ->Todos os outros valores
tipo x = switch (s) { case a -> v1; default -> v2; };switch de expressão (note o ; final)
case a -> { …; yield v; }Bloco que devolve v
case a: yield v;yield no estilo de dois-pontos
case Integer i when i > 0 ->Padrão com condição (Java 21+, veja Pattern Matching)
case null ->Trata null (Java 21+)

Exercícios

Missão final: numa pizzaria, o tamanho "P" custa 12.0, o "M" custa 17.5 e o "G" custa 22.5; outros tamanhos, 0.0. Use um switch de expressão com setas. No caso "G", use um bloco que mostre Tamanho grande ganha brinde! e devolva o preço com yield. Com tamanho = "G", a saída deve ser:
Tamanho grande ganha brinde!
Preço: 22.5

public class Main {
    public static void main(String[] args) {
        String tamanho = "G";
        // double preco = switch (tamanho) { … };
    }
}
public class Main {
    public static void main(String[] args) {
        String tamanho = "G";
        double preco = switch (tamanho) {
            case "P" -> 12.0;
            case "M" -> 17.5;
            case "G" -> {
                System.out.println("Tamanho grande ganha brinde!");
                yield 22.5;
            }
            default -> 0.0;
        };
        System.out.println("Preço: " + preco);
    }
}

1. A seta

Complete o caso para devolver "um" quando o valor for 1:

case 1 "um";

2. Devolver de um bloco

Qual palavra devolve o valor de um bloco dentro de um switch de expressão?

3. Vários valores

Complete para que 6 e 7 tenham a mesma regra:

case 6 -> "fim de semana";

4. Com int x = 2;, o que este código mostra?

switch (x) {
    case 1 -> System.out.println("um");
    case 2 -> System.out.println("dois");
    case 3 -> System.out.println("três");
}

5. Por que String s = switch (n) { case 1 -> "a"; case 2 -> "b"; }; não compila (com int n)?

6. Num switch de expressão sobre um enum que trata todos os valores, é melhor…


Resumo

  • case x -> (Java 14+): cada caso é uma regra completa. Sem break e sem fall-through.
  • Vários valores: case 1, 7 ->. Várias linhas: case 1 -> { … }, cada bloco com as suas variáveis.
  • Como expressão: tipo x = switch (s) { case a -> valor; … }; (com ; no fim).
  • Em blocos de um switch de expressão, yield valor; devolve o valor; return é proibido.
  • O switch de expressão tem de ser exaustivo: default para int/String; todos os valores para enum (e aí, melhor sem default).
  • Não misture : e -> no mesmo switch. Padrões (case Integer i when …, case null) são do Java 21+: veja Pattern Matching.