プログラマの宿命! 例外とエラー処理を理解する:【改訂版】Eclipseではじめるプログラミング(23)(3/3 ページ)
これからプログラミングを学習したい方、Javaは難しそうでとっつきづらいという方のためのJavaプログラミング超入門連載です。最新のEclipseとJava 6を使い大幅に情報量を増やした、連載「Eclipseではじめるプログラミング」の改訂版となります(この回と次回のみ、別連載「EclipseでJavaに強くなる」の改訂版です。今回は第3回「EclipseでJavaの例外を理解する」の改訂版です)
throwsでネストを浅く
ところで、finallyブロックの中でtry文を記述するとネストが深くなって気になる人もいるかもしれません。そのようなときは、「in.close();」のエラーをEclipseで修正するときに、[スロー宣言の追加]を選んでマウスで左クリックしてください。
すると、メソッドに「throws IOException」が追加されます。mainメソッドにつけてしまうと、例外をcatchできなくなるので、次のSample02クラスのようなプログラムにすると、その例外をcatchできるようになります。詳細については、次回の独自の例外定義について説明するときにしますが、このようにすることで、try文が深くネストすることを防げます。
package sample23; public class Sample02 { public static void main(String[] args) { Sample02 App app = new Sample02(); try { app.exec(); } catch (IOException e) { System.err.println("in.close()でIOExceptionが発生"); } } public void exec() throws IOException { java.io.BufferedReader in = new java.io.BufferedReader( new java.io.InputStreamReader(System.in)); System.out.print("Input name:"); String name; try { name = in.readLine(); System.out.println("Hello, "+name); } catch (java.io.IOException e) { System.err.println("in.readLine()でIOExceptionが発生"); } finally { in.close(); } } }
実行時に発生する例外
実行時に発生する例外はプログラミングエラーに対して使用されます。この例外はJavaではjava.lang.RuntimeExceptionクラスで表現されています。
このサブクラスには、パラメータが不正であることを意味する「java.lang.IllegalArgumentException」や配列の添え字が範囲外であることを意味する「java.lang.IndexOutOfBoundsException」や、メソッドを呼び出そうとした変数がnullを参照していたことを意味する「java.lang.NullPointerException」(通称「ヌルポ」)といったものがあります。
実行時に発生する例外のほとんどは、メソッドのAPI仕様に従わずにメソッド呼び出しを行うこと(事前条件違反、precondition violation)が原因です。例えば、次のプログラムはコンパイルエラーにはなりませんが、実行するとエラーが発生します。
package sample23; public class Sample03 { public static void main(String[] args) { int[] a = new int[4]; int i = 3; System.out.println("a[4]:"+a[i+1]); } }
配列「a」の要素へアクセスするためにはインデックスへは0から「a.length-1」の間の整数値しか使用できないという事前条件に対して違反をし、配列に存在しない要素へアクセスしようとしたためにエラーとなってしまうのです。
ここで次のように、java.lang.ArrayIndexOutOfBoundsExceptionもキャッチできますが、本来は「System.out.println("a[4]:"+a[i+1]);」を無条件に実行するコーディングをしているのはプログラマのミスですから、このようなjava.lang.RuntimeExceptionを継承した例外のキャッチをするコーディングは推奨されません。条件文で「i+1」が4未満である場合に「System.out.println("a[4]:"+a[i+1]);」を実行するようにコードを修正するべきです。
package sample23; // 例外のcatchの悪い例 public class Sample04 { public static void main(String[] args) { int[] a = new int[4]; int i = 3; try { System.out.println("a[4]:"+a[i+1]); } catch (java.lang.ArrayIndexOutOfBoundsException e) { System.out.println("エラー"); } } }
このように、「実行時に発生する例外」はプログラマのミスでエラーが発生しているということを教えてくれます。実際に出合ったときにはtry文を使って対処するのではなく、コードを修正して対応するということが基本になります。
Java VMが実行不能になるようなエラー
JavaVMが実行不能になるようなエラーが発生した場合にはjava.lang.Errorを継承したクラスが使われます。「java.lang.VirtualMachineError」などがあります。
こういったエラーが発生するのはシステムが異常な状態になったときだけなので、通常のアプリケーションではキャッチするべきではありません。このため、「実行時に発生する例外」と同様にjava.lang.Errorについてはキャッチしなくてもコンパイル時にチェックされません。
こういったことから分かるのは、通常のアプリケーション用プログラムをコーディングするうえでは、java.lang.Errorを使うことはないということです。ただし、java.lang.Errorによるエラーが発生した場合にはシステムの何かがおかしくなっているということだけは覚えておきましょう。
ここまでの説明から、「コンパイル時にチェックされない例外やエラー」を独自に実装するには、java.lang.Errorを継承する方法と、java.lang.RuntimeExceptionを継承する方法があるということに気が付くはずです。
しかし、JavaVMが実行不能になるような異常な状態を表現する例外やエラーを独自に実装することは基本的にはないので、もし「コンパイル時にチェックされない例外やエラー」を独自に実装したいといった場合は、java.lang.RuntimeExceptionを継承するのが普通です。
例外を“投げる”メソッド
ここまで「例外をキャッチする」という表現をしてきましたが、「キャッチする」に対応するのは「スローする(投げる)」になります。例外が発生するメソッドでは、どんな例外が発生するかが分かるように、どんな例外を投げるかについて宣言がされています。
例えば、「java.io.InputStreamReader」のcloseメソッドのAPIリファレンスを見ると、次のように記述されています。
public void close() throws IOException ストリームを閉じます。 定義: インタフェース Closeable 内の close 定義: クラス Reader 内の close 例外: IOException - 入出力エラーが発生した場合
このことから、java.io.InputStreamReaderクラスのcloseメソッド内では、java.io.IOExceptionという例外が発生する可能性があることが分かります。
また、APIリファレンスでのメソッドの宣言を見ても分かるように、プログラムでは、この例外が発生することを宣言するために、「throws(投げる)」というキーワードを使います。
次回は、独自の例外を定義する方法
今回は、ここまでです。次回は、独自の例外を定義する方法について紹介します。
今回作ったサンプルのソースコードは、こちらからダウンロードできます。
筆者紹介
小山博史(こやま ひろし)
情報家電、コンピュータと教育の研究に従事する傍ら、オープンソースソフトウェア、Java技術の普及のための活動を行っている。長野県の地域コミュニティである、SSS(G)やbugs(J)の活動へも参加している。
著書に「基礎Java」(インプレス)、共著に「Javaコレクションフレームワーク」(ソフトバンククリエイティブ)、そのほかに雑誌執筆多数。
- Javaの例外処理で知らないと損する7つのテクニック
- プログラマの宿命! 例外とエラー処理を理解する
- いまさら聞けない「Javadoc」と「アノテーション」入門
- 7ステップで理解するJavaでの列挙型/enum使用法
- 拡張for文の真の実力を知り、反復処理を使いこなせ
- キュー構造をJavaで実装してジェネリック型を理解する
- 強く型付けされているJavaの理解に必修の“型変換”
- あなたの知らない、4つのマニアックなJava文法
- “ネスト”した型で始める軽量Javaプログラミング!?
- Javaは「抽象クラス」で実装を上手に再利用できる
- 再利用性の高いクラス作成に重要な“アクセス制御”
- “コンストラクタ”と初期化、本当に理解できてる?
- 継承やオーバーライドで簡単にクラスを“拡張”しよう
- 「static」でクラス共有の変数・メソッドを使いこなせ!
- Javaの実案件に必須のパッケージとインポートを知る
- プログラムを「変更」しやすくする“インターフェイス”
- Javaの参照型を文字列操作で理解して文法を総復習
- クラスの振る舞いを表すJavaの“メソッド”とは?
- 複雑なデータを表現できるクラスやフィールドって?
- データ集合を扱うのに便利なJavaの配列と拡張for文
- プログラミングの真骨頂! Javaで“反復処理”を覚える
- プログラミングの醍醐味! Javaで“条件式”を理解する
- Javaで一から理解するプログラムの変数と演算子
- Eclipse 3.4で超簡単Javaプログラミング基礎入門
Copyright © ITmedia, Inc. All Rights Reserved.