Lendo e processando informações do teclado usando Java
Fomos contratados para desenvolver um software em Java para uma empresa e a instalação do nosso programa é feita via console.
Para conseguir usufruir do nosso produto o usuário precisa ler e aceitar os termos de uso, caso contrário não podemos permitir que a instalação continue.
Além disso, para estatísticas internas da empresa, após aceitar os termos o usuário também deve nos informar seu nome completo.
Vamos então começar criando a funcionalidade do usuário poder aceitar ou não os termos de uso do nosso produto.
Após a criação, testamos e obtivemos o seguinte resultado:
Eita, como assim? O programa não nos deixou responder, simplesmente considerou que aceitamos os termos de uso e finalizou a instalação. E agora?
Precisamos que o programa, ao chegar na pergunta "Você concorda com os termos?", espere por uma resposta do usuário e a processe de forma que, caso seja ‘S’ termine de instalar o programa, caso seja ‘N’ interrompa a instalação. Como fazer isso com Java?
Classe Scanner
Antigamente, fazer essa operação de ler informações era bem complexa. Vendo o quão recorrente essa funcionalidade era requisitada pelos desenvolvedores foi criada a classe Scanner, que tem como objetivo ler informações dentro do Java de forma mais fácil.
"Beleza cara, entendi, agora me mostre o código!"
Lendo entrada do teclado com a classe Scanner
Para usar a classe Scanner
o primeiro passo é instância-la:
mensagemDeTermos();
new Scanner();
completaInstalacao();
Note que a instanciamos logo após o método que chama a mensagem de termos de uso do nosso software e antes do método que completa a instalação, pois precisamos que o usuário primeiro leia os termos antes de aceitá-los.
Ao fazer isso recebemos um erro de compilação.
O Scanner
precisa saber de onde virão as informações, mas em nenhum momento informamos isso. No nosso caso precisamos que o usuário digite as entradas para que sejam lidas, ou seja, as informações virão do teclado.
Para informar que usaremos o teclado, vamos adicionar o parâmetro System.in
. Ficando com o seguinte resultado:
new Scanner(System.in);
Agora que temos a instância, vamos atribuí-la para um objeto chamado leitor
, pois é boa prática atribuirmos nomes que fazem referência ao objetivo do nosso objeto. Nosso Scanner
nada mais é do que um leitor de entradas.
mensagemDeTermos();
Scanner leitor = new Scanner(System.in);
completaInstalacao();
Agora que temos nosso leitor, qual o próximo passo? Ler as entradas, certo? Para isso usaremos o método [next()](https://docs.oracle.com/javase/7/docs/api/java/util/Scanner.html#next()). Este método lê a próxima entrada do usuário.
mensagemDeTermos();
Scanner leitor = new Scanner(System.in);
leitor.next();
completaInstalacao();
Pronto, agora nosso leitor
foi atribuído a um objeto e usamos o método next()
para pedir ao usuário que ele digite uma resposta. Vamos testar:
Veja que ele espera a nossa resposta antes de finalizar a instalação. Dando continuidade ao teste, vamos aceitar os termos:
Tudo funcionando, nosso programa aguardou a resposta do usuário e instalou o software, dada a resposta positiva.
Mas e se fosse "não"? Isto é, e se o usuário não concordasse com os nossos termos? O que aconteceria? Vamos testar e conferir:
O resultado esperado não era esse, não é mesmo? Para qualquer que seja a resposta do usuário, nosso software está finalizando a instalação...
Se pararmos para pensar, estamos apenas recebendo a resposta, não estamos processando-a. Em outras palavras, não estamos verificando se a resposta é ‘S’ ou ‘N’, logo, independente da resposta o método completaInstalacao()
é chamado.
Para resolvermos essa situação vamos primeiro atribuir a resposta do nosso usuário a uma variável para não perder a entrada lida pelo nosso leitor
:
mensagemDeTermos();
Scanner leitor = new Scanner(System.in);
String resposta = leitor.next();
completaInstalacao();
Hmm… já está melhor, mas isso ainda não resolveu nosso problema, ainda temos que processar a informação recebida, pois nosso programa não é mágico, precisamos mostrar pra ele qual direção seguir, ou seja, se ele continua a instalação ou não.
Para verificar a resposta, vamos criar uma condição que vai comparar se a resposta
é ‘S’ para que seja concluída a instalação. Como queremos comparar dois objetos, podemos usar o método equals
, que faz exatamente isso:
mensagemDeTermos();
Scanner leitor = new Scanner(System.in);
String resposta = leitor.next();
if("S".equals(resposta)){
completaInstalacao();
}
Ao olhar o código, pode não parecer natural chamar o método equals a partir de uma string hard coded, mas fazer isso é uma boa prática, pois temos certeza que a string ‘S’ não é nula, logo nos prevenimos contra NullPointerException
.
Pronto, vamos testar e ver se tudo está funcionando:
Com a resposta sendo positiva nosso programa funciona. Isso já acontecia antes de alterarmos o código, mas e se recebêssemos uma resposta negativa? Ou seja, o que aconteceria se o usuário não concordasse com os termos? Vamos ver:
Nada acontece! Nosso software termina a execução e o usuário fica sem entender absolutamente nada. O problema, no caso, foi que só ensinamos o nosso software a processar o ‘S’, ou seja, precisamos fazer o mesmo caso a resposta seja ‘N’.
Então vamos criar outra condição para caso a resposta seja negativa:
mensagemDeTermos();
Scanner leitor = new Scanner(System.in);
String resposta = leitor.next();
if("S".equals(resposta)){
completaInstalacao();
} else if("N".equals(resposta)){
cancelaInstalacao();
}
Pronto, vamos testar para validar o novo comportamento que criamos:
Isso ai! Mais uma funcionalidade feita. Mas e se o usuário responder algo diferente de ‘S’ ou ‘N’? Por exemplo responder usando "s",”n”,”sim”,”não” ou algo assim, o que acontece? Vamos ver:
Vish, ele nem sequer enviou uma resposta, imagine a cara do usuário ao ver isso? Bem confusa, né?
Precisamos enviar uma resposta caso a entrada seja diferente de ‘S’ ou ‘N’, então adicionaremos mais uma condição no nosso código. Podemos resolver essa situação da seguinte maneira:
mensagemDeTermos();
Scanner leitor = new Scanner(System.in);
String resposta = leitor.next();
if("S".equals(resposta)){
completaInstalacao();
} else if("N".equals(resposta)){
cancelaInstalacao();
}else{
System.out.println("Resposta inválida");
}
Pronto, agora teremos uma resposta para qualquer entrada que o usuário digitar. Vamos ver se isso realmente funciona:
Boa, funcionou! Uhul, mas nosso código ficou meio feio, né? O ideal seria jogar nossa lógica de validação (os if
's) dentro de um método e apenas chamá-lo, inclusive é uma boa prática fazer isso.
Para fazer essa refatoração, primeiro criaremos um método e jogaremos toda nossa lógica de validação de resposta dentro dele:
public static void validaResposta(String resposta) {
if ("S".equals(resposta)) {
completaInstalacao();
} else if ("N".equals(resposta)) {
cancelaInstalacao();
} else {
System.out.println("Resposta inválida");
}
}
Agora vamos apenas chamar esse método:
mensagemDeTermos();
Scanner leitor = new Scanner(System.in);
String resposta = leitor.nextLine();
validaResposta(resposta);
Caso queira saber mais sobre refatoração, código limpo e etc, deixarei o link de um artigo que trata exatamente sobre esse assunto, vale a pena dar uma olhada.
Agora que já conseguimos implementar a primeira feature que consistia em receber uma resposta do usuário para aceitação ou não dos termos de uso, vamos para a segunda parte, no caso, implementar uma função para receber o nome do nosso usuário.
Começaremos pedindo ao usuário para nos fornecer seu nome, mas antes de implementar tal funcionalidade, vamos pensar: Onde devemos escrever esse código?
Faz sentido pedir o nome do usuário que digitou ‘N’ ou o usuário que digitou uma resposta inválida? Não, né? Logo, nosso programa só deve perguntar o nome para os usuários que digitaram ‘S’, portanto, devemos codificar essa nova funcionalidade dentro do bloco if
onde a condição verifica se a resposta é igual a 'S'.
Agora sim podemos implementar esse código. Como estamos fora do escopo do nosso leitor
teremos que instancia-lo novamente, já que só o usaremos uma vez podemos instanciar a classe Scanner
e já chamar o método que queremos, no caso o next()
, e já salvar em uma variável. Por enquanto nada novo né?
Logo, dentro de validaResposta()
, teremos o seguinte código:
public static void validaResposta(String resposta) {
if ("S".equals(resposta)) {
completaInstalacao();
solicitaNome();
String nome = new Scanner(System.in).next();
}
//resto do código
}
Repare no nosso resultado:
Funcionou, conseguimos tanto aceitar os termos quanto informar nosso nome.
Dias depois, nosso chefe veio reclamar de um bug no software. Aparentemente só estava sendo salvo a primeira palavra do nome e não o nome completo.
Para testar vamos imprimir a variável nome
(não esqueça de remover essa linha de código após os testes) e ver o que realmente está sendo armazenado.
public static void validaResposta(String resposta) {
if ("S".equals(resposta)) {
completaInstalacao();
solicitaNome();
String nome = new Scanner(System.in).next();
System.out.println("Nome digitado: " + nome);
}
//resto do código
}
Agora só falta testar:
Eita, o bug é real! O nome digitado foi "Mario Alvial", mas nosso programa só considerou a primeira palavra, quando, na verdade, queríamos que ele lesse todas as palavras que o usuário digitasse.
Se olharmos a documentação da classe Scanner
, o método next()
lê apenas, e somente apenas, a próxima entrada, por isso, se o usuário tiver um nome composto, não vamos conseguir o resultado esperado.
Para resolver tal problema podemos usar outro cara da classe Scanner
.
O método nextLine
Precisamos de um cara que leia a linha inteira e esse cara é o método [nextLine()
](https://docs.oracle.com/javase/7/docs/api/java/util/Scanner.html#nextLine()). Sendo bem técnico, ele pula a linha que está e lê tudo o que foi pulado. Vamos lá!
public static void validaResposta(String resposta) {
if ("S".equals(resposta)) {
completaInstalacao();
solicitaNome();
String nome = new Scanner(System.in).nextLine();
System.out.println("Nome digitado: “ + nome);
}
//resto do código
}
Agora nos resta testar:
Opa calma aí, nosso programa passou batido pela pergunta, o que aconteceu? Ao invés de consertarmos o bug só o pioramos.
Após ler novamente a documentação (javadoc sempre salvando) consegui entender o problema. Vamos destrinchar esse erro.
O método next()
é o culpado da situação, como já foi falado ele lê a próxima entrada e só. Nessa linha, onde o usuário aceita os termos, temos duas entradas a ser lidas.
"Tá doido? Só tem uma, ou é ‘S’ ou é ‘N’”
Concordo, só tem uma entrada visível a ser lida, mas após a resposta do usuário temos uma quebra de linha certo? Essa quebra de linha é representada por \n
, que nada mais é do que uma sequência de escape para o compilador, isto é, quando o compilador vê o \n
ele pula para a próxima linha.
Então, esse \n
não foi lido pelo next()
e ficou para ser lido pelo nextLine()
que vem em seguida, logo o nextLine()
lê o resto da linha vazia e termina a execução do nosso programa, ele nem sequer chega a ler o nome.
Para exemplificar melhor vou mostrar com um desenho de qualidade:
Para resolver isso precisamos fazer com que o resto da linha onde o usuário aceita ou não os termos seja lida antes de chamarmos o nextLine()
para receber o nome.
Podemos abordar essa situação de duas formas:
Adicionando um nextLine()
logo após o next()
para que após a primeira resposta do usuário o nosso leitor
já seja posicionado na linha seguinte, deixando nosso código desse jeito:
mensagemDeTermos();
Scanner leitor = new Scanner(System.in);
String resposta = leitor.next();
leitor.nextLine();
validaResposta(resposta);
Vamos testar e ver se realmente essa abordagem funciona:
Boa, agora sim, funcionou perfeitamente.
Outra abordagem seria eliminar o mal pela raiz, substituindo o nosso método next()
pelo nextLine()
, ficando assim:
mensagemDeTermos();
Scanner leitor = new Scanner(System.in);
String resposta = leitor.nextLine();
validaResposta(resposta);
Novamente, vamos testar e ver se conseguimos o resultado esperado:
Tudo certo aqui também. Nos dois casos, temos o mesmo resultado, mas a primeira abordagem não é recomendada, pois estamos usando um método a mais sem necessidade. Com a segunda abordagem nosso método fica mais limpo.
Parabéns!!! Conseguimos criar todas as funcionalidades pedidas e "blindamos" nossa aplicação contra cenários problemáticos que podem surgir durante o uso do nosso software.
Eai, já teve problemas em receber entradas dos seus usuários? Compartilhe sua experiência aqui nos comentários.
Expandindo seu conhecimento sobre a classe Scanner
Além de usarmos a classe Scanner
para ler entradas do teclado, podemos usar também para ler arquivos externos. Para saber mais a respeito acesse esse post onde é mostrado como ler e processar arquivos externos usando a classe Scanner.
Aprenda outras funcionalidades bem úteis para o seu dia a dia na Formação Java, onde você vai aprender técnicas tanto básicas quanto avançadas para criar suas aplicações.