我們已經完成了這個初始模組的第二個設計挑戰,這是進入物件導向程式模組之前的最後一個任務。
我承認我發現這個挑戰有點愚蠢,它可能是一個沒有任何問題的程式碼挑戰,但沒關係。
系統必須透過終端接收兩個參數,這兩個參數代表兩個整數,您必須透過這兩個數字取得互動次數(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中文網其他相關文章!