我们已经完成了这个初始模块的第二个设计挑战,这是进入面向对象编程模块之前的最后一个任务。
我承认我发现这个挑战有点愚蠢,它可能是一个没有任何问题的代码挑战,但没关系。
系统必须通过终端接收两个参数,这两个参数代表两个整数,您必须通过这两个数字获取交互次数 (for) 并在控制台中打印递增的数字 (System.out.print),例如:
- 如果您传递数字 12 和 30,那么我们将与 18 次出现的交互进行打印,例如:“打印数字 1”、“打印数字 2”等等。
- 如果第一个参数大于第二个参数,则必须抛出名为 ParametrosInvalidosException 的自定义异常,并带有第二条消息:“第二个参数必须大于第一个”
建议步骤:
- 创建项目 DesafioControleFluxo
- 在项目中,创建 Contador.java 类来执行我们程序的所有编码。
- 在项目中,创建 ParametrosInvalidosException 类,它将代表系统中的业务异常。
如您所见,这并不是世界上最复杂的事情,但它开辟了一些有趣的可能性。
我们可以从最后开始并创建自定义异常(我的意思是,在创建项目之后,对吧):
//InvalidParametersException.java public class InvalidParametersException extends Exception { public InvalidParametersException() { super(); } }
这是定义自定义异常的最简单方法。我们创建一个类(并且按照命名模式,添加后缀 Exception),当它是受检查异常时,该类从 Exception 扩展;如果它不是受检查异常(非受检查异常),则从 RuntimeException 扩展。然后我们根据我们希望异常具有的 行为 定义类构造函数。在这种情况下,她不会做太多事情,所以我们要改进她。
哦卢卡斯,但是什么是类构造函数?你从来没有说过这件事!!1!
确实还没有提到什么是构造函数。下一个模块“面向对象”应该会纠正这个知识差距。控制住你的情绪,动物。
就我们的异常而言,我决定它必须是一个受检查的异常(即,必须在使用抛出它的方法时对其进行处理),以确保业务规则如果第一个参数大于对于第二个参数,您必须抛出名为 ParametrosInvalidosException 的自定义异常,并带有第二条消息:“第二个参数必须大于第一个” 受到尊重,并且存在错误处理,以免破坏应用程序
。为了在处理时更加灵活,我们可以在异常中定义额外的构造函数,每个构造函数执行不同的操作:
// InvalidParametersException.java public class InvalidParametersException extends Exception { private static final String DEFAULT_MESSAGE = "Um ou mais argumentos são inválidos."; public InvalidParametersException() { super(DEFAULT_MESSAGE); } public InvalidParametersException(String message) { super(message); } public InvalidParametersException(String message, Throwable cause) { super(message, cause); } public InvalidParametersException(Throwable cause) { super(DEFAULT_MESSAGE, cause); }}
让我们冷静地看待这件事。
我们通过扩展 Exception 类来声明 InvalidParametersException 类。正如我上面提到的,这意味着任何调用抛出此异常的方法的类都需要对其进行处理。
接下来,我们声明常量 DEFAULT_MESSAGE,并为其分配“一个或多个参数无效。”。我们怎么知道它是一个常数?由于最后的保留字和 SCREAMING_SNAKE_CASE 的使用,这是指定常量的常见模式。如果抛出不带参数的异常(如第一个构造函数中所定义),则将显示此值:它调用超类构造函数(在本例中为 Exception)并传递 DEFAULT_MESSAGE。因此,如果我们有一个无效参数,如果未定义自定义消息,则显示的错误将是“一个或多个参数无效。”。
第二个构造函数允许使用自定义消息抛出异常。换句话说,我们可以用自定义消息替换标准错误消息。例如,我们可以显示 You指示我访问 null 的内容,而不是显示线程“main”java.lang.NullPointerException 之类的内容。朋友你还好吗?.
最后两个有不同的论点,原因。它是一个 Throwable(可抛出的),用于重新抛出异常。例如,假设在您的某些应用程序中,有一个点需要对用户输入的两个数据进行除法,分母(还记得分数类中的分母吗?)最终为 0。
Lembra quando esse formato de meme era popular? Bons tempos aqueles...
Isso vai lançar uma ArithmeticException, mas o que você quer na verdade é usar sua exceção customizada novinha, que deu tanto duro para criar. Aí você pode fazer algo assim:
try { Scanner sc = new Scanner(System.in); int num1 = sc.nextInt(); //10 int num2 = sc.nextInt(); //0 int result = num1 / num2; //OH SHI- } catch (ArithmeticException ex) { throw new InvalidParametersException("Não pode dividir por 0, bicho", ex); }
Ou seja, o bloco try-catch captura a ArithmeticException, pega o motivo de ter sido lançada e passa para a exceção customizada, que vai disponibilizar uma mensagem mais amigável para o usuário E mostrar a causa dessa pataquada toda. A pilha de execução (stack trace) para essa situação poderia ser mais ou menos assim:
Exception in thread "main" InvalidParametersException: Não pode dividir por 0, bicho at Main.main(Main.java:10) Caused by: java.lang.ArithmeticException: / by zero at Main.main(Main.java:9)
Temos nossa mensagem amigável na primeira linha da pilha mas também temos o ponto exato onde as coisas deram errado, para termos uma depuração mais rápida.
Nossa, quanta coisa. E ainda nem entramos no método propriamente dito.
Aqui eu tenho que fazer uma mea-culpa: Eu reclamei a um tempo atrás de como na documentação da classe Scanner mostra que existe um método específico para capturar cada tipo primitivo e fui induzido a achar que esse era o único jeito de fazer isso. Mas conversando no Discord da Orange Juice, meu chegado Claudio me disse que tem um jeito muito mais simples:
Ou seja, eu fiquei reclamando reclamando reclamando mas aparentemente dá pra fazer exatamente como no C#, como eu falei que era melhor. Claro, não dá pra aceitar tudo cegamente, então eu fui fazer um teste:
public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.print("Insira o primeiro número: "); int num1 = Integer.parseInt(scanner.nextLine()); System.out.print("Insira o segundo número: "); int num2 = Integer.parseInt(scanner.nextLine()); System.out.println("O primeiro número foi: " + num1 + " e o segundo foi: " + num2); }
Não é que deu certo, gente?
Eu to me sentindo bem mal agora, não fiz a coisa mais simples antes de começar a reclamar... Que burro, dá zero pra mim.
Muito bem. Me desculpe, menino Java.
Isto posto, podemos começar a desenvolver nosso método de contagem. A classe Counter vai ter dois métodos: o counter, que vai conter a lógica da contagem e o main, que vai chamar o contador e tratar as exceções que surgirem. Vamos começar com o counter:
public class Counter { public static void main(String[] args) {} //por enquanto esse método segue vazio public static void count(int num1, int num2) throws InvalidParametersException { if (num1 < 0 || num2 < 0) throw new InvalidParametersException(); if (num1 > num2) throw new InvalidParametersException("O primeiro argumento deve ser maior que o segundo"); int counter = num2 - num1; System.out.printf("Vou imprimir todos os números de 1 até %d.%n", counter); for (int i = 1; i <= counter; i++) { System.out.printf("Imprimindo o número %d%n", i); } } }
Declaramos o método counter, que é público e não tem retorno (podemos ter certeza por conta do void ali). Além disso, esse método é estático, então não precisamos instanciar a classe para podermos usá-lo. Ele recebe dois argumentos do tipo int, num1 e num2 -- criatividade é a alma do negócio. Por fim, podemos ver que esse método lança a nossa maravilhosa exceção InvalidParametersException, o que vai obrigar o método main a realizar algum tipo de tratamento em cima dela.
Para garantir que as regras de negócio vão ser respeitadas, o método faz duas verificações:
Depois disso, é realizada a subtração que vai montar o loop. Feita essa conta, é impressa no console uma mensagem informando que a aplicação irá mostrar todos os números de 1 até o resultado.
E aqui vem mais uma particularidade do Java: a falta de interpolação de strings. Eu me acostumei a escrever
const variable = variable; let sentence = `I'm a sentence that uses a ${variable}!`;
ou
string name = "John Doe"; string sentence = $"My name is {name}.";
que é até esquisito imaginar uma linguagem moderna que não utiliza isso.
Para ficar melhor (e evitar usar a concatenação com aquela sintaxe horrorosa), descobri que poderia formatar a mensagem com o método String.format(), da mesma forma que a formatação de string do C. E assim como no C, existe a possibilidade de já formatar a string usando o método print. Em vez de usar o .println() para imprimir uma string e já pular uma linha (o ln significa que caractere para newline será adicionado no final), o método .printf() formata a mensagem de acordo com o placeholder informado.
Existem muitos placeholders que podem ser utilizados, mas os mais comuns são %s para string, %d para int, %f para números de ponto flutuante (como double e float) e %f para data/hora. Porém esse método não cria uma quebra de linha então caso seja necessário, é preciso adicionar o caractere %n para uma nova linha.
Aí, por último, fazemos nosso laço for, sem grandes mistérios. Inicializamos o contador do loop em 1 e o instruímos a repetir a tarefa até o chegar no resultado final, incrementando o valor do contador de um em um. A cada volta imprimimos o valor do contador atual, cumprindo, desse modo, o requisito do desafio.
Beleza, criamos o método que vai realizar a ação que precisamos. Agora, o que falta é chamar a função e executá-la em algum lugar, né? Vamos fazer isso no método main da nossa classe Counter:
public class Counter { public static void main(String[] args) { try { Scanner scanner = new Scanner(System.in); System.out.println("Insira dois números e a aplicação imprimirá a diferença entre eles, linha a linha."); System.out.print("Insira o primeiro número: "); int num1 = Integer.parseInt(scanner.nextLine()); System.out.print("Insira o segundo número: "); int num2 = Integer.parseInt(scanner.nextLine()); count(num1, num2); } catch (InvalidParametersException e) { System.out.println(e.getMessage()); } } public static void count(int num1, int num2) throws InvalidParametersException { /* lógica descrita acima */ }
Aqui não tem nada muito excepcional: Instanciamos um novo scanner e, seguindo a dica valiosa do Cláudio, pedimos ao usuário que insira dois números. Esses números são capturados como string e imediatamente convertidos em int. Com esses dados, chamamos a função count passando os dois números como parâmetros e, caso alguma exceção seja lançada, uma mensagem de erro será exibida no console.
Show, mas será que funciona?
Aparentemente sim. ✌️
Mas o que acontece se, por exemplo, algum usuário espírito de porco curioso inserisse um número muito grande em um dos campos? Será que a aplicação daria conta do recado?
Bom, não né. O int, destino da conversão da string nos inputs, aloca 32 bits de memória para cada variável. Isso quer dizer, na prática, que o valor máximo que ele pode armazenar é 2147483647 (e o menor, -2147483648). Quando o número alvo extrapola esse limite, essa exceção NumberFormatException é lançada.
Para solucionar isso, poderíamos mudar o tipo de destino de int para long, mas o problema da limitação ainda se mantém. Claro que é bem mais difícil que alguém indique um número grande como o long (cujo valor máximo é 9223372036854775807), mas é sempre bom não dar chance pro azar. Por isso, a melhor coisa é adicionar algum tipo de limitação e informar ao usuário que ele tá maluco precisa informar um número dentro do intervalo esperado.
Além disso, a aplicação encerrar quando encontra um erro é meio chato. O ideal seria que ela voltasse a iniciar caso encontrasse um erro, até que os inputs fossem inseridos de maneira correta.
Podemos resolver adicionando um novo catch no nosso try e envolvendo a aplicação toda em um laço while:
public class Counter { public static void main(String[] args) { while (true) { try { Scanner scanner = new Scanner(System.in); System.out.println("Insira dois números e a aplicação imprimirá a diferença entre eles, linha a linha."); System.out.print("Insira o primeiro número: "); int num1 = Integer.parseInt(scanner.nextLine()); System.out.print("Insira o segundo número: "); int num2 = Integer.parseInt(scanner.nextLine()); count(num1, num2); break; } catch (InvalidParametersException e) { System.out.println(e.getMessage()); } catch (NumberFormatException e) { System.out.println("Um dos números informados estão acima da capacidade de processamento desta aplicação. Por favor, tente novamente com um número menor."); } } } public static void count(int num1, int num2) throws InvalidParametersException { /* lógica descrita acima */ }
A primeira coisa que fizemos foi, então, envolver todo o try-catch em um laço while, e definimos a condição como true. Ou seja, enquanto true for... verdadeiro, o laço se repetirá.
Fizemos um famigerado loop infinito, a perdição de todo processador.
Em vez de colocarmos a condição para parada do while na sua definição, apenas colocamos um break ao final da chamada ao método count(); desse modo, se não houver alguma exceção lançada, o loop será interrompido.
Ao final da chamada, definimos mais um bloco catch, capturando a exceção NumberFormatException e passando uma mensagem de erro mais fácil de ser compreendida. Bora testar pra ver se está tudo certo?
Bom demais.
Agora, a única coisa que falta é chamar o método Counter.main() na classe Main. Pode ser redundante, mas eu prefiro deixar bem separadinhas e explicadas as coisas.
public class Main { public static void main(String[] args) { Counter.main(args); } }
É isso aí, pessoal. Obrigado pela paciência e por ter lido esse post gigantesco.
O repositório desse projetinho pode ser encontrado aqui. Até a próxima!
以上是项目挑战 - 创建计数器应用程序的详细内容。更多信息请关注PHP中文网其他相关文章!