連載
» 2006年06月22日 00時00分 公開

デバッグのヒント教えます(4):「NumberFormatException」が発生したらどうする?

[中越智哉,ナレッジエックス]

 気がついてみれば6月も下旬。1年の半分がまもなく過ぎようとしています。

 年をとるほど時の経つのが早く感じられると言いますが、これを思うたびに「光陰矢のごとし」という言葉を思い出します。と同時に、「少年老い易く学成り難し」という言葉も思い出してしまいます。月日の流れは速くしたり遅くしたり、もちろん止めるたりすることもできません。ですから、「矢のように」過ぎていく月日の流れだけに身を任せてしまっては、ただでさえ「成り難い」デバッグのスキルは向上できません。少しずつでも着実にデバッグのスキルを、今月のTipsでも体得していただければ、と思っております。

「NumberFormatException」が発生したらどう対処するか

分類:ランタイムエラー

 NumberFormatExceptionは、文字通り数値のフォーマットに関する例外です。では、どのような場合にこの例外が発生するのでしょうか。

 次の例を見てください。

package kx;

public class Tips2_4_1 {

        public static void main(String[] args) {

                      String strNum1??? = "VWXYZ";
                      int n1 = Integer.parseInt(strNum1);
                      System.out.println(strNum1+"をintに変換すると"+n1+"です");

       }
}

 このプログラムでは、String型の変数を、IntegerクラスのparseIntメソッドによってint型の数値に変換し、その結果を表示しようとしています。ですが、このプログラムを実行すると、次のような表示となります。

クリックすると全体を表示します クリックすると全体を表示します

 この例では、変数strNum1の内容が整数を表していないために、Integer.parseInt()の実行でNumberFormatExceptionが発生しています。

 この例は変換しようとしている文字列が数値を表していないために、比較的原因が分かりやすいのですが、文字列が整数ではなく小数を表している場合(Integer.parseInt()では、int型への変換なので小数を表す文字列は渡せない)や、文字列がint型の値の範囲を超える整数を表している場合(int型で扱える整数は、-2147483648以上、2147483647以下と決められている)も、NumberFormatExceptionが発生しますので、注意してください。

package kx;
public class Tips2_4_2 {
        public static void main(String[] args) {
                  String strNum2??? = "123.45";
                  int n2 = Integer.parseInt(strNum2);
                  System.out.println(strNum2+"の3乗は"+(n2*n2*n2)+"です");

          }
}
小数を変換しようとする例

package kx;
public class Tips2_4_3 {
        public static void main(String[] args) {
                  String strNum3??? = "12345678901";
                  int n3 = Integer.parseInt(strNum3);
                  System.out.println(strNum3+"をintに変換すると"+n3+"です");

          }
}
int型の値の範囲を超える数値を変換しようとする例

「NumberFormatException」=Integer.parseInt()やDouble.parseDouble()などに渡している文字列が、int型やdouble型の値として適切に表現できるかチェック


「SQLException」のデバッグに使えるメソッド

分類:ランタイムエラー

  SQLExceptionは、JDBC APIを利用してデータベースにアクセスする際に発生する可能性のある例外です。JDBCのほとんどのメソッドには、この例外が発生する可能性があることが定義されています。ですからJDBCを利用するプログラムでは、必ずといっていいほどtry〜catch節でこの例外に対処しているはずです。

 次の例を見てください。

package kx;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class Tips2_6 {

        public static void main(String[] args) {

            Connection conn = null;
            Statement stmt = null;
            ResultSet result = null;

            try {
                  Class.forName("com.mysql.jdbc.Driver");
                  conn                      = DriverManager.getConnection("jdbc:mysql://localhost/mysql","root","password");
                  stmt = conn.createStatement();
                  result = stmt.executeQuery("SELECT * FROM DB");
                  while(result.next()) {
                       System.out.print(result.getString("HOST")+",");
                       System.out.print(result.getString("DB")+",");
                       System.out.println(result.getString("USER"));
                  }

            } catch(ClassNotFoundException ex) {
                  ex.printStackTrace();
            } catch(SQLException ex) {
                  ex.printStackTrace();
            } finally {
                  try {
                        if (result!=null) result.close();
                        if (stmt!=null) stmt.close();
                        if (conn!=null) conn.close();
                  } catch(Exception ex) {   }
              }
        }

}

 このプログラムは、MySQLのmysqlデータベースに接続し、DBテーブルの内容のうちでHOST、USER、DBの3つのカラムについて全レコードの内容を表示します。何もトラブルがなければこのプログラムは正常に動作しますが、例えばプログラムの実行時にMySQLが起動していなかったりすると、次のような表示になります。

クリックすると全体を表示します クリックすると全体を表示します

 もちろん、この表示だけでもかなりの情報を得ることができます。スタックトレースの詳細メッセージで、「Unable to connect to any hosts」という表記があることから、接続が失敗していることが分かり、データベース側のトラブル(起動していない)であると推測することができます。しかし、もしさらに詳細な状況を知る必要がある場面では、SQLExceptionの持つメソッドを利用すると便利です。SQLExceptionに対するcatch節の中に、次のコードを加えてみましょう。

            } catch(SQLException ex) {
                  ex.printStackTrace();
                  System.out.println("SQLState :"+ex.getSQLState());
                  System.out.println("ErrorCode:"+ex.getErrorCode());

            } finally {
31行目の下に2行追加する(赤字の部分)

 getSQLStateメソッドは、例外発生時のSQLStateと呼ばれる状態コード(XOPENもしくはSQL99に従う)を返すメソッドで、このコードはデータベースの種類によらず標準化されたコードです。getErrorCodeメソッドは、データベースベンダ固有のエラーコードを返すメソッドで、データベースの種類によって返す値は異なりますが、それぞれのデータベースに専門化されているため、より詳細な状況を知ることのできる場合があります。

 では、さらにSQLを実行させている部分のコードを、次のように書き換えてみましょう。

            result = stmt.executeQuery("SELECT * FRM DB");

 一見してお分かりのように、SQLのFROMキーワードが「FRM」になっており構文が誤っています。この状態でプログラムを実行するとどのような結果になるでしょうか。

実行すると次のようになります。

クリックすると全体を表示します クリックすると全体を表示します

 SQLStateは42000、エラーコードは1064となりました。例えば、1064というエラーコードはMySQLに固有な番号ですが、MySQLのマニュアルで調べてみますと、ER_PARSE_ERRORという名称と、詳細メッセージのフォーマットを知ることができます。この例では、詳細メッセージの内容でおおむねどのような原因かを知ることができますが、複雑な状況への対応には、これらの情報も役に立つことでしょう。

SQLExceptionでは、詳細メッセージだけではなく、SQLStateやベンダ固有のエラーコードのような情報も得ることができ、状況の解析に役立てることができる。


バグの少ないコーディングのために状況を把握しやすくしておこう

 ここでご紹介する手法は、「バグを減らす」というよりは、「バグが見つかったときの対処を早くする」というものです。特に、プログラムの実行時に例外が発生した場合、その原因をどれだけ早く特定できるかは、例外への対処をどのように記述するかで大きく変わってきます。ここでは例外対処の記述方法について留意すべきポイントを3つご紹介します。

例外対処の記述方法

 Javaの例外処理機構は、トラブルが発生してもプログラム内で対処を行うことができるため、非常に有用な構文の1つです。しかし、この構文もきちんと使いこなさないと、かえってバグの発見を遅らせることになりますので注意が必要です。try〜catch節を使って例外を捕捉するとき、自分の作成したメソッドから例外を送出するときなどは、次のような点を注意してみてください。

1.catch節ではprintStackTrace()などを使い、例外のトレースを表示する

 try〜catch節を使えば、あらゆる例外を自分のプログラム内で捕捉し、対処を行うことができます。これは非常に強力な実行制御機能ですが、対処の内容はプログラマに任されているため、プログラマが適切な対処を行わないと、かえって深刻な状態を引き起こすことがあります。

 よく、catch節を記述してはいるものの、何も対処をしていない(最悪の場合、catch節の中が空になっている)コードを見かけることがありますが、これでは、せっかく例外を捕捉しているのに、みすみすその例外を見逃しているのと一緒です。

 コードを書いている途中では、その例外にこれといった対処が決められない場合もあると思いますが、最低でもprintStackTraceメソッドを使い(Log4jなどを使っている場合は、ログ出力メソッドに例外インスタンスを渡すなどして)、どこでどんな例外が発生したのかだけは表示させられるようにしておきましょう。例外が起こっているのに、プログラマがそれに気付かなければ、バグの発見はそれだけ遅れてしまうことになるからです。

2.例外発生時にメッセージを付加しておく

 自分で作ったメソッドから例外を送出する場合は、例外の詳細メッセージをできるだけ詳しく記述しておくとよいでしょう。上記のようにprintStackTraceメソッドなどを使ってcatch節で常にトレースを表示している場合は、例外を捕捉すると必ず詳細メッセージも同時に表示されます。詳細メッセージに状況がより詳しく解析できる情報を書いておけば、バグの発見を早めることにつながります。

3.例外を送出するときに、原因となる例外を含めておく

 ある基底クラスのオーバーライドメソッドを実装する場合などに、自分で例外を送出したいが、送出できる例外の型が限定されている場合があると思います。例えば、次のようなコードを見てください。

package kx;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.TagSupport;

public class Tips2_7 extends TagSupport {
    public int doStartTag() throws JspException {

        try {
              JspWriter out = pageContext.getOut();
              out.println("<b>TIPS2_7</b>");
        } catch(Exception ex) {
              throw new JspException();
        }
        return SKIP_BODY;
    }


}

 これは、JSPのカスタムタグのタグハンドラクラスのサンプルですが、定義されているdoStartTagメソッドはjavax.servlet.jsp.tagext.TagSupportクラスのオーバーライドメソッドで、送出できる例外がjavax.servlet.jsp.JspExceptionクラスに限定されています。そのため、メソッド中に例外を捕捉した場合は、すべてJspExceptionとして送出し直すようにコーディングしています。ですがこのままでは例外を受け取った呼び出し元では、例外が発生したことは認識できますが、実際にどのような例外が発生しているのかまでは知ることができません。

 あるバージョンまでのJava(JDK1.3)では、このようにコーディングするしかなかったのですが、最近のJava(JDK1.4以降)では「連鎖例外」という機能をサポートしているため、ある例外オブジェクトの中に、その例外が発生する原因となった例外オブジェクトを含めることができるようになりました。ですから、上記の例外送出部分を、

                    throw new JspException(ex);

 と書き換えれば、受け取った側で原因となった例外をgetCauseメソッドで取り出し、詳しい状況を解析することができるようになります。これも、バグの原因を特定するために有用な方法の1つです。


Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。