2006年も折り返しを過ぎて、いよいよ暑いシーズンになってきました。昨年から「クールビズ」スタイルが導入されて、普段スーツで勤務しているエンジニアの方々にとってはだいぶ過ごしやすくなりましたね。普段はカジュアルで勤務しているエンジニアの方々にとっても、冷房が控えめになることで体調が良くなったりと恩恵があったのではないでしょうか。デバッグも「クールビズ」に倣って「クールに」こなせるようになるといいですね。では今月もどうぞお付き合いください。
分類:コンパイルエラー
このコンパイルエラーは、メッセージの内容をそのまま読めば意味が理解できるものですので、原因は割合分かりやすい部類のエラーです。
次の例を見てください。
package kx; public class Tips3_1 { public static void main(String[] args) { int sum; int start = 10; if (start<=10) { sum = 0; } for(int i=0;i<10;i++) { sum = sum + i; System.out.println("これまでの合計="+sum); } } }
このプログラムをコンパイルすると、「ローカル変数 sum が初期化されていない可能性があります。」というエラーが表示されます。
確かに、7行目でsumというローカル変数を定義していますので、この変数が初期化されていないのが原因と思われます。Javaでは型の種類を問わず、あらゆる変数は値の代入を行って明示的に初期化しないと利用できない(ただ定義しただけでは、何も値が入っていないと見なされる)という文法になっています。言語によっては、定義しただけで、デフォルトの値が設定される場合もあるかもしれませんが、そのような言語を知っている型は注意が必要です。しかし、このプログラムではその後の11行目に「sum=0;」という記述がありますので、全く初期化していないわけでもなさそうです。ではどうしてコンパイルエラーになってしまうのでしょうか。
Javaのコンパイラは制御文(ifやswitch、forなど)を含めた構文解析をある程度行ってコンパイルエラーの判定をしています。このプログラムでは、sumを初期化する処理が、if文の条件式の評価結果がtrueの場合は実行されますが、評価結果がfalseの場合は実行されないため、「初期化されていない可能性がある」というコンパイルエラーと判定しているのです。ですから、このコンパイルエラーに対処するためには、制御文の実行結果がどのような結果になっても、必ず初期化処理が実行されるようにする必要があります。
if文やswitch〜case文のような分岐を伴う制御処理の場合、どの条件にも当てはまらない場合の分岐を記述することができるようになっています。if文では、elseを使うことで、条件が成立しない場合の処理を記述できますし、switch〜case文では、default節を使うことによって、ほかのどの選択肢にも当てはまらない場合の処理を記述することができます。分岐処理の中で変数の初期化をする必要がある場合は、これらを考慮して初期化の処理が漏れないように注意してみましょう。
変数が初期化されていない可能性があります=
1.変数に初期値を代入する処理を忘れていないか
2.または分岐処理の条件によって実行されない初期化がないかどうかを確認
分類:コンパイルエラー
例外処理を行うプログラムを書いていると、try節内で定義しておいた変数を、catch節内でも参照したいことが出てくると思います。これが顕著なのが、JDBCを使うプログラムを書く場合です。例えば、次のプログラムを見てください。
package kx; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class Tips3_3 { public static void main(String[] args) { try { Class.forName("com.mysql.jdbc.Driver"); String url ="jdbc:mysql://localhost/mydb"; String user = "user"; String pass = "passwd"; Connection conn = DriverManager.getConnection(url,user,pass); Statement stmt = conn.createStatement(); ResultSet result = stmt.executeQuery("SELECT * FROM EMP"); (省略) } 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(SQLException ex) { ex.printStackTrace(); } } } }
このプログラムでは、JDBC APIを利用してテーブル内容の参照を行う処理を実装しています。finally節でResultSet、Statement、Connectionオブジェクトをクローズする処理を書いていますが、これはtry節中で例外が発生するかどうかにかかわらず、必ずこれらのリソースが解放されるようにするための記述です。ところがこのプログラムをコンパイルすると、「〜を解決できません。」というコンパイルエラーが複数発生します。
「〜を解決できません。」というコンパイルエラーは、定義されていない変数を利用した場合などに表示されるエラーです。resultやstmt、connはtry節で定義してあるはずですが、なぜこのようなコンパイルエラーになってしまうのでしょうか。それには、Javaの変数のスコープ(有効範囲)を正しく知る必要があります。
Javaにおける変数のスコープは、基本的に「ブロック」と呼ばれる「{」から「}」で囲まれた範囲で決定します。ブロックにはクラスの開始と終了を示すブロックから、メソッドの開始と終了を示すブロック、for文やif文などの制御文で使われるブロックまで、さまざまなレベルのものがありますが、スコープに関するルールはどのブロックでも共通です。つまり、同じメソッドの内部であっても、ブロックの内部で定義された変数は、そのブロック内でしか参照できません。ブロックの外側で定義されている変数であれば、そこに属するブロックであれば、どのブロックからでも参照することができます。
このプログラムの場合は、try節のブロック内部でresultやstmt、connを定義しているために、catch節やfinally節のブロックからは、直接その変数が参照できないためにコンパイルエラーになってしまうのです。try〜catch〜finally節はひとまとまりの構文ですから、変数は共通に使えるような気がしてしまいますが、try節、catch節、finally節はそれぞれ別のブロックとして認識されています。ですから正しく動作させるためには、次のプログラムのように、これらの変数をブロックの外側で定義することになります。
ちなみに、conn、stmt、resultの定義時にnullを代入しているのは、try節の内部で例外が発生した場合、finally節を実行する時点でこれらの変数が初期化されないままになっている可能性があるためです。
package kx; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class Tips3_3_2 { public static void main(String[] args) { Connection conn = null; Statement? stmt = null; ResultSet? result = null; try { Class.forName("com.mysql.jdbc.Driver"); String url ="jdbc:mysql://localhost/mydb"; String user = "user"; String pass = "passwd"; conn = DriverManager.getConnection(url,user,pass); stmt = conn.createStatement(); result = stmt.executeQuery("SELECT * FROM EMP"); (省略) } 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(SQLException ex) { ex.printStackTrace(); } } } }
定義してあるはずの変数が使えない=使用したい変数がスコープの内部で定義されているかどうかを確認
分類:ランタイムエラー
システム開発をしていく中では、自分のマシンで動いたコードやモジュールが、別の環境に移して実行するとうまく動かない、ということがあります。原因は、もちろんその状況によって異なりますが、よく見られる原因の1つとして、モジュールの構成や環境設定などが、自分のマシンと同一になっていないことが挙げられます。「ClassNotFoundException」や別の項で紹介する「NoClassDefFoundError」「NoSuchMethodException」は、まさにそういった原因で発生します。
「ClassNotFoundException」ですが、JDKのドキュメントによれば、 アプリケーションが、クラスの文字列名を使用して次のメソッドでロードしようとしたが、指定された名前のクラスの定義が見つからなかった場合にスローされます。
とあります。ここに説明されている3つのメソッドの中で、業務システム開発でよく使われると思われるのが、ClassクラスのforNameメソッドではないでしょうか。例えば、JDBCを利用するアプリケーションを開発する場合に、JDBCドライバのクラスをロードする場合にも利用しますし、共通のインターフェイスを実装した実装クラスのインスタンスを作成するためにそのクラスをロードするときなどにも利用することがあるでしょう。ClassクラスのforNameメソッドは、引数にパッケージ名を含むクラス名を指定し、そのクラスをロードします。「指定された名前のクラス定義が見つからなかった」というのは、(1)そのクラスのクラスファイルがCLASSPATHに登録された場所にない、もしくは(2)指定しているクラス名が間違っている、のどちらかのパターンです。
もし、Eclipseなどのツールを使って開発していて、Eclipse上で実行すれば例外が出ないのに、それをほかのマシンに持ってきて、コマンドラインなどで実行したらClassNotFoundExceptionが出た、という場合は、(1)のケースを調査してみましょう。そのクラスのクラスファイルがCLASSPATHに登録された場所にない、というのは、JARファイルのようなアーカイブに含まれているクラスをロードしようとしている場合に、そのJARファイルの場所がCLASSPATHに登録されていない場合も含みます。
そもそも、開発環境上で開発、実行する段階から、この例外が発生している場合は、(1)(2)のどちらか、もしくは両方の可能性がありますので、両方のケースを調査し、CLASSPATHや指定しているクラス名が正しい状態になっているか確認してみましょう。
ClassNotFoundExceptionの発生=
(1)ロードしようとするクラスのクラスファイルがCLASSPATHに登録された場所に存在しない
(2)ロードしようとするクラスが含まれているJARファイルがCLASSPATHに登録された場所に存在しない
(3)指定しているクラス名の記述が間違っている
Copyright © ITmedia, Inc. All Rights Reserved.