【3/18〜】Amazon、VMwareが語る『クラウドの未来』 スラッシュドット    はてなブックマーク  Yahoo!ブックマークに登録  印刷



第12回 エラーに対処するプログラムを書く


平井玄
2004/1/8


  予期しないエラーに対処するには?

 プログラムのエラーには文法エラーのようにコンパイル時に発見できるものと、0で割り算してしまうような、コンパイル時には発見できないものがあります。あるいは、ハードディスクが故障してデータをファイルに書き込めないなど、実行時にはプログラマが想定できないようなエラーも発生します。
 
 このような想定外の場合に備えて、エラー発生時の処理を決めておくのが「例外」という機能です。まずは簡単な例を示しますので、どのような動作になるかを確認してみましょう。

10÷Nを計算する その1
import java.io.*;

public class DivideCalc {
  public static void main( String args[] ) {
    BufferedReader r = new BufferedReader( new InputStreamReader(System.in) );

    System.out.println( "10÷Nの計算をします" );
    System.out.println( "Nの値を入力してください:" );

    String s = r.readLine();
    int n = Integer.parseInt(s);

    System.out.println( "答えは" + (10 / n) + "です。" );
  }
}

 この実行結果は以下のとおりです。

C:\>javac DivideCalc.java
DivideCalc.java:10: 例外 java.io.IOException は報告されません。スローするにはキャッチまたは、スロー宣言をしなければなりません。
      s = r.readLine();
                     ^
エラー 1 個

C:\>

 一見正しくコンパイルできそうなプログラムでしたがエラーになってしまいました。なぜこのプログラムがコンパイルできなかったのか考えてみましょう。
 
 本連載で初めて登場するクラス「BufferedReader」はバッファからデータを取り出すために使うクラスです。コンストラクタの引数でnew InputStreamReader(System.in)と指定することにより、読み込み元のバッファを「InputStreamReader」からと指定しています。InputStreamReaderはバイト単位のデータを文字として読み込むためのクラスです。この場合、バイト単位のデータの読み込み元を「System.in」に指定しています。これまでのサンプルプログラムで何度となく登場していた「System.out」が画面出力を表すのに対して、System.inはキーボードからの入力を表します。従って「BufferedReader r = new BufferedReader( new InputStreamReader(System.in) )」全体では「キーボードから入力したバイト単位のデータを文字として読み込んでバッファにためるためのオブジェクトを生成し、そのオブジェクトにBufferedReader型の変数rでアクセスできるようにする」という意味になります。
 
  コンパイラがエラーを検出したのは「s = r.readLine();」の部分です。BufferedReaderクラスのメソッドであるreadLine()を実行するときに、「IOException」という例外が発生する可能性があるのに対処されていませんよ、というエラーです。readLine()のように、メソッドによっては発生し得る特定の例外についてプログラマが対処方法を決めておかなければならないのです。このとき使うのが「try〜catch」のブロックです。

10÷Nを計算する その2
public class DivideCalc2 {
  public static void main( String args[] ) {
    BufferedReader r = new BufferedReader( new InputStreamReader(System.in) );

    try {
      System.out.println( "10÷Nの計算をします" );
      System.out.println( "Nの値を入力してください:" );

      String s = r.readLine();
      int n = Integer.parseInt(s);

      System.out.println( "答えは" + (10 / n) + "です。" );
    } catch ( IOException e) {

      System.out.println( "キーボードが故障しているのかもしれません" );
    }
  }
}

 今回は無事にコンパイルできたはずです。「String s = r.readLine();」の部分でキーボードから文字を入力し、リターンキーを押したところで文字列が変数sに格納されます。さらに、変数sに格納された文字列はInteger型の静的メソッドであるparseInt()によって数値に変換され、int型の変数nに格納される、という流れです。parseInt()はHTMLDocument型クラスでHTMLを解釈するときに作ったparse()と同じように、文字列を解釈するメソッドです。

 では、プログラムを実行してみましょう。

C:\>javac DivideCalc2.java
C:\>java DivideCalc2
10÷Nの計算をします
Nの値を入力してください:
5
答えは2です。

 変更後のプログラムでは、処理の大部分を「try { 〜 }」のブロックに入れました。さらに、「catch { 〜 }」ブロックで、tryブロック内で発生したIOExceptionの例外を捕捉し、エラーメッセージを表示するようにしました。try〜catchの構文では、tryブロックに本来の処理、catchブロックに例外発生時の処理を書くことで、予期しないエラーに対処できるようになります。

 ただ、このプログラムは発生し得る例外に対処し切れていません。例えば、キーボードから数字ではない文字を入力したらどうなるでしょうか? 試しに「abc」という文字列を入力してみましょう。

C:\>java DivideCalc2
10÷Nの計算をします
Nの値を入力してください:
abc
Exception in thread "main" java.lang.NumberFormatException:
 For input string: "abc"
        at java.lang.NumberFormatException.forInputString(NumberFormatException.
java:48)
        at java.lang.Integer.parseInt(Integer.java:468)
        at java.lang.Integer.parseInt(Integer.java:518)
        at DivideCalc2.main(DivideCalc2.java:12)

C:\>

 数値に変換できるはずのない「abc」という文字を入力したため、「int n = Integer.parseInt(s);」の部分で例外が発生してしまいました。実は、tryブロック内の処理で発生した例外を正しく捕捉するには、適切なcatchブロックを個々に記述しなければならないのです。

 ここでJava言語のAPI 仕様でIntegerクラスのparseInt()メソッドで発生する例外を調べると、文字列が構文解析可能な整数型を含まない場合に「NumberFormatException」という例外が発生することが分かります。そこで、プログラムを以下のように修正します。

10÷Nを計算する その3
public class DivideCalc2 {
  public static void main( String args[] ) {
    BufferedReader r = new BufferedReader( new InputStreamReader(System.in) );

    try {
      System.out.println( "10÷Nの計算をします" );
      System.out.println( "Nの値を入力してください:" );

      String s = r.readLine();
      int n = Integer.parseInt(s);

      System.out.println( "答えは" + (10 / n) + "です。" );
    } catch ( IOException e) {
      System.out.println( "キーボードが故障しているのかもしれません" );
    } catch ( NumberFormatException e ) {
      System.out.println( "数値に変換できる文字を入力してください" );
  }

}
}

 このプログラムを実行し、Nの値に「abc」を入力してみましょう。

C:\>java DivideCalc3
10÷Nの計算をします
Nの値を入力してください:
abc
数値に変換できる文字を入力してください

C:\>

 今回はエラーが表示されずにプログラム内で例外が捕捉されて、ユーザーには対処方法のメッセージが表示されました。「catch ( <捕捉したい例外のクラス> <変数名> )」と記述することで、例外ごとに対処方法を切り分けられるのです。
 
  一方、例外を捕捉できさえすればよい、という場合もあるでしょう。発生し得るすべての例外をいちいち調べていたらきりがありません。そこで、例外の種類を特定しない以下のようなcatchブロックの書き方もできます。

10÷Nを計算する その4
public class DivideCalc4 {
  public static void main( String args[] ) {
    BufferedReader r = new BufferedReader( new InputStreamReader(System.in) );

    try {
      System.out.println( "10÷Nの計算をします" );
      System.out.println( "Nの値を入力してください:" );

      String s = r.readLine();
      int n = Integer.parseInt(s);

      System.out.println( "答えは" + (10 / n) + "です。" );
    } catch ( Exception e ) {
      System.out.println( "何かの例外が発生したので処理を続行できませんでした" );
    }

  }
}

  例外発生の有無に関係なく実行するための
−finallyブロック−

 catchブロックは例外を捕捉し、例外発生時の処理を記述するために使いますが、例外が発生してもしなくても処理しなければならないことはどうしたらいいのでしょうか?

 例えば、テキストファイルの内容を画面に表示するプログラムを考えてみます。WindowsなどのOSが管理しているファイルを読み込むには、まず特定のファイルを扱うことをOSに対して宣言(オープン)し、その後、使い終わったことをOSに通知(クローズ)しなければなりません。ディスクの読み込み中にエラーが発生し例外を捕捉しても、そのままプログラムを終了させてしまうのではなく、必ずファイルを閉じなければならないのです。

 このように、特定のリソースの利用開始から終了するまでに起こる例外を捕捉しつつ、最後に確実にリソースを解放することは、ユーザーに安定したサービスを提供するプログラムを書くために欠かせないことです。そこでfinallyという文を使います。

テキストファイルを表示する
import java.io.*;

public class ShowTextFile {
  public static void main( String args[] ) {
    if ( args.length == 0 ) return;
    try {
      FileInputStream fis = new FileInputStream(args[0]);
      try {
        int i;
        for ( i = fis.read(); i != -1; i = fis.read() )
          System.out.print((char)i);
        System.out.println();
      } catch ( IOException e ) {
        System.out.println("ディスクI/Oエラー");
        return;
      } finally {
        fis.close();
      }

    } catch ( FileNotFoundException e ) {
      System.out.println("ファイルが見つかりません");
      return;
    } catch ( IOException e ) {
      System.out.println("ファイルを閉じられません");
      return;
    }
  }
}

 tryブロックの中にtryブロックがある、一見複雑な構造になりました。そこで重要な部分を抜き出して説明します。

FileInputStream fis = new FileInputStream(args[0]);
try {
  int i;
  for ( i = fis.read(); i != -1; i = fis.read() )
    System.out.print((char)i);
  System.out.println();
  } catch ( IOException e ) {
    System.out.println("ディスクI/Oエラー");
    return;
  } finally {
    fis.close();
}

 1行目では、テキストファイル読み込み用のクラスFileInputStreamのオブジェクトを生成しています。FileInputStreamクラスのコンストラクタの引数になっている「args[0]」は、コマンドラインでこのプログラムを実行したときの1番目の引数を格納する配列です。例えば、「java ShowTextFile TEST.TXT」と入力したときのargs[0]には「TEST.TXT」が格納されます。

C:\>javac ShowTextFile.java
C:\>java ShowTextFile test.txt

(ここにtest.txtの内容が表示される)

C:\>


  制御文としての役割

 例として挙げたテキストファイルを読み込んで表示するプログラムの主要部分は、3つの部分に分かれています。tryブロックでは、データを1文字ずつ読み込んで、ファイルの終わりに達するまで画面に表示します。catchブロックではIOExceptionの例外を捕捉し、ディスクI/Oエラーによる読み込みの中断に備えています。最後のfinallyブロックでは、現在開いているファイルを閉じています。

 この例で重要なのは、最後のfinallyブロックはたとえ例外が発生しても必ず実行されることです。例えば、ディスクI/Oエラーが発生してcatchブロックに制御が移ったとします。このときプログラムが画面に「ディスクI/Oエラー」と表示することまでは通常の文と同じです。しかし、次にあるreturn文が実行されてメソッドを終了するとき、すぐにはメソッドを抜け出さずにfinallyブロックにある「fis.close()」が実行されるのです。「第6回 プログラムの制御構造を理解する」でreturnを「メソッドを呼び出した親メソッドに制御を戻す」文と説明しました。しかし、try〜catch〜finallyの場合は実行順が通常とは少し異なり、finallyブロックにある文を実行してから親メソッドに制御が戻されます。つまり、例外処理はreturnやbreakといった処理を中断させる制御文のふるまいを若干変える効果があるのです。

 また、この例ではファイルをオープンして内容を表示するのに関連する処理が、本来の処理、エラー時の処理、終了処理の3つに区分けされました。例外を使うと、forやwhileなどの制御文とは別の意味で、プログラムの構造を役割に応じて記述できるのです。そこで次回は、プログラムを分かりやすく記述するという例外のもう1つの側面について説明します。






「いまから始めるJava」記事一覧


ホワイトペーパーTechTargetジャパン

Java Solution フォーラム 新着記事

@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

RSSフィード

スキルアップ/キャリアアップ(JOB@IT)

- PR -
- PR -

お勧め求人情報

キャリアアップ 〜JOB@IT
@IT Special -PR-
  TomcatやJBossなどAPサーバ環境に関する
情報を集約! “業務”用APサーバ大百科

New!
  一気に解説! 最新のクラスタストレージ
「RAIDを超えたストレージ基準」……など

New!
  クラウド的ユーザー体験の変化は脅威か?
仮想化技術を使いこなす運用管理術を紹介

New!

  上司や部下、部署内メンバーとの情報共有
を“ガラッ”と変えるコラボツールとは?

New!
  おばかアプリ選手権、第4弾開催中!!
ムダにカッコよくてくだらない作品求ム!

  社内ファイルサーバを“クラウド”に統合
VPN直結「クラウド型ストレージ」を紹介

  Twitterのアカウントはなぜ突破された?
メールによる新手の攻撃手法とその対策

  もう仮想化のお試しフェイズは終わりだ!
Hyper-V 2.0が基幹システムも仮想化

  美人!? まあまあ? 気になる いやし系!!
PV急増で「美人時計」がとった手段とは?

  クライアント企業から求められる人材
⇒IT技術と経営戦略を併せ持つ「戦略家」

  .NET編集長が実践する「技術情報検索術」
サンプル・コードを簡単に探す“技”は?

  業務効率と情報セキュリティ対策を両立!
手間なく確実に機密情報を守る方法とは?

  進化を続ける富士通ストレージETERNUS DX
製品開発者の自信を裏付けるものとは何か

  運用管理の課題を“2つの観点”から分析
ユーザー満足度の高い「仮想環境」とは?

  【CTC事例】約30の基幹システムを統合!
膨大なバッジジョブを制御した方法は?

  仮想化すればコストは削減できるか?
仮想化に必要な「3つの視点」を解説する

  その数、なんと400台以上! グループ内
サーバの「統合管理」によるメリットは?