Entrou em loop infinito num scanner(system.in) que não reinicia

Olá pessoal, estou com um problema com o código abaixo:

==============================
package outrosEstudos;

import java.util.InputMismatchException;
import java.util.NoSuchElementException;
import java.util.Scanner;

public class Tabela {

public static void main(String[] args) {
	
	
	int tamanhoTabela = 1;
	
	do {
	
	
	try {
		System.out.print("\nDigite quantas linhas você quer sua tabela?\nDigite um número inteiro e positivo: ");

		Scanner scanner = new Scanner(System.in);
		
		tamanhoTabela = scanner.nextInt();
		
		scanner.close();
		
		} catch (InputMismatchException input) {
			System.out.print("\nValor inválido, tente novamente. (input) = " + input);
		} catch (NoSuchElementException no) {			
			System.out.print("\nValor inválido, tente novamente. (no) = " + no);
		} catch (Exception e) {
			System.out.print("\nValor inválido, tente novamente. (e) = " + e);
		}
			
	} while (tamanhoTabela < 1);
	
	System.out.println("\nLinhas = " + tamanhoTabela);
}

}

Quando digito um valor textual (um “a” por exemplo) ou um número como “0”, ao invés dele voltar ao início do laço e pedir novamente o valor, o software assume que o primeiro valor digitado é o mesmo valor do scanner.

Como faço para ele não entrar em loop infinito?

Obrigado.

Inicializa tamanhoTabela com um número menor que 1.

int tamanhoTabela = 0;

Olá, agradeço. Testei mas não resolveu.

Não entendo o porquê, mas o que resolveu foi excluir a linha “scanner.close();”

2 curtidas

Para ler do System.in deves usar apenas o método nextLine() do Scanner.

2 curtidas

Não fechar o Scanner é uma má prática, sempre que usar algo que implemente a interface Closeable você deve fechar. Acredito que usar o try with resources resolva o seu problema.

try (Scanner scanner = new Scanner(System.in)) {
 [...]
}

@Rodrigo_Vidal, existe uma discussão sobre fechar ou não fechar um Closeable que usa o System.in.

Eu não sabia, foi o @hugokotsubo que comentou. Acho que vale a pena vc olhar a discussão abaixo e os links que ele compartilhou.

2 curtidas

Vlw a dica, não sabia disso, mas será que com o try-with-resources o problema se resolve? Quem vai gerenciar o close vai ser a JVM.

1 curtida

O problema é que esse try está dentro de um loop. Ao terminar a primeira iteração, o Scanner será fechado, e consequentemente o System.in também será.

Só que o System.in não pode ser reaberto (ele é “especial”, gerenciado pela JVM, e vc não consegue reabri-lo), então na segunda iteração você tentará criar o Scanner novamente e ler do System.in, que agora está fechado. Isso lançará uma exceção, que vai cair no catch, que só imprime o erro e continua o loop - e por isso entra em loop infinito.

2 curtidas

Isso não se aplica se o InputStream do Scanner for o System.in.
Aliás, quando o System.in corresponder ao teclado, não há nenhum benefício em usar o Scanner pois o único método dele que consome a quebra de linha ao pressionar o ENTER é o nextLine(), mas o povo tem mania de usar outros métodos como next(), nextInt(), nextFloat() o que causa comportamentos inesperados e frustração de quem está iniciando na programação.

2 curtidas

Sei que o tópico já foi resolvido, mas eu particularmente não gosto dessa ideia de aceitar algo que “funciona, mas não sei porquê”. É importante entender o que acontece com seu código, senão você vai passar a vida “programando por coincidência”.

O que acontece é que ao fechar um Scanner, o stream que ele usa também é fechado. Como regra geral, é importante fechar todo recurso que abrimos (arquivos, conexões de rede, de banco de dados, etc). Mas o System.in é exceção, pois ele é um recurso “especial”, gerenciado pela JVM, e uma vez fechado, você não consegue reabri-lo.

E é aí que está o problema, seu código está fechando o System.in e depois tentando usá-lo novamente. Por exemplo:

Vamos supor que primeiro digitei 0. Então tamanhoTabela será zero, o Scanner é fechado (e consequentemente, o System.in também), mas como tamanhoTabela é menor que 1, o do/while continua executando.

Então na próxima iteração ele tentará ler novamente do System.in, mas como ele está fechado, será lançada uma exceção. Então o código cai em dos catch e imprime a mensagem de erro. Como não foi possível ler do System.in, o valor de tamanhoTabela continua inalterado (ou seja, continua sendo zero), e por isso o loop continua (tenta ler novamente do System.in, que está fechado, lança exceção, etc).

Por isso que “funciona” se não fechar o Scanner, pois aí o System.in não estará fechado e será possível ler outro valor.


Mas além disso, será que você precisa criar um novo Scanner a cada iteração? Por que não criar um apenas no início? Além disso, como a entrada está sendo via teclado, é melhor usar nextLine, pois usar nextInt esconde algumas armadilhas (leia aqui para saber mais).

Então eu sugiro fazer assim:

// cria o Scanner apenas uma vez, fora do loop
Scanner scanner = new Scanner(System.in);
int tamanhoTabela;
while (true) {
    try {
        System.out.print("\nDigite quantas linhas você quer sua tabela?\nDigite um número inteiro e positivo: ");
        tamanhoTabela = Integer.parseInt(scanner.nextLine());
        if (tamanhoTabela < 1) {
            System.out.println("O número deve ser positivo");
        } else {
            break; // interrompe o while
        }
    } catch (NumberFormatException e) {
        System.out.print("\nVocê não digitou um número");
    }
}

System.out.println("\nLinhas = " + tamanhoTabela);

Ou seja, enquanto não for digitado um número válido, o loop continua. Se o número for válido, use break para interromper o loop.

Algumas pessoas “não gostam” de break e preferem fazer algo assim:

Scanner scanner = new Scanner(System.in);
int tamanhoTabela = -1;
while (tamanhoTabela < 1) {
    try {
        System.out.print("\nDigite quantas linhas você quer sua tabela?\nDigite um número inteiro e positivo: ");
        tamanhoTabela = Integer.parseInt(scanner.nextLine());
        if (tamanhoTabela < 1) {
            System.out.println("O número deve ser positivo");
        }
    } catch (NumberFormatException e) {
        System.out.print("\nVocê não digitou um número");
    }
}

System.out.println("\nLinhas = " + tamanhoTabela);
3 curtidas