未来エンジニア養成所Blog

月単価180万以上のプログラミング講師がプログラミングを皆に楽しんでもらうための情報をお届けします。

【Java】オブジェクト指向の基本問題2-16

title


問題2-16

ExceptionBasicPracticeクラスのmainメソッドは完成しています。

4200という値をコマンドライン引数で指定した値で割り、その計算結果を出力するプログラムです。

しかし、次の3つの点で不完全です。


【不完全な理由1】

コマンドライン引数なしで実行するとArrayIndexOutOfBoundsException例外がスローされてしまう


【不完全な理由2】

コマンドライン引数に整数に変換できない文字列を指定するとNumberFormatException例外がスローされてしまう


【不完全な理由3】

コマンドライン引数に0を指定すると0割によるArithmeticException例外がスローされてしまう


これらの点を、次の条件を満たすように改善してください。

  1. これらの例外を例外処理(try-catch文)にて捕捉し、次のようなメッセージを出力してください。

    不完全な理由1の場合・・・「コマンドライン引数を指定してください」
    不完全な理由2の場合・・・「コマンドライン引数には整数を指定してください」
    不完全な理由3の場合・・・「コマンドライン引数には0以外の整数を指定してください」

  2. プログラムの最後には正常終了・異常終了に関係なく「プログラムを終了します」というメッセージを表示してください。


【実行結果】

> java ExceptionBasicPractice
コマンドライン引数を指定してください
プログラムを終了します
> java ExceptionBasicPractice A
コマンドライン引数には整数を指定してください
プログラムを終了します
> java ExceptionBasicPractice 0
コマンドライン引数には0以外の整数を指定してください
プログラムを終了します
> java ExceptionBasicPractice 12
割り算の結果は350です
プログラムを終了します


【ExceptionBasicPractice.java】

public class ExceptionBasicPractice {
    public static void main(String[] args) {

        // 分子変数
        int numerator = 4200;

        // 分母変数
        int denominator;

        // コマンドライン引数を整数値に変換する
        denominator = Integer.parseInt(args[0]);

        // 割り算結果変数に代入
        int result = numerator / denominator;

        System.out.println("割り算の結果は" + result + "です");

    }
}


解答例

【ExceptionBasicPractice.java】

public class ExceptionBasicPractice {
    public static void main(String[] args) {

        // 分子変数
        int numerator = 4200;

        // 分母変数
        int denominator;

        try {

            // コマンドライン引数を整数値に変換する
            denominator = Integer.parseInt(args[0]);

            // 割り算結果変数に代入
            int result = numerator / denominator;

            System.out.println("割り算の結果は" + result + "です");

        } catch(ArrayIndexOutOfBoundsException e) {
            System.out.println("コマンドライン引数を指定してください");
        } catch(NumberFormatException e) {
            System.out.println("コマンドライン引数には整数を指定してください");
        } catch(ArithmeticException e) {
            System.out.println("コマンドライン引数には0以外の整数を指定してください");
        } finally {
            System.out.println("プログラムを終了します");
        }
    }
}


解説

コンパイルは通ったけど、実行してみると動作を続けられないケースがあります。

Javaではそのような問題を「例外をスローする」と表現します。



「例外」には色々な種類があるのですが、Javaの面白いところはその色々な種類の例外がクラスで定義されており、問題が起こると例外クラスがインスタンス化されてスローされる点です。



例外処理には大きく分けて2パターンあります。

1つはtry-catch文を記述して例外を捕捉する方法、もう1つはメソッド定義の際にthrows句を記述する方法です。
(今回の問題は前者)



try-catch文は次のように記述します。
(注:今回はJava7から導入されたtry-with-resources文は扱いません)

try {
    // 例外がスローされる可能性のあるプログラムを記述
} catch(捕捉したい例外クラス名1 例外参照変数) {
    // 例外捕捉に関する処理
} catch(捕捉したい例外クラス名2 例外参照変数) {
    // 例外捕捉に関する処理
} finally {
    // 例外スローの有無に関係なく実行したいプログラムを記述
}



まず、tryブロックを記述し、例外がスローされる可能性のあるプログラムを記述します。


そして次にcatchブロックを記述します。

これは、複数記述することが可能です。

catchの右後ろには丸括弧をつけ、その中に捕捉したい例外クラス名と例外参照変数(一般的にはe)を記述します。


tryブロック内でトラブルが起きると、その種類に応じた例外クラスがインスタンス化され、スローされます。

プログラムは基本的に上から1行ずつ順に実行されますが、例外がスローされると特殊なモードとなり、それ以降のtryブロックの処理は実行されません。

スローされた例外クラスと同じクラスのcatchブロック(あるいはスーパークラスでも良い)があれば、スローされた例外インスタンスを捕まえることができます。

捕まえた例外オブジェクトそのものが「e」です。

例外参照変数「e」には例外関する有益な情報(たとえば何行目で例外がスローされたとか)が格納されていて、メソッドを使って確認することも可能です。

例外をキャッチすれば、特殊なモードは終了して普通のプログラム実行に戻ります。



try-catch文にはfinallyブロックを最後に一度だけ記述できます。

これは、例外がスローされようとされなかろうと、必ず実行される領域です。

ファイルやデータベースなどの外部リソースのクローズによく利用されます。



では、問題を見てみましょう。

今回、3つの例外をスローするのは次の2行です。

// コマンドライン引数を整数値に変換する
denominator = Integer.parseInt(args[0]);

// 割り算結果変数に代入
int result = numerator / denominator;


この2行をtryブロックに記述するわけです。


ちなみに、分子・分母変数の宣言は絶対に例外をスローしないのでtryブロックの上に記述しています。

tryブロック内で変数を宣言してしまうと、その変数はtryブロック内でのみ有効なので注意してください。

学習を始めたばかりだと、このミスを結構しがちです。



そして、今回は3パターンの例外をスローする可能性があるので、catchブロックを3つ記述しています。

もし3種類の例外をキャッチしても、同じメッセージを出力したいのなら、Java7で導入されたマルチキャッチという機能も使えます。

次のように記述します。

catch(ArrayIndexOutOfBoundsException
                | NumberFormatException
                | ArithmeticException e) {
    System.out.println("コマンドライン引数が不正です");
}


見やすさを考慮してcatchブロック内を改行していますが、1行で記述して問題ありません。

記号「|」を「または」という意味で使っていると考えてください。

ただ、並べる例外クラス間に継承関係があるとコンパイルエラーになりますので注意してください。



「プログラムを終了します」というメッセージは必ず出力したいので、finallyブロックに記述しています。

もしもtryブロックで想定外の例外がスローされ、どのcatchブロックでも捕捉できなかった場合はプログラムが強制終了します。


しかし、そんな状況でもfinallyブロック内の処理は実行されるのがポイントです。



あと、問題文にもありましたが、今回スローされる3つの例外は「非検査例外」といって、例外処理が任意(してもしなくても良い)であり、普通は行いません。

「非検査例外」は「プログラマのミス」に起因するものがほとんどなので、事前に回避できるから普通は例外処理をしないのです。


もう1つ「検査例外」というのがあって、こちらは例外処理が必須となります。



最後に余談になりますが、Javaで一番良くスローされる例外はNullPointerExceptionです。

これは、nullが格納されている参照変数に対してドットを使ってインスタンスメソッドなどを呼び出そうとするとスローされます。

引数で参照変数を受け取った場合などは、できるだけnullの判定を行うと良いでしょう。


参考図書



LINE公式アカウント

仕事が辛くてたまらない人生が、仕事が楽しくてたまらない人生に変わります。
【登録いただいた人全員に、無料キャリア相談プレゼント中!】


LineOfficial

友だち追加