Javaプログラミング言語は、エラーやその他の例外的な出来事を扱うために_例外_を使います。
例外とは、プログラムの実行中に発生し、命令の通常の流れを妨げる出来事です。 Javaでは例外は明示的に発生させ、その行為を_例外をスローする_と呼びます。 例外を処理する行為を_例外をキャッチする_と呼びます。
Javaでは、すべての例外はExceptionクラスのサブクラスであり、Exceptionクラス自体はThrowableのサブクラスです。
Javaでは、例外を2つの種類に分けています:
_チェック例外_は、アプリケーションが予期し、回復すべき例外的な状況です。
チェック例外の例として、メソッドが存在しないファイルを読み取ろうとしたときに発生するFileNotFoundExceptionがあります。
この種類の例外はコンパイル時にチェックされます。チェック例外をスローするメソッドは、メソッドシグネチャでそのことを指定する必要があり、チェック例外をスローする可能性があるメソッドを呼び出すコードは、それを処理する必要があります。そうしないと、コードはコンパイルされません。
すべてのチェック例外は、RuntimeExceptionを継承していないExceptionのサブクラスです。
_非チェック例外_は、アプリケーションが通常予期できず、回復もできない例外的な状況です。
非チェック例外の例として、nullではない値を期待しているメソッドがnullを受け取ったときに発生するNullPointerExceptionがあります。
この種類の例外はコンパイル時にチェックされません。非チェック例外をスローするメソッドは、メソッドシグネチャでそのことを指定する必要はなく、非チェック例外をスローする可能性があるメソッドを呼び出すコードも、それを処理する必要はありません。
すべての非チェック例外はRuntimeExceptionを継承しており、RuntimeException自体はExceptionを拡張したものです。
Javaのメソッドは、throw文を使って例外をスローできます。
メソッドからチェック例外をスローする場合は、以下の例のように、throwsキーワードを使ってメソッドシグネチャでそのことを指定する必要があります。
これにより、呼び出し側のコードは、例外がスローされる可能性を予期し、適切に処理しなければならなくなります。
public class InsufficientBalanceException extends Exception {
}
public class BankAccount {
public void withdraw(double amount) throws InsufficientBalanceException {
if (balance < amount) {
throw new InsufficientBalanceException();
}
// rest of the method implementation
}
}
メソッドから非チェック例外をスローする場合、メソッドシグネチャでそのことを指定する必要はありません。ただし、指定することは可能です。
public class BankAccount {
public void withdraw(double amount) {
if (amount < 0) {
throw new IllegalArgumentException("Cannot withdraw a negative amount");
}
// rest of the method implementation
}
}
Javaで例外を処理するには、try、catch、finallyキーワードを使います。
tryブロックで囲む必要があります。tryブロックの後には、tryブロック内でスローされた例外をキャッチする1つ以上のcatchブロックが続きます。catchブロックの後には、例外がスローされたかどうかに関係なく、tryブロックの後に必ず実行されるfinallyブロックを置くこともできます。次の例は、これらのキーワードがどのように機能するかを示しています。
public class ATM {
public void withdraw(BankAccount bankAccount, double amount) {
try {
System.out.println("Withdrawing " + amount);
bankAccount.withdraw(amount);
System.out.println("Withdrawal succeeded");
} catch (InsufficientBalanceException) {
System.out.println("Withdrawal failed: insufficient balance");
} catch (RuntimeException e) {
System.out.println("Withdrawal failed: " + e.getMessage());
} finally {
System.out.println("Current balance: " + bankAccount.getBalance());
}
}
}
この例で、例外がスローされない場合は、次のように出力されます:
Withdrawing 10.0
Withdrawal succeeded
Current balance: 5.0
ただし、bankAccount.withdraw(amount)文がInsufficientBalanceExceptionをスローした場合は、次のように出力されます:
Withdrawing 10.0
Withdrawal failed: insufficient balance
Current balance: 5.0
または、bankAccount.withdraw(amount)によって非チェック例外がスローされた場合は、次のように出力されます:
Withdrawing -10.0
Withdrawal failed: Cannot withdraw a negative amount
Current balance: 5.0
Javaには、_エラー_と呼ばれる別のカテゴリもあります。これは、アプリケーションの外部にある深刻な問題です。
エラーの例として、アプリケーションがシステムで利用可能なメモリよりも多くのメモリを使おうとしたときに発生するOutOfMemoryErrorがあります。
非チェック例外と同様に、エラーはコンパイル時にチェックされません。 違いは、エラーがシステムレベルの問題を表しており、通常はアプリケーションではなく、Java仮想マシンや環境によってスローされることです。 アプリケーションは通常、それらをキャッチしたり処理したりしようとすべきではありません。
Javaのすべてのエラーは、Errorクラスを継承しています。
この演習では、シンプルな整数電卓のエラー処理を実装します。話を簡単にするために、足し算・掛け算・割り算を計算するメソッドはあらかじめ用意されています。
目標は、引数として16、51、+を受け取ったときに、16 + 51 = 67というパターンのStringを返す、ちゃんと動く電卓を作ることです。
CalculatorConundrum calculator = new CalculatorConundrum();
calculator.calculate(16, 51, "+");
// => returns "16 + 51 = 67"
calculator.calculate(32, 6, "*");
// => returns "32 * 6 = 192"
calculator.calculate(512, 4, "/");
// => returns "512 / 4 = 128"
calculateメソッドを実装するこのタスクで実装する中心となるメソッドは、CalculatorConundrum.calculate()メソッドです。
このメソッドは3つの引数を取ります。
最初の2つの引数operand1とoperand2は、演算の対象となる整数です。
3つ目の引数operationはString型で、この演習では次の演算を実装する必要があります:
+というStringを使った足し算*というStringを使った掛け算/というStringを使った割り算CalculatorConundrum.calculate()メソッドを更新して、不正な演算を処理します:
operation引数がnullのときは、メッセージOperation cannot be nullとともにIllegalArgumentExceptionをスローします。operation引数が""のときは、メッセージOperation cannot be emptyとともにIllegalArgumentExceptionをスローします。operation引数が+、*、/以外の演算のときは、メッセージOperation '{operation}' does not existとともにIllegalOperationExceptionをスローします。calculator.calculate(10, 1, null);
// => throws IllegalArgumentException with message "Operation cannot be null"
calculator.calculate(10, 1, "");
// => throws IllegalArgumentException with message "Operation cannot be empty"
calculator.calculate(10, 1, "-");
// => throws IllegalOperationException with message "Operation '-' does not exist"
Javaでは、ゼロで割ろうとするとArithmeticExceptionがスローされます。
CalculatorConundrum.calculate()メソッドを更新して、この例外をキャッチし、メッセージDivision by zero is not allowedと、キャッチしたArithmeticExceptionを原因として持つIllegalOperationExceptionをスローします。
calculator.calculate(512, 0, "/");
// => throws IllegalOperationException with message "Division by zero is not allowed"