Sua dose diária de programação

Conceito, 3 exemplos e 3 exercícios: uma pequena conquista de cada vez.

✦   Lição

Decisões com condicionais

Condicionais deixam o programa escolher o que fazer conforme os dados. O if executa um bloco só quando a condição entre parênteses é true; o else cobre o caso contrário; e uma cadeia if / else if / else testa as condições de cima para baixo e executa apenas o primeiro bloco verdadeiro. Em Java a condição precisa ser um boolean: if (estoque) com um int não compila.

Java Fundamentos Iniciante 3 exemplos + 3 exercícios
42 lições publicadas 10 linguagens novas lições todo dia útil
Java / Fundamentos

Decisões com condicionais

por Tatum Winslow
Iniciante

Entenda o conceito

Condicionais deixam o programa escolher o que fazer conforme os dados. O if executa um bloco só quando a condição entre parênteses é true; o else cobre o caso contrário; e uma cadeia if / else if / else testa as condições de cima para baixo e executa apenas o primeiro bloco verdadeiro. Em Java a condição precisa ser um boolean: if (estoque) com um int não compila.

As condições nascem dos operadores relacionais (==, !=, <, <=, >, >=) e se combinam com os lógicos: && (e), || (ou) e ! (não). && e || fazem curto-circuito: se o lado esquerdo já decide o resultado, o direito nem é avaliado, o que permite escrever texto != null && texto.equals("x") sem risco. Para comparar o conteúdo de String, use equals, nunca ==.

Para escolher entre dois valores, o operador ternário condicao ? valorSeTrue : valorSeFalse cabe numa linha. Para muitos casos sobre um mesmo valor, o switch com setas (case "a", "b" -> ...), disponível desde o Java 14, funciona como expressão: devolve um valor, não "cai" para o próximo case e usa yield quando o case precisa de um bloco.

↳ Pontos-chave
  • Numa cadeia if / else if / else, só o primeiro bloco com condição verdadeira roda; por isso as faixas vão da mais restrita para a mais ampla.
  • A condição do if tem que ser boolean; uma comparação como total >= 100 já produz true ou false.
  • && e || fazem curto-circuito: em x != null && x.equals(y), o equals só roda se x não for nulo.
  • Compare o conteúdo de textos com equals (ou equalsIgnoreCase); == compara se é o mesmo objeto.
  • O ternário c ? a : b escolhe entre dois valores; o switch com -> é uma expressão que devolve valor, sem break e sem queda entre os casos.

Do conceito à prática

3 exemplos para ver a ideia funcionando.

01Frete da loja com if, else if e else

Frete.java
public class Frete {

    public static void main(String[] args) {
        double totalCompra = 149.90;
        int itens = 3;

        System.out.printf("Total da compra: R$ %.2f%n", totalCompra);

        // if simples: o bloco só roda se a condição for true.
        if (itens > 2) {
            System.out.println("Ganhou um brinde por levar mais de 2 itens!");
        }

        // if / else if / else: as condições são testadas de cima para baixo
        // e só o primeiro bloco verdadeiro é executado.
        double frete;
        if (totalCompra >= 200) {
            frete = 0;
        } else if (totalCompra >= 100) {
            frete = 9.90;
        } else {
            frete = 19.90;
        }
        System.out.printf("Frete: R$ %.2f%n", frete);

        // Uma comparação produz um boolean, que pode ir para uma variável.
        boolean freteGratis = frete == 0;
        if (!freteGratis) {
            double falta = 200 - totalCompra;
            System.out.printf("Faltam R$ %.2f para o frete grátis%n", falta);
        }
    }
}

O primeiro if não tem else: se a compra tiver mais de 2 itens, a mensagem do brinde aparece; se não, o programa segue em frente sem fazer nada. Já a cadeia do frete testa as faixas de cima para baixo. Como 149.90 >= 200 é falso e 149.90 >= 100 é verdadeiro, o frete fica em 9.90 e o else nem é considerado.

A comparação frete == 0 produz um boolean que vai para a variável freteGratis, e !freteGratis inverte esse valor. Repare que frete foi declarado antes da cadeia e recebe valor em todos os ramos: o compilador confere isso e recusaria o uso de frete se algum caminho o deixasse sem valor.

02Regras de empréstimo com &&, || e equals

Emprestimo.java
public class Emprestimo {

    public static void main(String[] args) {
        String tipoLeitor = "aluno";
        int livrosEmprestados = 2;
        boolean temMulta = false;
        String livro = "Vidas Secas";

        // && exige as duas condições; ! inverte um boolean.
        boolean podeEmprestar = livrosEmprestados < 3 && !temMulta;
        System.out.println("Pode pegar outro livro? " + podeEmprestar);

        // Strings se comparam com equals, não com ==.
        int prazoDias;
        if (tipoLeitor.equals("professor")) {
            prazoDias = 30;
        } else if (tipoLeitor.equals("aluno") || tipoLeitor.equals("servidor")) {
            prazoDias = 14;
        } else {
            prazoDias = 7;
        }
        System.out.println("Prazo para " + tipoLeitor + ": " + prazoDias + " dias");

        // Curto-circuito: se o lado esquerdo já decide, o direito nem é avaliado.
        String reserva = null;
        if (reserva != null && reserva.equals(livro)) {
            System.out.println("Livro reservado para você.");
        } else {
            System.out.println("Nenhuma reserva para " + livro + ".");
        }

        // equalsIgnoreCase compara ignorando maiúsculas e minúsculas.
        String busca = "VIDAS SECAS";
        if (busca.equalsIgnoreCase(livro)) {
            System.out.println("Encontrado no acervo: " + livro);
        }
    }
}

A regra de empréstimo combina duas condições com &&: o leitor precisa ter menos de 3 livros e não ter multa (!temMulta). O prazo usa || para dar o mesmo valor a dois tipos de leitor, e toda comparação de texto é feita com equals, que confere o conteúdo da String.

O teste da reserva mostra o curto-circuito na prática: como reserva é null, o lado esquerdo reserva != null já é falso e o Java nem chama reserva.equals(livro), que lançaria NullPointerException. Por fim, equalsIgnoreCase encontra o livro mesmo com a busca toda em maiúsculas.

03Cardápio da semana com switch e ternário

Cardapio.java
public class Cardapio {

    public static void main(String[] args) {
        String dia = "sexta";
        int convidados = 7;

        // switch expression (Java 14+): cada case com -> devolve um valor
        // e não existe "queda" para o case seguinte.
        String prato = switch (dia) {
            case "segunda", "quarta" -> "Sopa de legumes";
            case "terça", "quinta" -> "Risoto de cogumelos";
            case "sexta" -> "Moqueca de peixe";
            default -> "Feijoada";
        };
        System.out.println("Prato de " + dia + ": " + prato);

        // Um case com bloco usa yield para devolver o valor.
        int tempoMinutos = switch (prato) {
            case "Sopa de legumes" -> 40;
            case "Moqueca de peixe" -> {
                int preparo = 20;
                int cozimento = 35;
                yield preparo + cozimento;
            }
            default -> 60;
        };
        System.out.println("Tempo de preparo: " + tempoMinutos + " minutos");

        // Operador ternário: condição ? valor se true : valor se false.
        String panela = convidados > 6 ? "panela grande" : "panela média";
        System.out.println("Use a " + panela + " para " + convidados + " pessoas");

        // O ternário também cabe no meio de uma concatenação.
        System.out.println("Sobremesa: " + (dia.equals("sexta") ? "pudim" : "frutas"));
    }
}

O primeiro switch é uma expressão: o valor do case escolhido vai direto para prato. Um case pode listar vários rótulos separados por vírgula, e o default garante que sempre exista um resultado. Com a seta ->, só o case escolhido roda; não há o risco de esquecer um break e "cair" no caso seguinte, como no switch antigo com dois-pontos.

No segundo switch, o case da moqueca precisa de um bloco para calcular o tempo, então usa yield para devolver o valor. O ternário resolve escolhas entre duas opções numa linha só; dentro de uma concatenação, ele precisa de parênteses para ser avaliado antes do +.


Agora é com você

Tente resolver no rascunho antes de abrir a resposta. Os códigos não são executados nesta página.

01Situação do aluno no boletimFácil

Calcule a média de duas notas (6.5 e 5.0) e mostre a situação do aluno: aprovado com média a partir de 7, recuperação a partir de 5 e reprovado abaixo disso.

↳ Requisitos
  • A classe se chama Situacao e as notas ficam em variáveis double.
  • A média é calculada pelo programa, não digitada pronta.
  • Usa uma única cadeia if / else if / else para decidir a situação.
  • A saída é idêntica à esperada.
código inicial
public class Situacao {

    public static void main(String[] args) {
        double nota1 = 6.5;
        double nota2 = 5.0;
        // Calcule a média, imprima e decida a situação.
    }
}
saída esperada
Média: 5.75
Situação: recuperação
Ver dica

Pense na ordem dos testes: uma média 8 também é maior ou igual a 5, então qual faixa deve ser conferida primeiro?

Ver resposta
public class Situacao {

    public static void main(String[] args) {
        double nota1 = 6.5;
        double nota2 = 5.0;
        double media = (nota1 + nota2) / 2;

        System.out.println("Média: " + media);

        // As faixas são testadas da maior para a menor.
        if (media >= 7) {
            System.out.println("Situação: aprovado");
        } else if (media >= 5) {
            System.out.println("Situação: recuperação");
        } else {
            System.out.println("Situação: reprovado");
        }
    }
}

Passo a passo

  1. nota1 e nota2 são double, porque notas podem ter casas decimais.
  2. (nota1 + nota2) / 2 soma primeiro (graças aos parênteses) e divide depois; como os operandos são double, a divisão mantém os decimais e dá 5.75.
  3. if (media >= 7) testa a faixa mais alta; 5.75 >= 7 é falso, então o bloco é pulado.
  4. else if (media >= 5) só é avaliado porque o teste anterior falhou; 5.75 >= 5 é verdadeiro e a recuperação é impressa.
  5. O else final pega todo o resto, sem precisar de condição: se nenhuma faixa anterior serviu, a média é menor que 5.

Por que funciona

Numa cadeia if / else if / else, o Java para no primeiro teste verdadeiro e ignora os demais. Por isso cada ramo pode ser escrito de forma simples: quando chegamos ao else if (media >= 5), já sabemos que a média é menor que 7, e não é preciso escrever media >= 5 && media < 7.

Testar da faixa mais alta para a mais baixa garante que cada média caia em exatamente uma situação.

Erros comuns

  • Ordem invertida: começar com if (media >= 5) faz uma média 9 sair como recuperação, porque o primeiro teste já é verdadeiro. Teste a faixa mais restrita primeiro.
  • Três ifs soltos: if (media >= 7) ... if (media >= 5) ... sem else imprime duas situações para uma média 8. Encadeie com else if.
  • Esquecer os parênteses: nota1 + nota2 / 2 divide só a segunda nota e dá 9.0. Agrupe a soma: (nota1 + nota2) / 2.
  • Ponto e vírgula depois do if: if (media >= 7); encerra o if ali, e o bloco seguinte roda sempre. Não coloque ; depois da condição.

Outra forma de resolver

Com um ternário aninhado, a situação cabe numa expressão:

String situacao = media >= 7 ? "aprovado" : media >= 5 ? "recuperação" : "reprovado";
System.out.println("Situação: " + situacao);

Funciona, mas com mais de duas opções fica difícil de ler; a cadeia if / else if é a forma mais clara para faixas.

Saída esperada

Média: 5.75
Situação: recuperação

02Desconto e frete no fechamento do pedidoMédio

Feche um pedido de R$ 180 de uma cliente VIP que usou o cupom FRETEGRATIS. Ela ganha 10% de desconto se a compra for de pelo menos R$ 200 ou se for VIP, e o frete de R$ 15 é zerado se o cupom for FRETEGRATIS e o valor com desconto for de pelo menos R$ 100.

↳ Requisitos
  • A regra do desconto usa || e a do frete usa &&, cada uma num único if.
  • O cupom é comparado com equals, não com ==.
  • A linha do frete usa um ternário para mostrar grátis ou o valor.
  • Todos os valores são int, e a saída é idêntica à esperada.
código inicial
public class Checkout {

    public static void main(String[] args) {
        int total = 180;
        boolean clienteVip = true;
        String cupom = "FRETEGRATIS";
        // Aplique o desconto, decida o frete e imprima o resumo.
    }
}
saída esperada
Subtotal: R$ 180
Desconto: R$ 18
Frete: grátis
Total a pagar: R$ 162
Ver dica

Decida o desconto antes do frete: a regra do frete olha o valor que sobrou depois do desconto.

Ver resposta
public class Checkout {

    public static void main(String[] args) {
        int total = 180;
        boolean clienteVip = true;
        String cupom = "FRETEGRATIS";

        // ||: basta uma das condições para ganhar 10% de desconto.
        int desconto = 0;
        if (total >= 200 || clienteVip) {
            desconto = total * 10 / 100;
        }
        int comDesconto = total - desconto;

        // &&: o cupom precisa estar certo E a compra valer pelo menos R$ 100.
        int frete = 15;
        if (cupom.equals("FRETEGRATIS") && comDesconto >= 100) {
            frete = 0;
        }

        System.out.println("Subtotal: R$ " + total);
        System.out.println("Desconto: R$ " + desconto);
        System.out.println("Frete: " + (frete == 0 ? "grátis" : "R$ " + frete));
        System.out.println("Total a pagar: R$ " + (comDesconto + frete));
    }
}

Passo a passo

  1. As três variáveis guardam o cenário do pedido; total é int porque os valores são inteiros em reais.
  2. desconto começa em 0 e só muda se total >= 200 || clienteVip for verdadeiro; como clienteVip é true, a cliente ganha o desconto mesmo com a compra abaixo de R$ 200.
  3. total * 10 / 100 multiplica antes de dividir, então 1800 / 100 dá 18 exato, sem perder nada na divisão inteira.
  4. frete começa em 15 e só vira 0 quando as duas condições do && são verdadeiras: o cupom é FRETEGRATIS e 162 >= 100.
  5. cupom.equals("FRETEGRATIS") compara o conteúdo do texto, letra por letra.
  6. Na linha do frete, frete == 0 ? "grátis" : "R$ " + frete escolhe o texto; os parênteses em volta do ternário fazem a escolha antes da concatenação com o rótulo.

Por que funciona

O || modela regras do tipo "basta um motivo" e o &&, regras do tipo "precisa de todos". Começar cada variável com o valor padrão (0 de desconto, 15 de frete) e só alterá-la dentro do if deixa o código curto: não é preciso um else que repita o valor padrão.

A ordem das regras também importa: o frete depende de comDesconto, então o desconto precisa ser calculado antes.

Erros comuns

  • Comparar texto com ==: cupom == "FRETEGRATIS" compara se as duas variáveis apontam para o mesmo objeto, não o conteúdo, e pode dar false com textos iguais (por exemplo, quando o cupom vem de uma digitação). Use equals.
  • Trocar && por ||: cupom.equals("FRETEGRATIS") || comDesconto >= 100 zera o frete de qualquer compra acima de R$ 100, mesmo sem cupom.
  • Dividir antes de multiplicar: total / 100 * 10 faz 180 / 100, que dá 1 na divisão inteira, e o desconto sai 10 em vez de 18.
  • Ternário sem parênteses: "Frete: " + frete == 0 ? ... não compila, porque o + é avaliado antes do == e o Java tenta comparar uma String com 0.

Outra forma de resolver

As condições podem virar variáveis boolean com nomes que explicam a regra:

boolean ganhaDesconto = total >= 200 || clienteVip;
int desconto = ganhaDesconto ? total * 10 / 100 : 0;

Essa forma é preferível quando a mesma condição é usada em mais de um lugar ou quando a regra tem muitas partes e um nome ajuda a lê-la.

Saída esperada

Subtotal: R$ 180
Desconto: R$ 18
Frete: grátis
Total a pagar: R$ 162

03Rolagens de ataque com switchDesafio

Num jogo de RPG, um ataque rola um dado de 20 faces: 20 é sempre acerto crítico, 1 é sempre falha crítica e qualquer outro valor acerta se o dado mais o bônus (4) alcançar a defesa (15). Classifique as rolagens 20, 11, 10 e 1 e mostre o dano de cada uma (base 8, dobrado no crítico e zero quando erra).

↳ Requisitos
  • Um método static String classificar(int dado, int bonus, int defesa) devolve o resultado com um switch de setas sobre o dado.
  • O default desse switch usa um ternário para decidir entre acerto e erro.
  • Um segundo método, calcularDano, usa um switch sobre o texto do resultado.
  • Cada linha é impressa com printf e a saída é idêntica à esperada.
código inicial
public class Ataque {

    static String classificar(int dado, int bonus, int defesa) {
        // Use um switch com -> sobre o dado.
        return "";
    }

    static int calcularDano(String resultado, int danoBase) {
        // Use um switch sobre o resultado.
        return 0;
    }

    public static void main(String[] args) {
        int bonus = 4;
        int defesa = 15;
        int danoBase = 8;
        // Classifique as rolagens 20, 11, 10 e 1 e imprima cada uma.
    }
}
saída esperada
Dado 20: acerto crítico (16 de dano)
Dado 11: acerto (8 de dano)
Dado 10: erro (0 de dano)
Dado 1: falha crítica (0 de dano)
Ver dica

Os casos especiais (20 e 1) ganham um case próprio; a conta com o bônus só interessa para os outros valores.

Ver resposta
public class Ataque {

    // Devolve o resultado de uma rolagem de d20 contra a defesa do inimigo.
    static String classificar(int dado, int bonus, int defesa) {
        return switch (dado) {
            case 20 -> "acerto crítico";
            case 1 -> "falha crítica";
            default -> dado + bonus >= defesa ? "acerto" : "erro";
        };
    }

    // O dano depende do resultado: crítico dobra, erro zera.
    static int calcularDano(String resultado, int danoBase) {
        return switch (resultado) {
            case "acerto crítico" -> danoBase * 2;
            case "acerto" -> danoBase;
            default -> 0;
        };
    }

    public static void main(String[] args) {
        int bonus = 4;
        int defesa = 15;
        int danoBase = 8;

        String r1 = classificar(20, bonus, defesa);
        String r2 = classificar(11, bonus, defesa);
        String r3 = classificar(10, bonus, defesa);
        String r4 = classificar(1, bonus, defesa);

        System.out.printf("Dado 20: %s (%d de dano)%n", r1, calcularDano(r1, danoBase));
        System.out.printf("Dado 11: %s (%d de dano)%n", r2, calcularDano(r2, danoBase));
        System.out.printf("Dado 10: %s (%d de dano)%n", r3, calcularDano(r3, danoBase));
        System.out.printf("Dado 1: %s (%d de dano)%n", r4, calcularDano(r4, danoBase));
    }
}

Passo a passo

  1. classificar recebe o valor do dado, o bônus e a defesa, e devolve o resultado como String. Um método static pode ser chamado direto do main, sem criar objeto, e evita repetir a mesma decisão quatro vezes.
  2. return switch (dado) { ... }; devolve o valor da própria expressão switch; o ; depois da chave de fechamento termina o return.
  3. case 20 e case 1 tratam os valores especiais antes de qualquer conta, então um 1 com bônus alto continua sendo falha crítica.
  4. O default resolve todos os outros valores com o ternário dado + bonus >= defesa ? "acerto" : "erro": 11 + 4 = 15 alcança a defesa e acerta, enquanto 10 + 4 = 14 erra por um ponto.
  5. calcularDano faz um switch sobre o texto do resultado: o crítico dobra o dano, o acerto usa o valor base e o default cobre erro e falha crítica, que não causam dano.
  6. No main, cada rolagem é classificada uma vez e o resultado fica numa variável, que é usada tanto na impressão quanto no cálculo do dano.
  7. printf("Dado 20: %s (%d de dano)%n", ...) encaixa o texto em %s e o número em %d, terminando a linha com %n.

Por que funciona

O switch com setas é uma expressão que precisa sempre produzir um valor, por isso o compilador exige o default quando os casos não cobrem todos os inteiros ou todos os textos possíveis. Os casos fixos (20 e 1) ficam em case próprios, e a regra geral, que depende de uma comparação, fica no default com um ternário: cada ferramenta faz o que sabe fazer melhor.

Separar a classificação do cálculo de dano deixa cada decisão num lugar só. Se a regra do crítico mudar (triplicar o dano, por exemplo), basta alterar uma linha de calcularDano.

Erros comuns

  • Testar a soma antes dos casos especiais: if (dado + bonus >= defesa) ... else if (dado == 20) nunca chega ao crítico, porque 20 + 4 já passa pelo primeiro teste e sai como acerto simples. Trate o 20 e o 1 primeiro.
  • Esquecer o default: um switch expressão sobre int só com case 20 e case 1 não compila ("the switch expression does not cover all possible input values").
  • Misturar seta e dois-pontos: case 20: "acerto crítico"; dentro de um switch com -> não compila. Use só setas, e yield se o caso precisar de um bloco.
  • Comparar o resultado com ==: if (resultado == "acerto") depende de os textos serem o mesmo objeto. No switch sobre String, o Java já compara com equals; fora dele, use equals também.
  • Esquecer o ; depois do switch: return switch (dado) { ... } sem ; gera "';' expected", porque o switch aqui é parte de uma instrução return.

Outra forma de resolver

A mesma classificação pode ser escrita com uma cadeia if / else if:

static String classificar(int dado, int bonus, int defesa) {
    if (dado == 20) {
        return "acerto crítico";
    } else if (dado == 1) {
        return "falha crítica";
    } else if (dado + bonus >= defesa) {
        return "acerto";
    }
    return "erro";
}

O if é preferível quando cada ramo depende de uma condição diferente; o switch fica mais limpo quando as decisões giram em torno de valores fixos de uma mesma variável, como os lados de um dado.

Saída esperada

Dado 20: acerto crítico (16 de dano)
Dado 11: acerto (8 de dano)
Dado 10: erro (0 de dano)
Dado 1: falha crítica (0 de dano)

Últimas lições

Ver o arquivo completo →