お盆を過ぎて暑さもようやく一段落してきましたが、皆さんいかがお過ごしですか。お盆休みで故郷に帰省して、のんびりできた方もいらっしゃるでしょうか。お盆休みを避けて、これから休暇を取られる方や、開発プロジェクトによっては、上期末に納品を控えていて、休みは納品後にまとめて、という方もいらっしゃるかもしれませんね。
この連載もいよいよラストスパートです。お休みが取れた方も、これからお休みを取られる方も、最後までどうぞお付き合いください。
分類:コンパイルエラー
今回ご紹介するコンパイルエラーは、ちょっと特殊な環境で発生するエラーです。閉じた環境で開発をしている場合にはなかなか起きないですが、チームで開発をしていたりすると、発生することもありますのでぜひ知っておいてください。
このコンパイルエラーは、かなり限定的な状況で発生するものなので、エラーを発生する手順を先に紹介していきたいと思います。
まず、次のような2つのクラスTips4_1_1、Tips4_1_2を用意します。
package kx; public class Tips4_1_1 { public static void main(String[] args) { Tips4_1_2 t = new Tips4_1_2(); t.print(); } }
package kx; public class Tips4_1_2 { public void print() { System.out.println("Hello!"); } }
コードを見ると分かるように、Tips4_1_1は、内部でTips4_1_2のインスタンスを生成し、メソッドを呼び出しているので、Tips4_1_1がTips4_1_2を必要としていることが分かります(クラス図を図1に示します)。
では、これらのクラスをJ2SE5.0でコンパイルしてみましょう。これらのクラスには特に文法的な誤りはありませんので、コンパイルエラーなどは起きません。
次に、Tips4_1_1だけをJ2SE 1.4でコンパイルしてみてください(IDEなどで実行すると再現しないことがあるので、コマンドラインから実行することをお勧めします)。
すると、次のようなコンパイルエラーが表示されます(画面の様子は図2)。
kx\Tips4_1_1.java:11: kx.Tips4_1_2 にアクセスできません。 クラスファイル .\kx\Tips4_1_2.class は不正です。 クラスファイルのバージョン 49.0 は不正です。48.0 であるべきです。 削除するか、クラスパスの正しいサブディレクトリにあるかを確認してください。 Tips4_1_2 t = new Tips4_1_2(); ^ エラー 1 個
つまり、Tips4_1_1.javaをコンパイルすると、そのクラス中でTips4_1_2を参照している(インスタンスの生成およびメソッド呼び出し)ので、コンパイラはTips4_1_2のクラスファイルを参照します。Tips4_1_2にはすでにコンパイル済みのクラスファイルが存在していますが、そのクラスファイルはJ2SE5.0でコンパイルしたものなので、コンパイラ自身のバージョン(J2SE1.4)よりも新しいバージョンです。
このような場合、コンパイラはTips4_1_2.javaをリコンパイルせずに、上記のようなコンパイルエラーを表示するのです(図3を参照)。
このコンパイルエラーが出た場合には、以下のいずれかの方法で解消することができます。
「クラスファイルのバージョン番号xx.xは不正です。xx.xであるべきです」が表示されたら=以前コンパイルしたときのJavaのバージョンと、いまコンパイルしようとしているコンパイラのJavaのバージョンが異なっていないかチェックする。
分類:ランタイムエラー
Javaでは、あらゆるクラスは暗黙のうちにjava.lang.Objectを継承している、というルールがあります。さらに、ポリモルフィズムといって、あるクラスの型がその親クラスであるかのように見せ掛けることもできるようになっています。この2つの性質を利用すると、型の違いに合わせていちいちメソッドを定義しなくても、あらゆるクラスのインスタンスを引数に受け取ったり、戻り値として呼び出し元に渡したりすることができます。
次の例を見てください。
package kx; public class Tips4_2_1 { private Object anyObj; public void setObject(Object newObj) { nyObj = newObj; } public Object getObject() { return anyObj; } }
このクラスはsetObject、getObjectというgetter/setterを持っています。このgetter/setterによってアクセスされる値はObject型の変数なので、どのようなクラスのインスタンスも渡すことができ、また、受け取ることができます。ですが注意しなければならないのは、次のようなプログラムを書いた場合です。
package kx; public class Tips4_2_2 { public static void main(String[] args) { Tips4_2_1 t = new Tips4_2_1(); String s = "ナレッジエックス"; t.setObject(s); Integer i = (Integer)t.getObject(); } }
このプログラムでは、前述のTips4_2_1クラスのインスタンスに、setObjectメソッドでString型のオブジェクトを渡し、直後にgetObjectメソッドでそのオブジェクトを取り出しています。getObjectメソッドは戻り値がObject型なので、通常は取り出したオブジェクトはキャスト演算子によってキャスト(型変換)をしてから使います。このプログラムでも取り出したオブジェクトをキャストして変数に代入しています。このプログラムをコンパイルしても、コンパイルエラーにはなりませんが、実行すると、ClassCastExceptionが発生します(図4)。
このプログラムでは、String型のオブジェクトをsetObjectメソッドで渡しているにもかかわらず、取り出したオブジェクトをInteger型に変換しようとしています。ClassCastExceptionは、そのオブジェクトと実際には継承関係がないクラスにキャストをしようとすると発生します。StringとIntegerは、共通の親を持つクラスではありますが、両者には直接の継承関係はありません(図5)。
このようなケースは、ポリモルフィズムの性質を生かして汎用的な操作ができる半面、外部からはどんな型のオブジェクトが入っているか分かりにくいことが問題となります。このような問題に対する1つの解決策として、J2SE 5.0からはGenericsという機能が使えるようになりました。Genericsを使ってクラスを定義しておくと、クラスの変数やメソッドの引数、戻り値の型などを、インスタンスを生成するときに動的に決めることができるようになります。次の例を見てください。
package kx; public class Tips4_2_3<T> { private T anyObj; public void setObject(T newObj) { anyObj = newObj; } public T getObject() { return anyObj; } }
「T」というのが、Genericsの機能によって、後から決めることのできる型名(の代わり)です。このクラスを使って、先ほどと同じようなプログラムを作ってみます。
package kx; public class Tips4_2_4 { public static void main(String[] args) { Tips4_2_3<String> t = new Tips4_2_3<String>(); String s = "ナレッジエックス"; t.setObject(s); Integer i = (Integer)t.getObject(); } }
Genericsの機能を使い、インスタンス生成時に「T」としていた部分をString型に設定しています。これによって、クラスTips4_2_3のgetter/setterでやりとりされるオブジェクトの型は、String型に限定されたことになります。そのため、getObjectメソッドで取り出したオブジェクトをInteger型にキャストしようとする記述は、コンパイル時にコンパイルエラーとなり(図6)、実行前に文法上の誤りとして修正することが可能になります。
ClassCastExceptionの発生=継承関係にないクラスへキャストしようとしていないかを確認。Genericsを活用すれば実行前にコンパイルエラーとして検出させることもできる。
分類:仕様どおり動かない
プログラムを実行して例外が発生するのは、正常な動作をしていないので望ましくない状況ではありますが、例外が発生すべき状況なのに例外が発生しないというのも、安全なプログラムの実行のためには望ましくない状況です。例えば、次のプログラムを見てください。
package kx; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; public class Tips4_3 { public static ArrayList<String> getNames() { Connection conn?? = null; Statement? stmt?? = null; ResultSet? result = null; ArrayList<String> list = new ArrayList<String>(); 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 NAME FROM EMP"); while(result.next()) { list.add(result.getString("NAME")); } } catch(ClassNotFoundException ex) { } catch(SQLException ex) { } finally { try { if (result!=null) result.close(); if (stmt!=null) stmt.close(); if (conn!=null) conn.close(); } catch(SQLException ex) { } } return list; } }
このクラスのgetNamesメソッドはJDBCを使ってEMPというテーブルにアクセスし、NAMEカラムの内容をArrayListに格納して返すというものです。EMPテーブルが空であった場合は、ArrayListには何も格納されませんので、中身が空のArrayListオブジェクトが返却されます。しかしこのメソッドでは、例外をcatch節で捕捉しているものの、catch節内に何も処理を書いていないため、データベースアクセスの処理で例外が発生した場合にも、中身が空のArrayListオブジェクトが返されてしまいます。
try〜catch節によって例外を捕捉している場合は、特別な理由がない限りは、catch節を空のままにしてはいけません。この例のように、正常な状況による処理結果なのか、異常な状況による処理結果なのかが、全く区別できなくなってしまうからです。発生した例外への対処を、どのクラスのどのメソッドが行うべきかは、もちろん設計によって異なりますので、この場合も選択肢は1つではありませんが、例えば、次のような対応が考えられます。
(1)catch節を書かずに、メソッド定義部にthrows SQLException、ClassNotFoundExceptionなどを追加し、例外処理はメソッドの呼び出し元に任せる。
変更されたコード(getNamesメソッド)は次のようなものです。
public static ArrayList<String> getNames() throws SQLException,ClassNotFoundException { (省略) try { Class.forName("com.mysql.jdbc.Driver"); (省略) } finally { (省略) } return list; }
(2)メソッド定義部にユーザー定義の例外クラスを記述する。SQLExceptionやClassNotFoundExceptionをcatch節で捕捉したら、ユーザー定義の例外クラスのインスタンスを生成し、コンストラクタには捕捉した例外オブジェクトを渡してthrowする。
コードは次のようなものです。
package kx; public class TipsException extends Exception { public TipsException(Throwable t) { super(t); } }
public static ArrayList<String> getNames() throws TipsException { (省略) try { (省略) } catch(Exception ex) { throw new TipsException(ex); } finally { try { (省略) } catch(SQLException ex) { throw new TipsException(ex); } } return list; }
(3)もし、(1)(2)どちらの対処もできない場合は、少なくともcatch節で例外を捕捉したときに、printStackTrace()メソッドで、例外のトレースは出力しておく。
そうすれば、例外発生時には気が付かなかったとしても、後でログを確認することで例外の状況を知ることができるためです。
例外に相当する状況なのに例外が出ない=例外をcatchしているのに、何も対処をしていない個所がないかを調べる。個所がないかを調べる。
Copyright © ITmedia, Inc. All Rights Reserved.