Java while e do-while

Até agora, cada linha do seu programa rodava uma vez. Mas muita coisa no dia a dia é repetição: contar de 1 a 10, guardar dinheiro todo mês até bater uma meta, pedir uma senha até a pessoa acertar. Para isso existem os laços (ou loops). Nesta aula você conhece os dois mais simples: o while e o do-while.

Você vai aprender
  • O que é um laço e como o while decide se repete ou para.
  • Como montar um contador (o padrão mais comum de todos).
  • Laços que dependem de uma condição, e não de uma contagem.
  • O do-while, que roda pelo menos uma vez, e quando ele é a escolha certa.
  • O que é um laço infinito, por que ele acontece e como pará-lo com Ctrl+C.
  • Como usar o do-while para pedir um valor até ele ser válido.

Todos os exemplos foram rodados no terminal da Ana, a usuária da máquina fictícia notebook, com o JDK 25, dentro da pasta ~/java. Para rodar, usamos o modo "arquivo-fonte" que você viu em javac e java: java Main.java compila e executa de uma vez.


A ideia antes do código

Imagine que a Ana está lavando a louça. Ninguém diz a ela "lave o prato 1, lave o prato 2, lave o prato 3…". A instrução é uma só:

Enquanto ainda houver louça suja na pia, lave mais uma peça.

Repare nas duas partes dessa frase:

  • Uma pergunta que tem resposta sim ou não: "ainda há louça suja?". Em Java, é uma expressão booleana, que vale true ou false.
  • Uma tarefa que se repete enquanto a resposta for "sim": "lave mais uma peça".

E tem um detalhe escondido, mas essencial: a tarefa muda a resposta da pergunta. Cada peça lavada deixa a pia mais vazia, até que um dia a resposta vira "não" e a Ana para. Se ela só olhasse para a pia sem lavar nada, ficaria ali para sempre. Guarde isso: é a causa número 1 dos laços infinitos, que você vai ver mais adiante.

while, em inglês, quer dizer exatamente enquanto.


O primeiro while: um contador

Vamos fazer o computador escrever "Volta 1" até "Volta 5". Sem laço, seriam cinco println iguais. Com laço:

public class Main {
    public static void main(String[] args) {
        int contador = 1;
        while (contador <= 5) {
            System.out.println("Volta " + contador);
            contador++;
        }
        System.out.println("Fim! O contador parou em " + contador);
    }
}
$ java Main.java
Volta 1
Volta 2
Volta 3
Volta 4
Volta 5
Fim! O contador parou em 6

Vamos ver linha por linha:

LinhaO que faz
int contador = 1;Cria a variável que vai contar as voltas e começa em 1. É o início.
while (contador <= 5)A condição: "enquanto o contador for menor ou igual a 5". Vem sempre entre parênteses.
{ … }O corpo do laço: as linhas que se repetem.
System.out.println("Volta " + contador);A tarefa: mostra o número da volta.
contador++;Soma 1 ao contador (é o mesmo que contador = contador + 1, veja Operadores de Atribuição). É o passo, a linha que "lava mais um prato".
System.out.println("Fim! …");Está fora das chaves: roda uma vez só, depois que o laço termina.

Anatomia do while

while
A palavra-chave: "enquanto".
(contador <= 5)
A condição, entre parênteses. Precisa dar true ou false. É testada antes de cada volta.
{
Abre o corpo do laço.
…
As instruções que se repetem. Uma delas precisa mudar algo da condição.
}
Fecha o corpo. Ao chegar aqui, o Java volta para a condição e testa de novo.

O que acontece, volta por volta

Programadores chamam cada repetição de iteração (do latim iterare, "repetir"). Acompanhe o valor do contador em cada teste da condição:

Testecontador valecontador <= 5?O que o Java faz
1º1trueMostra "Volta 1", contador vira 2
2º2trueMostra "Volta 2", contador vira 3
3º3trueMostra "Volta 3", contador vira 4
4º4trueMostra "Volta 4", contador vira 5
5º5trueMostra "Volta 5", contador vira 6
6º6falsePula o corpo e continua depois da }

Repare em dois fatos que confundem muita gente:

  • A condição foi testada 6 vezes, mas o corpo rodou 5. O último teste é o que dá false e encerra o laço.
  • Depois do laço, o contador vale 6, e não 5. Foi justamente o 6 que fez a condição falhar. A saída "O contador parou em 6" confirma.
As três peças de todo contador

Início (int contador = 1;), condição (contador <= 5) e passo (contador++). Esqueceu uma, o laço quebra: sem início, nem compila; sem passo, nunca termina. O for, da próxima aula, junta essas três peças numa linha só.

Contando para baixo

O passo não precisa ser ++. Para uma contagem regressiva, comece alto, teste com >= e diminua com --:

public class Main {
    public static void main(String[] args) {
        int contador = 10;
        while (contador >= 1) {
            System.out.println(contador);
            contador--;
        }
        System.out.println("Feliz Ano Novo!");
    }
}
$ java Main.java
10
9
8
7
6
5
4
3
2
1
Feliz Ano Novo!

Missão: a Ana vai largar uma corrida. Mude o programa para contar de 5 até 1 e depois mostrar Já!. Use o while. Dica: são três mudanças (início, condição e passo) e a última mensagem.

public class Main {
    public static void main(String[] args) {
        int contador = 1;
        while (contador <= 5) {
            System.out.println(contador);
            contador++;
        }
        System.out.println("Fim");
    }
}
public class Main {
    public static void main(String[] args) {
        int contador = 5;
        while (contador >= 1) {
            System.out.println(contador);
            contador--;
        }
        System.out.println("Já!");
    }
}

Laços que não são contagem

O while brilha quando você não sabe de antemão quantas voltas serão. A Ana quer juntar R$ 1000 guardando R$ 150 por mês. Quantos meses? Em vez de fazer a conta de cabeça, deixe o laço descobrir:

public class Main {
    public static void main(String[] args) {
        double saldo = 0;
        int meses = 0;
        while (saldo < 1000) {
            saldo = saldo + 150;
            meses++;
            System.out.println("Mês " + meses + ": R$ " + saldo);
        }
        System.out.println("A meta de R$ 1000 foi atingida em " + meses + " meses.");
    }
}
$ java Main.java
Mês 1: R$ 150.0
Mês 2: R$ 300.0
Mês 3: R$ 450.0
Mês 4: R$ 600.0
Mês 5: R$ 750.0
Mês 6: R$ 900.0
Mês 7: R$ 1050.0             « 1050 não é menor que 1000: o laço para aqui
A meta de R$ 1000 foi atingida em 7 meses.

A condição agora é sobre o saldo, e não sobre um contador. A variável meses só conta quantas voltas foram dadas, para mostrar no final. (O .0 aparece porque saldo é um double, veja double e float.)

Outro exemplo: qual é a primeira potência de 2 que passa de 100? Comece em 1 e dobre enquanto for pequeno:

public class Main {
    public static void main(String[] args) {
        int numero = 1;
        while (numero < 100) {
            numero = numero * 2;
        }
        System.out.println("Primeira potência de 2 maior ou igual a 100: " + numero);
    }
}
$ java Main.java
Primeira potência de 2 maior ou igual a 100: 128

O número passou por 1, 2, 4, 8, 16, 32, 64 e 128. Quando chegou a 128, a condição 128 < 100 deu false e o laço parou.

Um while pode rodar zero vezes

Como a condição é testada antes da primeira volta, se ela já começar falsa, o corpo nunca roda. A loja da Ana tem um laço que vende uma unidade de cada vez enquanto houver estoque. E se o estoque já for zero?

public class Main {
    public static void main(String[] args) {
        int estoque = 0;
        while (estoque > 0) {
            System.out.println("Vendendo uma unidade...");
            estoque--;
        }
        System.out.println("Estoque final: " + estoque);
    }
}
$ java Main.java
Estoque final: 0             « o corpo do while nem chegou a rodar

Esse é o comportamento certo: não dá para vender o que não existe. Guarde este exemplo, porque o do-while vai se comportar de outro jeito.


O do-while: primeiro faz, depois pergunta

O do-while (do = "faça") é o while de cabeça para baixo: o corpo vem primeiro e a condição vem no fim. Por isso ele roda pelo menos uma vez, mesmo que a condição seja falsa desde o começo.

Uma comparação do dia a dia: provar a comida. Você prova primeiro e só depois decide se precisa de mais sal. Não dá para decidir antes de provar.

do
"Faça": marca o início do corpo.
{ … }
O corpo, que roda uma vez sem nenhum teste.
while
Depois da primeira volta, a pergunta "repito?".
(condição)
Se der true, volta para o do; se der false, segue em frente.
;
O ponto e vírgula obrigatório no fim. O while comum não tem; o do-while tem.

Veja a mesma loja, com estoque zero, agora com do-while:

public class Main {
    public static void main(String[] args) {
        int estoque = 0;
        do {
            System.out.println("Vendendo uma unidade...");
            estoque--;
        } while (estoque > 0);
        System.out.println("Estoque final: " + estoque);
    }
}
$ java Main.java
Vendendo uma unidade...      « rodou uma vez antes de testar a condição
Estoque final: -1            « vendeu o que não tinha!

A condição estoque > 0 era falsa desde o início, mas o corpo rodou uma vez antes de ela ser testada, e o estoque ficou negativo. Aqui o do-while foi a escolha errada. Ele é a escolha certa quando a primeira volta precisa acontecer, como ao pedir um dado ao usuário: você precisa perguntar uma vez antes de saber se a resposta é válida. É o que vem a seguir.

whiledo-while
Quando testa a condiçãoAntes de cada voltaDepois de cada volta
Mínimo de voltas01
Ponto e vírgula no fimNãoSim: } while (…);
Com a condição falsa desde o inícioNão roda nadaRoda uma vez
Uso típicoRepetir enquanto algo for verdade (que pode já ser falso)Fazer algo e repetir se precisar (menus, pedir dados)

Validar a entrada com do-while

A Ana está fazendo um programa para lançar as notas da turma. A nota precisa estar entre 0 e 10. Se o professor digitar 15 por engano, o programa deve reclamar e pedir de novo, quantas vezes for preciso.

Para ler o que a pessoa digita, usamos o Scanner, que tem uma aula só para ele: Ler do Teclado (Scanner). Por enquanto, basta saber que teclado.nextInt() espera a pessoa digitar um número inteiro e apertar Enter.

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner teclado = new Scanner(System.in);
        int nota;
        do {
            System.out.print("Digite uma nota de 0 a 10: ");
            nota = teclado.nextInt();
            if (nota < 0 || nota > 10) {
                System.out.println("Nota inválida! Tente de novo.");
            }
        } while (nota < 0 || nota > 10);
        System.out.println("Nota registrada: " + nota);
    }
}
LinhaO que faz
import java.util.Scanner;Avisa que vamos usar a classe Scanner (veja Scanner).
Scanner teclado = new Scanner(System.in);Cria o "leitor do teclado".
int nota;Declara a variável fora do laço, porque ela será usada na condição e depois do laço.
do { … }Pergunta a nota. Sempre roda pelo menos uma vez: é o que queremos.
if (nota < 0 || nota > 10)Se a nota for inválida, avisa (veja if e o "ou" || em Operadores Lógicos).
while (nota < 0 || nota > 10);Repete enquanto a nota for inválida. Uma nota válida faz a condição dar false e o laço termina.

Rodando no terminal, o professor digitou 15, depois -2 e por fim 8:

$ java Main.java
Digite uma nota de 0 a 10: 15
Nota inválida! Tente de novo.
Digite uma nota de 0 a 10: -2
Nota inválida! Tente de novo.
Digite uma nota de 0 a 10: 8
Nota registrada: 8
Por que não um while comum?

Daria, mas você teria que pedir a nota duas vezes no código: uma antes do laço (para a condição ter o que testar) e outra dentro dele. Com o do-while, o pedido aparece uma vez só. "Pergunte primeiro, verifique depois" é o caso clássico do do-while.

A mesma entrada, sem digitar

Você pode mandar as respostas prontas para o programa com o printf do terminal e um | (barra vertical, o "cano" que liga a saída de um comando à entrada do outro). Cada \n é um Enter:

$ printf '15\n-2\n8\n' | java Main.java
Digite uma nota de 0 a 10: Nota inválida! Tente de novo.
Digite uma nota de 0 a 10: Nota inválida! Tente de novo.
Digite uma nota de 0 a 10: Nota registrada: 8

O programa é o mesmo; a diferença está na tela. Quando você digita, o terminal mostra as teclas e o Enter pula a linha. Quando a entrada vem pelo |, ninguém digita nada, então os números não aparecem e as mensagens ficam emendadas. É assim também no editor do guia quando a caixa já traz a entrada pronta.


Laço infinito: quando a condição nunca fica falsa

Um laço infinito é um laço cuja condição nunca vira false. O programa fica preso dando voltas para sempre. Quase sempre é um engano, e o mais comum é esquecer o passo.

Este código tem um erro de propósito

Ele compila e roda, mas nunca termina. Se testar no seu computador, esteja pronto para apertar Ctrl+C.

public class Main {
    public static void main(String[] args) {
        int contador = 1;
        while (contador <= 5) {
            System.out.println("Volta " + contador);
        }
    }
}

Falta o contador++. O contador fica em 1 para sempre, 1 <= 5 é sempre true, e o programa escreve "Volta 1" sem parar. No terminal da Ana, em dois segundos ele escreveu mais de meio milhão de linhas, até ela apertar Ctrl+C. Este é o fim da sessão:

$ java Main.java
Volta 1
Volta 1
Volta 1                      « … mais de 500 mil linhas iguais …
^CVolta 1                    « a Ana apertou Ctrl+C; o terminal mostra ^C
Volta 1                      « algumas linhas que já estavam a caminho
Volta 1
Volta 1
Volta 1
$ echo $?
130                          « 130 = programa interrompido pelo Ctrl+C

Como parar um programa preso

  1. Clique na janela do terminal onde o programa está rodando.
  2. Aperte Ctrl+C (segure Ctrl e toque em C). O terminal mostra ^C e o programa é encerrado.
  3. O prompt ana@notebook:~/java$ volta. O código de saída, que você vê com echo $?, é 130: o jeito do Linux de dizer "interrompido pelo usuário".
  4. Conserte o laço (quase sempre falta o passo, ou ele vai na direção errada) e rode de novo.

Nas IDEs (veja Editores e IDEs), o mesmo papel é do botão de parar, um quadrado vermelho perto do botão de rodar.

O laço infinito silencioso

Nem todo laço infinito enche a tela. Este aqui quer somar 1 + 2 + 3 + 4 + 5, mas também esqueceu o passo e não escreve nada dentro do laço:

public class Main {
    public static void main(String[] args) {
        int contador = 1;
        int soma = 0;
        while (contador <= 5) {
            soma = soma + contador;
        }
        System.out.println("Soma: " + soma);
    }
}
$ java Main.java
^C                           « nada aconteceu por 4 segundos; a Ana apertou Ctrl+C
$ echo $?
130

O programa parece "travado": nenhuma mensagem, nenhum erro, o cursor parado. Na verdade ele está trabalhando sem parar, somando 1 na variável soma milhões de vezes por segundo. Quando um programa "não faz nada e não termina", desconfie de um laço infinito.

Laço infinito de propósito: while (true)

Às vezes um laço infinito é desejado: um servidor que atende pedidos o dia todo, um menu que só termina quando a pessoa escolhe "Sair". Nesses casos se escreve while (true) e, dentro dele, um break que encerra o laço na hora certa. O break tem a sua própria aula: break e continue.

Missão: este programa deveria mostrar Soma: 15 (1 + 2 + 3 + 4 + 5), mas está preso num laço infinito. Encontre a linha que falta e conserte. Leia o código com calma e conserte antes de clicar em Rodar.

public class Main {
    public static void main(String[] args) {
        int contador = 1;
        int soma = 0;
        while (contador <= 5) {
            soma = soma + contador;
        }
        System.out.println("Soma: " + soma);
    }
}
public class Main {
    public static void main(String[] args) {
        int contador = 1;
        int soma = 0;
        while (contador <= 5) {
            soma = soma + contador;
            contador++;
        }
        System.out.println("Soma: " + soma);
    }
}

Formas diferentes de repetir

Para a mesma tarefa (mostrar 1, 2 e 3) há várias formas. Elas dão a mesma saída; o que muda é a clareza.

FormaCódigoQuando usar
while com contadorint i = 1; while (i <= 3) { System.out.println(i); i++; }Funciona, mas para contar o for é mais claro.
do-whileint i = 1; do { System.out.println(i); i++; } while (i <= 3);Quando a primeira volta tem que acontecer sempre.
forfor (int i = 1; i <= 3; i++) { System.out.println(i); }Contagens: você sabe quantas voltas serão. Veja for.
while com condiçãowhile (saldo < 1000) { … }Quando o número de voltas depende do que acontece dentro do laço.

Regra prática: sabe quantas vezes? Use for. Repete até algo acontecer? Use while. Precisa fazer uma vez antes de decidir? Use do-while.


Quando dá errado

"variable contador might not have been initialized"
public class Main {
    public static void main(String[] args) {
        int contador;
        while (contador <= 5) {
            System.out.println(contador);
            contador++;
        }
    }
}
$ java Main.java
Main.java:4: error: variable contador might not have been initialized
        while (contador <= 5) {
               ^
1 error
error: compilation failed

O que quer dizer: "a variável contador pode não ter sido inicializada". Você criou a variável, mas não deu um valor inicial a ela. Na primeira volta, o Java não saberia comparar "nada" com 5, então ele se recusa a compilar. O ^ aponta para a variável problemática.

Como consertar: dê o valor inicial na declaração: int contador = 1;.

"';' expected" no fim do do-while
public class Main {
    public static void main(String[] args) {
        int contador = 1;
        do {
            System.out.println(contador);
            contador++;
        } while (contador <= 3)
    }
}
$ java Main.java
Main.java:7: error: ';' expected
        } while (contador <= 3)
                               ^
1 error
error: compilation failed

O que quer dizer: "esperava um ponto e vírgula". O do-while termina com ; depois do parêntese, e ele faltou. O ^ aponta exatamente onde o ; deveria estar.

Como consertar: } while (contador <= 3);.

O programa não termina e não mostra nada: o ; depois do while
public class Main {
    public static void main(String[] args) {
        int contador = 1;
        while (contador <= 5); {
            System.out.println("Volta " + contador);
            contador++;
        }
        System.out.println("Fim!");
    }
}
$ java Main.java
^C                           « nenhuma "Volta"; a Ana teve que apertar Ctrl+C
$ echo $?
130

O que aconteceu: o ; logo depois de while (contador <= 5) é um "corpo vazio". Para o Java, o laço é while (contador <= 5) ;, que repete nada para sempre. As chaves que vêm depois viram um bloco comum, que nunca é alcançado. O javac não reclama, porque é Java válido.

Como consertar: apague o ; entre o ) e a {. Só o do-while leva ;, e no fim.

"unreachable statement" com while (false)
public class Main {
    public static void main(String[] args) {
        int contador = 1;
        while (false) {
            System.out.println(contador);
        }
    }
}
$ java Main.java
Main.java:4: error: unreachable statement
        while (false) {
                      ^
1 error
error: compilation failed

O que quer dizer: "instrução inalcançável". Se a condição é literalmente false, o corpo nunca poderia rodar, e o javac considera isso um engano. Com uma variável (while (estoque > 0)) não há erro, porque o valor só é conhecido quando o programa roda.

Uma volta a mais ou a menos

Você queria 5 voltas e saíram 4 (ou 6). É o erro mais comum com laços e tem até nome: erro de "um a mais" (off-by-one). Confira três coisas: o valor inicial (0 ou 1?), o operador (< ou <=?) e o limite. int i = 1; while (i < 5) dá 4 voltas (1 a 4); int i = 1; while (i <= 5) dá 5. Na dúvida, monte a tabela "volta por volta" como fizemos lá em cima.


Referência

FormaO que fazMínimo de voltas
while (condição) { … }Testa e, se true, roda o corpo; repete0
do { … } while (condição);Roda o corpo e, se a condição der true, repete1
while (true) { … break; … }Laço infinito de propósito, encerrado por breakaté o break
contador++ / contador--O passo: soma ou subtrai 1
Ctrl+CInterrompe um programa no terminal (código de saída 130)

Exercícios

1. Complete a condição

Complete para o laço mostrar os números de 1 a 10:

int n = 1; while () { System.out.println(n); n++; }

2. O passo que falta

Complete o passo da contagem regressiva de 3 a 1:

int n = 3; while (n >= 1) { System.out.println(n); }

3. Fechando o do-while

Complete o fim do do-while que repete enquanto senha for diferente de 1234:

do { … }

4. Quantas vezes "Oi" aparece? int x = 10; while (x < 5) { System.out.println("Oi"); x++; }

5. E agora? int x = 10; do { System.out.println("Oi"); x++; } while (x < 5);

6. Depois de int i = 0; while (i < 3) { i++; }, quanto vale i?

7. Seu programa está rodando há um minuto sem mostrar nada nem terminar. O que fazer?

Missão final: faça o programa pedir a idade com Sua idade: e repetir a pergunta enquanto ela for menor que 1 ou maior que 120. No fim, mostre Idade registrada: seguido da idade. Use um do-while. A entrada de teste é 0, depois 200, depois 29.

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner teclado = new Scanner(System.in);
        int idade;
        // escreva o do-while aqui

    }
}
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner teclado = new Scanner(System.in);
        int idade;
        do {
            System.out.print("Sua idade: ");
            idade = teclado.nextInt();
        } while (idade < 1 || idade > 120);
        System.out.println("Idade registrada: " + idade);
    }
}

Resumo

  • Um laço repete um bloco de código. Cada repetição é uma iteração.
  • while (condição) { … } testa a condição antes de cada volta; pode rodar 0 vezes.
  • do { … } while (condição); testa depois; roda pelo menos 1 vez e termina com ;.
  • Um contador tem três peças: início, condição e passo. Depois do laço, o contador vale o primeiro valor que fez a condição falhar.
  • Se nada dentro do laço muda a condição, ele é infinito. Pare com Ctrl+C (código 130) e procure o passo que falta.
  • Cuidado com o ; logo depois de while (…): vira um laço vazio e infinito.
  • Para pedir um dado até ele ser válido, use do-while: pergunte primeiro, verifique depois.