[RESOLVIDO] Verificação do tipo de leitura

Olá! Sou iniciante em java e estou com um problema.

Estou criando um gerador de senha aleatória e em uma das etapas o usuário entra com o tamanho da senha que deseja gerar. Criei o método a seguir para fazer a leitura, porém se ele digita uma letra, por exemplo, o programa dá errou pois estou tentando ler um inteiro e não foi esse o tipo lido.

private static int readLength() {
        Scanner scanner = new Scanner(System.in);
        return scanner.nextInt();
    }

Eu pensei em fazer a leitura de uma string ao invés de inteiro, que aí abrangeria qualquer coisa que o usuário digitar, e trabalhar na verificação da string antes de continuar. Se for uma boa ideia, como recomendam essa verificação? E se não for, o que acham que seria prático fazer? Não me parece ser um problema para ter que criar um novo método ou classe só pra tratar isso, mas como sou iniciante, queria essa opinião de vocês.

PS: existe algum instanceof para primitivos?

Obrigado!

Pensei na seguinte solução:

import java.util.Scanner;

public class Programa {
  private static final Scanner scan = new Scanner(System.in);

  public static void main(String... args) {
    final int length = readLength();
    System.out.println("Vc escolheu " + length);
    scan.close();
  }

  private static int readLength() {
    try {
      System.out.print("Insira um número: ");
      return Integer.parseInt(scan.nextLine());
    } catch (final NumberFormatException e) {
      System.out.println("Número inválido!");
      return readLength();
    }
  }
}

Sempre que o usuario inserir qualquer coisa que não seja um número, ele chama novamente o método readLength().

Não. Segundo a especificação, os operandos do operador instanceof deve ser um Reference Type, ou seja, não pode ser um tipo primitivo.

Fonte: https://docs.oracle.com/javase/specs/jls/se15/html/jls-15.html#jls-15.20.2

Mas como os tipos primitivos tem limites bem definidos, vc poderia fazer algo assim:

public class Programa {
  public static void main(String... args) {
    long number = 123;

    if (number <= Byte.MAX_VALUE) {
      System.out.println("é byte");
    } else if (number <= Short.MAX_VALUE) {
      System.out.println("é short");
    } else if (number <= Integer.MAX_VALUE) {
      System.out.println("é int");
    } else {
      System.out.println("é long");
    }
  }
}

Obs.: Nem sei onde vc usaria um código assim, mas tá aí de exemplo!

1 curtida

O problema de chamar o método dentro dele mesmo é que se isso se repetir um número suficiente de vezes, pode causar um estouro de pilha - pois cada chamada recursiva é empilhada e elas ficam ocupando espaço na pilha até que uma delas retorne (no caso, somente quando um número válido for digitado). Veja aqui como ocorre o StackOverflowError depois de várias chamadas (fiz um exemplo sem Scanner apenas para mostrar como o problema ocorre, então imagine que o usuário digitou errado muitas vezes, o erro seria o mesmo).

Claro que se rodar poucas vezes, o problema não ocorre, mas nesse caso, por que não usar simplesmente um loop, que evita esse problema em qualquer situação?

private static int readLength() {
    while (true) {
        try {
          System.out.print("Insira um número: ");
          return Integer.parseInt(scan.nextLine());
        } catch (final NumberFormatException e) {
          System.out.println("Número inválido!");
        }
    }
}

Enquanto não for digitado um número, o while continua. Quando for digitado um número, o método retorna. E nunca ocorrerá o estouro de pilha, pois é somente uma chamada do método (em vez de uma nova chamada a cada vez que digitar errado).

Não use recursão onde não precisa (e na maioria das vezes não precisa).

Outro detalhe é que não precisa fechar o System.in. Claro que fechar qualquer recurso que abrimos geralmente é uma boa ideia, mas o System.in é “especial” (gerenciado pela JVM) e na prática não precisa fechá-lo (leia mais sobre isso aqui e aqui).

2 curtidas

Porque eu não tinha pensado nisso. Sua idéia ficou muito melhor!

Entendi. Eu sempre recomendo pra quem pede ajuda aqui que feche o Scanner, inclusive usando o try-with-resources. Eu achava que só fechava o Scanner e não o System.in.

No caso o melhor a se fazer é ignorar os avisos e nunca fechar? Sei que pros exercicios que vemos aqui não vai fazer diferença e pra um projeto sério que realmente precise do Scanner precisaria de uma solução mais robusta.

Ao fechar o Scanner, a stream que ele usa também é fechada.

Claro que, como regra geral, sempre é bom fechar todo recurso que abrimos, mas o System.in é exceção, porque ele é gerenciado pela JVM e uma vez fechado, você não consegue reabri-lo (o mesmo vale para System.out e System.err, aliás).

1 curtida

Muito obrigado pelas respostas!

Essa foi a linha que resolveu meu problema, já fazendo a verificação na própria leitura, muito bom!

Sim, o problema do StackOverFlow já havia me ocorrido em exercícios passados e já estava usando este recurso do loop em outra parte do código, onde o usuário entrava com um SIM ou NÃO para “incluir letras maiúsculas?” “incluir números?” “incluir caracteres especiais?” e como a resposta permite somente duas opções, coloquei assim:

 private static boolean readOption() {
        Scanner scanner = new Scanner(System.in);
        String option;
        do {
            option = scanner.nextLine();
            if (option.compareToIgnoreCase("s") == 0) {
                return true;
            } else if (option.compareToIgnoreCase("n") == 0) {
                return false;
            } else {
                System.out.print("Opção inválida, digite novamente (s/n) ");
            }
        } while (true);
    }

Não sei se é o mais prático, mas ficou funcional

Foi mais na intenção de ao invés de retornar direto o scan.nextLine(), salvaria em uma variável aux e tentaria algo do tipo aux intanceof int para a verificação antes de dar continuidade, mas o Integer.parseInt(scan.nextLine()) foi cirúrgico.

Muito obrigado mesmo, pessoal!

1 curtida