ホームページ  >  記事  >  Java  >  プロジェクトチャレンジ - カウンターアプリケーションの作成

プロジェクトチャレンジ - カウンターアプリケーションの作成

王林
王林オリジナル
2024-09-03 10:46:32952ブラウズ

この初期モジュールの 2 番目の設計課題、オブジェクト指向プログラミング モジュールに進む前の最後のタスクに到達しました。
正直に告白しますが、このチャレンジは少しばかげていると思いました。コードチャレンジでも問題なくできたかもしれませんが、それは問題ありません。

提案された課題:

システムは、2 つの整数を表す 2 つのパラメーターを端末経由で受信する必要があります。これら 2 つの数値を使用して、インタラクション (for) の数を取得し、増分された数値をコンソール (System.out.print) に出力する必要があります。たとえば、 :

  • 数値 12 と 30 を渡すと、数値を出力するための 18 回の対話 (for) が行われます。例: 「数値 1 を出力」、「数値 2 を出力」など。
  • 最初のパラメータが 2 番目のパラメータより大きい場合は、「2 番目のパラメータは最初のパラメータより大きい必要があります」という 2 番目のメッセージを含む ParametrosInvalidosException というカスタム例外をスローする必要があります

推奨される手順:

  1. プロジェクトを作成する DesafioControleFluxo
  2. プロジェクト内で、プログラムのすべてのコーディングを実行する Contador.java クラスを作成します。
  3. プロジェクト内で、システム内のビジネス例外を表すクラス 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 になったとします。

Desafio de Projeto - Criando uma aplicação contadora

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)

Desafio de Projeto - Criando uma aplicação contadora
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:
Desafio de Projeto - Criando uma aplicação contadora
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);  
}

Desafio de Projeto - Criando uma aplicação contadora

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.
Desafio de Projeto - Criando uma aplicação contadora

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:

  1. Checa se qualquer um dos números é negativo e, em caso positivo lança uma exceção informando ao usuário que um ou mais argumentos são inválidos;
    • Essa checagem, apesar de não ter sido pedida, é para garantir que não haveria resultados esquisitos na subtração feita. Eu não queria, por exemplo, ter que lidar com resultados negativos.
  2. Checa se o primeiro número é maior do que o segundo. Se sim, lança uma exceção orientando o usuário que o segundo número (o numerador da divisão) deve ser maior.

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?

Desafio de Projeto - Criando uma aplicação contadora
Desafio de Projeto - Criando uma aplicação contadora
Desafio de Projeto - Criando uma aplicação contadora

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?
Desafio de Projeto - Criando uma aplicação contadora
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?
Desafio de Projeto - Criando uma aplicação contadora
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);  
    }
}

Desafio de Projeto - Criando uma aplicação contadora

É 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 サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。