この初期モジュールの 2 番目の設計課題、オブジェクト指向プログラミング モジュールに進む前の最後のタスクに到達しました。
正直に告白しますが、このチャレンジは少しばかげていると思いました。コードチャレンジでも問題なくできたかもしれませんが、それは問題ありません。
システムは、2 つの整数を表す 2 つのパラメーターを端末経由で受信する必要があります。これら 2 つの数値を使用して、インタラクション (for) の数を取得し、増分された数値をコンソール (System.out.print) に出力する必要があります。たとえば、 :
- 数値 12 と 30 を渡すと、数値を出力するための 18 回の対話 (for) が行われます。例: 「数値 1 を出力」、「数値 2 を出力」など。
- 最初のパラメータが 2 番目のパラメータより大きい場合は、「2 番目のパラメータは最初のパラメータより大きい必要があります」という 2 番目のメッセージを含む ParametrosInvalidosException というカスタム例外をスローする必要があります
推奨される手順:
- プロジェクトを作成する DesafioControleFluxo
- プロジェクト内で、プログラムのすべてのコーディングを実行する Contador.java クラスを作成します。
- プロジェクト内で、システム内のビジネス例外を表すクラス ParametrosInvalidosException を作成します。
ご覧のとおり、これは世界で最も複雑なものではありませんが、いくつかの興味深い可能性が開かれています。
最後から開始して、カスタム例外を作成できます (つまり、プロジェクトを作成した後です):
//InvalidParametersException.java public class InvalidParametersException extends Exception { public InvalidParametersException() { super(); } }
これはカスタム例外を定義する最も簡単な方法です。チェック例外の場合は Exception から拡張し、チェックされていない場合 (未チェック例外) は RuntimeException から拡張するクラスを作成します (そして、命名パターンに従ってサフィックス Exception を追加します)。次に、例外に持たせたい動作に従ってクラス コンストラクターを定義します。この場合、彼女は大したことはしないので、彼女を改善しましょう。
ああ、ルーカス、でもクラス コンストラクターとは何ですか??あなたはそれについて何も言っていませんでした!!1!
コンストラクターとは何かについては、まだ言及されていません。次のモジュールであるオブジェクト指向では、この知識のギャップを修正する必要があります。感情を抑えなさい、動物よ。
今回の例外の場合、ビジネス ルール 最初のパラメータが大きい場合は、それがチェック例外である必要がある (つまり、例外をスローするメソッドが使用されるときに処理される必要がある) と判断しました。 2 番目のパラメータでは、ParametrosInvalidosException という 2 番目のメッセージを含むカスタム例外をスローする必要があります。「2 番目のパラメータは最初のパラメータより大きくなければなりません」 が尊重され、アプリケーションを中断しないようにエラー処理が行われます
。処理時の柔軟性を高めるために、例外で追加のコンストラクターを定義し、それぞれが異なる処理を行うことができます。
// 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 を宣言し、「1 つ以上の引数が無効です。」を割り当てます。それが定数であることはどのようにしてわかるのでしょうか?最後の予約語と、定数を指定するための一般的なパターンである SCREAMING_SNAKE_CASE が使用されているためです。この値は、最初のコンストラクターで定義されているように、引数なしで例外がスローされた場合に表示されます。これは、DEFAULT_MESSAGE を渡してスーパークラス コンストラクター (この場合は例外) を呼び出します。したがって、無効なパラメータがある場合、カスタム メッセージが定義されていない場合、表示されるエラーは「1 つ以上の引数が無効です。」となります。
2 番目のコンストラクターを使用すると、カスタム メッセージで例外をスローできます。つまり、標準のエラー メッセージをカスタマイズしたメッセージに置き換えることができます。たとえば、スレッド「メイン」の java.lang.NullPointerException で例外のようなものを表示する代わりに、「null だったものにアクセスするように指示されました」と表示できます。大丈夫ですか?
最後の 2 つは、異なる議論、つまり原因を持っています。これは Throwable (スロー可能) であり、例外を再スローするために使用されます。たとえば、あなたのアプリケーションで、ユーザーが入力した 2 つのデータを除算する必要がある点があり、その分母 (分数のクラスで覚えていますか?) が 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 中国語 Web サイトの他の関連記事を参照してください。