Sua dose diária de programação

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

✦   Lição

Operadores

Operadores são os símbolos que fazem contas, comparam e combinam valores. Os aritméticos (+, -, *, /, %) seguem a matemática: *, / e % têm precedência sobre + e -, e operadores do mesmo nível são avaliados da esquerda para a direita. Por isso 2 + 3 * 4 dá 14, e 120 / 6 * 2 dá 40. Quando a ordem natural não serve, os parênteses mandam.

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

Operadores

por Sutton Ashford
Iniciante

Entenda o conceito

Operadores são os símbolos que fazem contas, comparam e combinam valores. Os aritméticos (+, -, *, /, %) seguem a matemática: *, / e % têm precedência sobre + e -, e operadores do mesmo nível são avaliados da esquerda para a direita. Por isso 2 + 3 * 4 dá 14, e 120 / 6 * 2 dá 40. Quando a ordem natural não serve, os parênteses mandam.

Os operadores de atribuição composta (+=, -=, *=, /=, %=) encurtam x = x + y para x += y, e têm um detalhe: fazem um casting escondido para o tipo da variável, então, com pontos declarado como int, pontos += 38.4; compila e descarta os decimais. Já ++ e -- somam ou subtraem 1, e a posição importa quando o valor é usado na mesma expressão: n++ devolve o valor antigo e depois incrementa; ++n incrementa e devolve o novo.

Os relacionais (==, !=, <, <=, >, >=) produzem um boolean, que pode ser guardado numa variável, e os lógicos combinam esses valores. Além de && e ||, que fazem curto-circuito, existem & e |, que avaliam sempre os dois lados, e ^, o ou exclusivo, verdadeiro quando só um dos lados é true.

Duas armadilhas comuns: o + com uma String vira concatenação, então "Dias: " + 17 + 14 imprime Dias: 1714; e o % mantém o sinal do dividendo (-15 % 7 é -1). Para um resto sempre positivo, como em contas circulares, use Math.floorMod.

↳ Pontos-chave
  • *, / e % vêm antes de + e -; no mesmo nível, a conta vai da esquerda para a direita. Na dúvida, use parênteses.
  • x += y equivale a x = (tipo de x) (x + y): o casting escondido faz int aceitar um double, descartando os decimais.
  • n++ devolve o valor antigo e ++n o novo; sozinhos numa linha, os dois têm o mesmo efeito.
  • Com uma String à esquerda, + concatena: "a" + 1 + 2 é "a12", mas "a" + (1 + 2) é "a3".
  • & e | avaliam os dois lados, && e || param cedo, ^ é o ou exclusivo; para resto sem sinal negativo, Math.floorMod.

Do conceito à prática

3 exemplos para ver a ideia funcionando.

01Placar de partida com precedência e parênteses

Placar.java
public class Placar {

    public static void main(String[] args) {
        int abates = 9;
        int assistencias = 4;
        int mortes = 4;

        // * e / vêm antes de + e -: as multiplicações são feitas primeiro.
        int pontos = abates * 100 + assistencias * 50 - mortes * 30;
        System.out.println("Pontos: " + pontos);

        // Parênteses mudam a ordem: a soma acontece antes da multiplicação.
        int bonusEquipe = (abates + assistencias) * 10;
        int semParenteses = abates + assistencias * 10;
        System.out.println("Bônus da equipe: " + bonusEquipe);
        System.out.println("Sem parênteses daria: " + semParenteses);

        // Operadores de mesma precedência são avaliados da esquerda para a direita.
        System.out.println("100 - 30 - 20 = " + (100 - 30 - 20));
        System.out.println("120 / 6 * 2 = " + (120 / 6 * 2));

        // O casting vem antes da divisão, então a conta é feita em double.
        double razao = (double) abates / mortes;
        System.out.println("Razão abates/mortes: " + razao);

        // Relacionais produzem boolean, que pode ir para uma variável.
        boolean recorde = pontos > 1000;
        boolean empate = pontos == 1000;
        System.out.println("Bateu o recorde? " + recorde);
        System.out.println("Empatou com o recorde? " + empate);
    }
}

Em abates * 100 + assistencias * 50 - mortes * 30, as três multiplicações são feitas antes da soma e da subtração, como na matemática, e o resultado é 980. Já em bonusEquipe, os parênteses obrigam a somar primeiro: (9 + 4) * 10 dá 130, enquanto sem eles a multiplicação vence e sobra 49. As linhas 100 - 30 - 20 e 120 / 6 * 2 mostram que operadores de mesmo nível vão da esquerda para a direita.

No fim, pontos > 1000 e pontos == 1000 não estão dentro de um if: eles produzem um boolean que fica guardado numa variável e pode ser impresso ou reutilizado depois.

02Carrinho com atribuição composta e incremento

Carrinho.java
public class Carrinho {

    public static void main(String[] args) {
        int itens = 0;
        int totalCentavos = 0;

        // total += x é um atalho para total = total + x.
        totalCentavos += 2_590;        // uma camiseta
        itens++;                       // ++ soma 1
        totalCentavos += 1_250 * 2;    // duas canecas
        itens += 2;
        System.out.println("Itens: " + itens + " | Total: " + totalCentavos + " centavos");

        // -= e -- desfazem: a cliente tirou uma caneca.
        totalCentavos -= 1_250;
        itens--;
        System.out.println("Itens: " + itens + " | Total: " + totalCentavos + " centavos");

        // Pós-incremento devolve o valor antigo; pré-incremento, o novo.
        int numeroPedido = 41;
        int atual = numeroPedido++;    // atual recebe 41, depois numeroPedido vira 42
        int seguinte = ++numeroPedido; // numeroPedido vira 43, depois seguinte recebe 43
        System.out.println("atual=" + atual + " seguinte=" + seguinte + " numeroPedido=" + numeroPedido);

        // *=, /= e %= seguem a mesma ideia.
        int canecasNoEstoque = 30;
        canecasNoEstoque *= 2;         // chegou outro lote igual: 60
        int caixasCheias = canecasNoEstoque / 8;
        canecasNoEstoque %= 8;         // o que não fecha uma caixa de 8
        System.out.println("Caixas cheias: " + caixasCheias + " | Avulsas: " + canecasNoEstoque);

        // A atribuição composta faz um casting escondido para o tipo da variável.
        int pontosFidelidade = 100;
        pontosFidelidade += totalCentavos * 0.01; // 100 + 38.4 vira 138
        // pontosFidelidade = pontosFidelidade + totalCentavos * 0.01; // não compila
        System.out.println("Pontos de fidelidade: " + pontosFidelidade);
    }
}

O carrinho é atualizado só com atalhos: += e -= mexem no total, ++ e -- na quantidade, e *=, / e %= repartem o estoque em caixas de 8. O trecho do número do pedido mostra a diferença entre pós e pré-incremento: numeroPedido++ entrega 41 e só depois incrementa; ++numeroPedido incrementa antes e entrega 43.

A última conta revela o casting escondido da atribuição composta: totalCentavos * 0.01 é 38.4, um double, e mesmo assim pontosFidelidade += ... compila e guarda 138. A forma longa, comentada logo abaixo, não compila, porque somar int com double dá double e o Java não estreita sozinho numa atribuição comum.

03Devolução na biblioteca: concatenação, &, ^ e floorMod

Devolucao.java
public class Devolucao {

    static int consultasReserva = 0;

    // Conta as chamadas para mostrar quando o lado direito é avaliado.
    static boolean semReserva() {
        consultasReserva++;
        return true;
    }

    public static void main(String[] args) {
        int diasEmprestado = 17;
        int prazo = 14;
        boolean atrasado = diasEmprestado > prazo;

        // Com String, o + é avaliado da esquerda para a direita.
        System.out.println("Dias: " + diasEmprestado + prazo);       // concatena: 1714
        System.out.println("Atraso: " + (diasEmprestado - prazo));   // parênteses: 3

        // && não avalia o lado direito se o esquerdo já é false; & sempre avalia.
        boolean renovaCurto = !atrasado && semReserva();
        System.out.println("Renova (&&)? " + renovaCurto + " | consultas: " + consultasReserva);
        boolean renovaCompleto = !atrasado & semReserva();
        System.out.println("Renova (&)? " + renovaCompleto + " | consultas: " + consultasReserva);

        // ^ (ou exclusivo) é true quando exatamente um dos lados é true.
        boolean trouxeCarteirinha = true;
        boolean trouxeDocumento = true;
        System.out.println("Só um documento? " + (trouxeCarteirinha ^ trouxeDocumento));

        // Dias da semana de 0 (domingo) a 6 (sábado); hoje é terça (2).
        int hoje = 2;
        System.out.println("Prazo para pagar a multa: dia " + (hoje + 5) % 7);
        // Voltar no tempo dá número negativo: % mantém o sinal, floorMod não.
        System.out.println("Pegou no dia (com %): " + (hoje - diasEmprestado) % 7);
        System.out.println("Pegou no dia (floorMod): " + Math.floorMod(hoje - diasEmprestado, 7));
    }
}

A primeira linha imprime 1714 porque o + é avaliado da esquerda para a direita: "Dias: " + 17 já é uma String, e o 14 é colado no fim. Com parênteses, a subtração acontece antes e o atraso sai certo. O método semReserva conta as próprias chamadas: com &&, o lado esquerdo já é false e ele nem roda; com &, roda mesmo assim, o que só é útil quando o lado direito precisa executar sempre.

O ^ dá false porque os dois documentos foram trazidos, e a pergunta é se veio só um. Por fim, (2 - 17) % 7 dá -1, porque o % do Java mantém o sinal do dividendo, enquanto Math.floorMod devolve 6, o sábado, que é o dia certo numa semana circular.


Agora é com você

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

01Dobrar a receita de boloFácil

Uma receita de bolo rende 4 porções com 200 g de farinha, 120 g de açúcar e 3 ovos. Dobre a receita, tire um quarto do açúcar já dobrado e acrescente um ovo, usando operadores de atribuição composta e incremento.

↳ Requisitos
  • A classe se chama Ingredientes, e as quatro quantidades são variáveis int.
  • A receita é dobrada com *= em cada variável, sem reescrever os valores na mão.
  • O açúcar é reduzido com -= e o ovo extra é somado com ++.
  • A saída é idêntica à esperada.
código inicial
public class Ingredientes {

    public static void main(String[] args) {
        int porcoes = 4;
        int farinha = 200;
        int acucar = 120;
        int ovos = 3;
        // Dobre a receita, reduza o açúcar, some um ovo e imprima.
    }
}
saída esperada
Porções: 8
Farinha: 400 g
Açúcar: 180 g
Ovos: 7
Ver dica

Um quarto do açúcar é o próprio açúcar dividido por 4, e a redução só pode acontecer depois de dobrar.

Ver resposta
public class Ingredientes {

    public static void main(String[] args) {
        int porcoes = 4;
        int farinha = 200;   // gramas
        int acucar = 120;    // gramas
        int ovos = 3;

        // Dobra a receita com atribuição composta.
        porcoes *= 2;
        farinha *= 2;
        acucar *= 2;
        ovos *= 2;

        // Menos doce: tira um quarto do açúcar atual.
        acucar -= acucar / 4;
        // Um ovo a mais para dar liga.
        ovos++;

        System.out.println("Porções: " + porcoes);
        System.out.println("Farinha: " + farinha + " g");
        System.out.println("Açúcar: " + acucar + " g");
        System.out.println("Ovos: " + ovos);
    }
}

Passo a passo

  1. As quatro quantidades começam como int, porque são porções, gramas e ovos inteiros.
  2. porcoes *= 2; é o atalho de porcoes = porcoes * 2;, e a mesma linha se repete para farinha, açúcar e ovos.
  3. acucar -= acucar / 4; calcula primeiro o lado direito (240 / 4 dá 60) e depois subtrai do valor atual, deixando 180.
  4. ovos++; soma 1 à variável; sozinho numa linha, tanto faz escrever ovos++ ou ++ovos.
  5. Os println juntam o rótulo, o valor e a unidade com +, já que a primeira parte é uma String.

Por que funciona

Cada operador composto lê o valor atual da variável, aplica a operação com o lado direito e guarda o resultado na própria variável. Como a redução do açúcar vem depois do *=, ela usa o valor já dobrado: 240 - 60 dá 180. A ordem das linhas é a ordem das mudanças, e é por isso que o resultado bate com a receita.

Erros comuns

  • Dividir em vez de subtrair: acucar /= 4; deixa só um quarto do açúcar (60 g) em vez de tirar um quarto. O pedido é acucar -= acucar / 4;, que mantém três quartos.
  • Inverter o operador: acucar =- acucar / 4; compila, mas é lido como acucar = -acucar / 4 e guarda -60. O símbolo é -=, com o sinal antes do igual.
  • Trocar o operador por um valor fixo: farinha = 400; imprime o mesmo resultado, mas quebra quando a receita base mudar e não usa o operador pedido.

Outra forma de resolver

Escrever acucar = acucar * 3 / 4; também funciona e deixa claro que sobram três quartos:

acucar = acucar * 3 / 4;   // 240 * 3 / 4 = 180

Repare que a multiplicação vem antes da divisão; com acucar * (3 / 4), o 3 / 4 inteiro daria 0. É preferível quando a ideia é "fica uma fração", e não "tira uma parte".

Saída esperada

Porções: 8
Farinha: 400 g
Açúcar: 180 g
Ovos: 7

02Média ponderada e frequência no boletimMédio

Um aluno tirou 7, 8 e 5 nas provas, com pesos 2, 3 e 5, e faltou a 6 de 40 aulas. Mostre a conta escrita sem parênteses, a média ponderada correta, a frequência em porcentagem inteira e se ele está aprovado e se é destaque, guardando as respostas em variáveis boolean.

↳ Requisitos
  • A linha "Sem parênteses" usa a mesma expressão da média, só que sem os parênteses, para mostrar o efeito da precedência.
  • A frequência é um int calculado multiplicando por 100 antes de dividir pelo total de aulas.
  • aprovado exige média de pelo menos 6 e frequência de pelo menos 75; destaque exige média de pelo menos 9 ou nenhuma falta. Nada de if.
  • A saída é idêntica à esperada.
código inicial
public class Boletim {

    public static void main(String[] args) {
        int nota1 = 7, nota2 = 8, nota3 = 5;
        int peso1 = 2, peso2 = 3, peso3 = 5;
        int aulas = 40;
        int faltas = 6;
        // Calcule as contas e guarde as respostas em variáveis boolean.
    }
}
saída esperada
Sem parênteses: 40.5
Média ponderada: 6.3
Frequência: 85%
Aprovado? true
Destaque? false
Ver dica

Na frequência, a ordem da multiplicação e da divisão muda o resultado quando tudo é int.

Ver resposta
public class Boletim {

    public static void main(String[] args) {
        int nota1 = 7, nota2 = 8, nota3 = 5;
        int peso1 = 2, peso2 = 3, peso3 = 5;
        int aulas = 40;
        int faltas = 6;

        // Sem parênteses, só o último produto é dividido.
        double errada = nota1 * peso1 + nota2 * peso2 + nota3 * peso3 / 10.0;
        // Com parênteses, a soma inteira é dividida pela soma dos pesos.
        double media = (nota1 * peso1 + nota2 * peso2 + nota3 * peso3) / 10.0;
        // Multiplicar antes de dividir evita perder a parte decimal no int.
        int frequencia = (aulas - faltas) * 100 / aulas;

        // Relacionais e lógicos guardam a resposta em boolean.
        boolean aprovado = media >= 6 && frequencia >= 75;
        boolean destaque = media >= 9 || faltas == 0;

        System.out.println("Sem parênteses: " + errada);
        System.out.println("Média ponderada: " + media);
        System.out.println("Frequência: " + frequencia + "%");
        System.out.println("Aprovado? " + aprovado);
        System.out.println("Destaque? " + destaque);
    }
}

Passo a passo

  1. Notas, pesos, aulas e faltas são int; a vírgula na declaração (int nota1 = 7, nota2 = 8) cria várias variáveis do mesmo tipo numa linha.
  2. Em errada, a precedência faz só nota3 * peso3 / 10.0 virar 2.5, que é somado a 14 + 24: o resultado absurdo 40.5 mostra o problema.
  3. Em media, os parênteses somam os três produtos (63) antes de dividir por 10.0; como 10.0 é double, a divisão não descarta o .3.
  4. (aulas - faltas) * 100 / aulas faz 34 * 100 primeiro e só depois divide, dando 85; dividir antes daria 0.
  5. media >= 6 && frequencia >= 75 junta duas comparações com o "e" lógico, e o resultado vai direto para a variável aprovado.
  6. media >= 9 || faltas == 0 é false, porque nenhum dos dois lados é verdadeiro.

Por que funciona

A precedência decide a ordem das operações sem olhar para a intenção de quem escreveu, por isso os parênteses na média são obrigatórios. Na frequência, a ordem da esquerda para a direita é aproveitada: multiplicar primeiro mantém a precisão antes da divisão inteira. Comparações são expressões como quaisquer outras, então seu resultado boolean pode ser guardado e impresso sem passar por um if.

Erros comuns

  • Dividir por um int: (...) / 10 faz divisão inteira e imprime 6.0. Um dos lados precisa ser double, como 10.0.
  • Dividir antes de multiplicar: (aulas - faltas) / aulas * 100 dá 0, porque 34 / 40 entre inteiros é 0.
  • Concatenar a comparação sem parênteses: "Aprovado? " + media >= 6 não compila ("bad operand types for binary operator '>='"), porque o + vem antes e compara uma String com um número. Guarde em variável ou use parênteses.
  • Usar = no lugar de ==: faltas = 0 é atribuição e não compila dentro do ||, já que produz um int.

Outra forma de resolver

Os pesos podem ser somados por uma expressão, em vez do 10.0 fixo, o que evita erro se um peso mudar:

int somaPesos = peso1 + peso2 + peso3;
double media = (double) (nota1 * peso1 + nota2 * peso2 + nota3 * peso3) / somaPesos;

Aqui o casting fica do lado de fora dos parênteses de propósito: a soma inteira não perde nada, e a conversão acontece antes da divisão.

Saída esperada

Sem parênteses: 40.5
Média ponderada: 6.3
Frequência: 85%
Aprovado? true
Destaque? false

03Tabuleiro circular com resto e floorModDesafio

Num jogo de tabuleiro circular com 12 casas (0 a 11), um peão está na casa 9 com 50 moedas. No turno 1 ele anda 7 casas e ganha 200 moedas por volta completa; no turno 2 tira a carta "volte 6 casas". Mostre cada turno, as moedas, se a casa final é de bônus (par e diferente de 0) e o número do próximo turno.

↳ Requisitos
  • As voltas saem com / e a nova casa com %, ambos sobre a posição mais o dado; as moedas sobem com +=.
  • O recuo usa Math.floorMod, e a linha do turno 2 também mostra o valor negativo que o % daria.
  • O número do turno é impresso com turno++ dentro do próprio println, sem linhas separadas para incrementar.
  • O bônus é um boolean calculado com %, ==, != e &&, sem if.
código inicial
public class Tabuleiro {

    public static void main(String[] args) {
        final int CASAS = 12;
        int posicao = 9;
        int moedas = 50;
        int turno = 1;
        // Turno 1: ande 7 casas. Turno 2: volte 6 casas.
    }
}
saída esperada
Turno 1: casa 4, voltas: 1
Turno 2: casa 10 (com % seria -2)
Moedas: 250
Casa de bônus? true
Próximo turno: 3
Ver dica

Andar de 9 até 16 num círculo de 12 casas passa pela largada uma vez; o resto mostra onde o peão para.

Ver resposta
public class Tabuleiro {

    public static void main(String[] args) {
        final int CASAS = 12;     // casas numeradas de 0 a 11, em círculo
        int posicao = 9;
        int moedas = 50;
        int turno = 1;

        // Turno 1: o dado tira 7. / conta as voltas e % acha a casa.
        int destino = posicao + 7;
        int voltas = destino / CASAS;
        posicao = destino % CASAS;
        moedas += voltas * 200;   // cada volta completa paga 200 moedas
        System.out.println("Turno " + turno++ + ": casa " + posicao + ", voltas: " + voltas);

        // Turno 2: carta "volte 6 casas". % deixaria o resultado negativo.
        int comResto = (posicao - 6) % CASAS;
        posicao = Math.floorMod(posicao - 6, CASAS);
        System.out.println("Turno " + turno++ + ": casa " + posicao + " (com % seria " + comResto + ")");

        // Casa par diferente da largada dá bônus.
        boolean bonus = posicao % 2 == 0 && posicao != 0;
        System.out.println("Moedas: " + moedas);
        System.out.println("Casa de bônus? " + bonus);
        System.out.println("Próximo turno: " + turno);
    }
}

Passo a passo

  1. CASAS é final porque o tamanho do tabuleiro não muda; o nome em maiúsculas segue a convenção de constantes.
  2. destino = posicao + 7 dá 16. 16 / 12 é 1 volta completa, e 16 % 12 é 4, a casa onde o peão para.
  3. moedas += voltas * 200; multiplica primeiro, por precedência, e depois soma ao saldo: 50 + 200 dá 250.
  4. "Turno " + turno++ + ... usa o valor atual de turno (1) na mensagem e só depois o incrementa para 2; o mesmo acontece no turno 2.
  5. (posicao - 6) % CASAS é -2 % 12, que dá -2: o % do Java mantém o sinal do dividendo e não serve para recuar num círculo.
  6. Math.floorMod(posicao - 6, CASAS) devolve 10, sempre entre 0 e 11, que é a casa certa ao recuar 6 a partir da 4.
  7. posicao % 2 == 0 && posicao != 0 resolve primeiro o %, depois as comparações e por último o &&, então não precisa de parênteses.
  8. Na última linha, turno já vale 3, porque cada turno++ anterior somou 1 depois de ser usado.

Por que funciona

O par / e % decompõe um número em "quantas vezes coube" e "quanto sobrou", que no tabuleiro são as voltas e a casa. Para andar para trás, o resto precisa ficar sempre na faixa de 0 a 11, e é exatamente isso que Math.floorMod garante, enquanto o % devolve um número negativo que não corresponde a nenhuma casa.

O pós-incremento dentro do println mostra que turno++ é uma expressão com valor: ela entrega o número antigo para a concatenação e deixa a variável atualizada para a próxima linha. A precedência (% antes de ==, que vem antes de &&) permite escrever o bônus sem parênteses extras.

Erros comuns

  • Recuar com %: posicao = (posicao - 6) % CASAS; deixa o peão na casa -2, que não existe. Use Math.floorMod, ou some CASAS antes: (posicao - 6 + CASAS) % CASAS.
  • Pré-incremento no lugar do pós: "Turno " + ++turno imprime Turno 2 e Turno 3, pulando o primeiro turno.
  • Contar a volta depois de mudar a posição: calcular voltas = posicao / CASAS depois de posicao = destino % CASAS dá 0, porque a posição já foi reduzida. Guarde o destino antes.
  • Esquecer os parênteses no comResto: posicao - 6 % CASAS é lido como posicao - (6 % 12), e imprime -2 só por coincidência; com outra posição o valor sairia errado.

Outra forma de resolver

Como o tamanho do tabuleiro é positivo, somar CASAS antes do % também evita o negativo, desde que o recuo seja menor que o tabuleiro:

posicao = (posicao - 6 + CASAS) % CASAS;   // (4 - 6 + 12) % 12 = 10

É um truque comum em código antigo, mas falha com recuos maiores que 12 casas; Math.floorMod funciona com qualquer valor e deixa a intenção explícita.

Saída esperada

Turno 1: casa 4, voltas: 1
Turno 2: casa 10 (com % seria -2)
Moedas: 250
Casa de bônus? true
Próximo turno: 3
← Anterior na trilha de JavaDecisões com condicionais

Últimas lições

Ver o arquivo completo →