
デバッグのヒント教えます(8)
JDKのバージョンが原因で起きるコンパイルエラー
ナレッジエックス
中越智哉
2006/8/31
INDEX |
お盆を過ぎて暑さもようやく一段落してきましたが、皆さんいかがお過ごしですか。お盆休みで故郷に帰省して、のんびりできた方もいらっしゃるでしょうか。お盆休みを避けて、これから休暇を取られる方や、開発プロジェクトによっては、上期末に納品を控えていて、休みは納品後にまとめて、という方もいらっしゃるかもしれませんね。
この連載もいよいよラストスパートです。お休みが取れた方も、これからお休みを取られる方も、最後までどうぞお付き合いください。
| JDKのバージョンの違いが原因で起きる コンパイルエラー |
分類:コンパイルエラー
今回ご紹介するコンパイルエラーは、ちょっと特殊な環境で発生するエラーです。閉じた環境で開発をしている場合にはなかなか起きないですが、チームで開発をしていたりすると、発生することもありますのでぜひ知っておいてください。
このコンパイルエラーは、かなり限定的な状況で発生するものなので、エラーを発生する手順を先に紹介していきたいと思います。
まず、次のような2つのクラスTips4_1_1、Tips4_1_2を用意します。
| Tips4_1_1 |
package kx; |
| Tips4_1_2 |
package kx; |
コードを見ると分かるように、Tips4_1_1は、内部でTips4_1_2のインスタンスを生成し、メソッドを呼び出しているので、Tips4_1_1がTips4_1_2を必要としていることが分かります(クラス図を図1に示します)。
![]() |
| 図1 クラス図 |
では、これらのクラスをJ2SE5.0でコンパイルしてみましょう。これらのクラスには特に文法的な誤りはありませんので、コンパイルエラーなどは起きません。
次に、Tips4_1_1だけをJ2SE 1.4でコンパイルしてみてください(IDEなどで実行すると再現しないことがあるので、コマンドラインから実行することをお勧めします)。
すると、次のようなコンパイルエラーが表示されます(画面の様子は図2)。
kx\Tips4_1_1.java:11: kx.Tips4_1_2 にアクセスできません。 |
![]() |
| 図2 コンパイルエラーが発生した様子 |
つまり、Tips4_1_1.javaをコンパイルすると、そのクラス中でTips4_1_2を参照している(インスタンスの生成およびメソッド呼び出し)ので、コンパイラはTips4_1_2のクラスファイルを参照します。Tips4_1_2にはすでにコンパイル済みのクラスファイルが存在していますが、そのクラスファイルはJ2SE5.0でコンパイルしたものなので、コンパイラ自身のバージョン(J2SE1.4)よりも新しいバージョンです。
このような場合、コンパイラはTips4_1_2.javaをリコンパイルせずに、上記のようなコンパイルエラーを表示するのです(図3を参照)。![]() |
| 図3 コンパイルエラーが発生する様子。48.0は、J2SE1.4でコンパイルされたことを示すクラスファイルのバージョン番号、同じく49.0は、J2SE5.0でコンパイルされたことを示すクラスファイルのバージョン番号 |
このコンパイルエラーが出た場合には、以下のいずれかの方法で解消することができます。
- 新しいバージョンのクラスファイルを削除してから、再度コンパイルを行う
- 依存しているクラスも含めて一度にコンパイルを行う
- コンパイラ自身のバージョンを新しいバージョンに合わせたものに変える
| 「クラスファイルのバージョン番号xx.xは不正です。xx.xであるべきです」が表示されたら=以前コンパイルしたときのJavaのバージョンと、いまコンパイルしようとしているコンパイラのJavaのバージョンが異なっていないかチェックする。 |
| 「ClassCastException」で落ちてしまったら |
分類:ランタイムエラー
Javaでは、あらゆるクラスは暗黙のうちにjava.lang.Objectを継承している、というルールがあります。さらに、ポリモルフィズムといって、あるクラスの型がその親クラスであるかのように見せ掛けることもできるようになっています。この2つの性質を利用すると、型の違いに合わせていちいちメソッドを定義しなくても、あらゆるクラスのインスタンスを引数に受け取ったり、戻り値として呼び出し元に渡したりすることができます。
次の例を見てください。
Tips4_2_1 |
package kx; |
このクラスはsetObject、getObjectというgetter/setterを持っています。このgetter/setterによってアクセスされる値はObject型の変数なので、どのようなクラスのインスタンスも渡すことができ、また、受け取ることができます。ですが注意しなければならないのは、次のようなプログラムを書いた場合です。
Tips4_2_2 |
package kx; |
このプログラムでは、前述のTips4_2_1クラスのインスタンスに、setObjectメソッドでString型のオブジェクトを渡し、直後にgetObjectメソッドでそのオブジェクトを取り出しています。getObjectメソッドは戻り値がObject型なので、通常は取り出したオブジェクトはキャスト演算子によってキャスト(型変換)をしてから使います。このプログラムでも取り出したオブジェクトをキャストして変数に代入しています。このプログラムをコンパイルしても、コンパイルエラーにはなりませんが、実行すると、ClassCastExceptionが発生します(図4)。
![]() |
| 図4 ClassCastExceptionが発生した例 |
このプログラムでは、String型のオブジェクトをsetObjectメソッドで渡しているにもかかわらず、取り出したオブジェクトをInteger型に変換しようとしています。ClassCastExceptionは、そのオブジェクトと実際には継承関係がないクラスにキャストをしようとすると発生します。StringとIntegerは、共通の親を持つクラスではありますが、両者には直接の継承関係はありません(図5)。
![]() |
| 図5 StringとIntegerの継承関係 |
このようなケースは、ポリモルフィズムの性質を生かして汎用的な操作ができる半面、外部からはどんな型のオブジェクトが入っているか分かりにくいことが問題となります。このような問題に対する1つの解決策として、J2SE 5.0からはGenericsという機能が使えるようになりました。Genericsを使ってクラスを定義しておくと、クラスの変数やメソッドの引数、戻り値の型などを、インスタンスを生成するときに動的に決めることができるようになります。次の例を見てください。
Tips4_2_3 |
package kx; |
「T」というのが、Genericsの機能によって、後から決めることのできる型名(の代わり)です。このクラスを使って、先ほどと同じようなプログラムを作ってみます。
Tips4_2_4 |
package kx; |
Genericsの機能を使い、インスタンス生成時に「T」としていた部分をString型に設定しています。これによって、クラスTips4_2_3のgetter/setterでやりとりされるオブジェクトの型は、String型に限定されたことになります。そのため、getObjectメソッドで取り出したオブジェクトをInteger型にキャストしようとする記述は、コンパイル時にコンパイルエラーとなり(図6)、実行前に文法上の誤りとして修正することが可能になります。
![]() |
| 図6 Genericsを使って型を指定した場合のコンパイルエラー |
| ClassCastExceptionの発生=継承関係にないクラスへキャストしようとしていないかを確認。Genericsを活用すれば実行前にコンパイルエラーとして検出させることもできる。 |
| 例外に相当する状況なのに例外が出ない |
分類:仕様どおり動かない
プログラムを実行して例外が発生するのは、正常な動作をしていないので望ましくない状況ではありますが、例外が発生すべき状況なのに例外が発生しないというのも、安全なプログラムの実行のためには望ましくない状況です。例えば、次のプログラムを見てください。
package kx; |
このクラスの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 { |
(2)メソッド定義部にユーザー定義の例外クラスを記述する。SQLExceptionやClassNotFoundExceptionをcatch節で捕捉したら、ユーザー定義の例外クラスのインスタンスを生成し、コンストラクタには捕捉した例外オブジェクトを渡してthrowする。
コードは次のようなものです。
| ユーザー定義の例外クラス(TipsException) |
package kx; |
変更されたgetNamesメソッド |
public static ArrayList<String> getNames() throws TipsException { |
(3)もし、(1)(2)どちらの対処もできない場合は、少なくともcatch節で例外を捕捉したときに、printStackTrace()メソッドで、例外のトレースは出力しておく。
そうすれば、例外発生時には気が付かなかったとしても、後でログを確認することで例外の状況を知ることができるためです。
| 例外に相当する状況なのに例外が出ない=例外をcatchしているのに、何も対処をしていない個所がないかを調べる。 |
デバッグのヒント教えます バックナンバー
| Java Solution全記事一覧 |
TechTargetジャパン
- Scalaのパッケージ、アクセス修飾子、オブジェクト継承 (2012/5/22)
インポート、パッケージオブジェクト、抽象クラス/抽象メソッド、オーバーライド、final、シールドクラスなども - 基幹系システムでCloud SQLは使えるか試してみた (2012/5/17)
サンプルとしてMRPシステムを作成して動かし、「再帰呼び出し」などのパフォーマンスを測定して検証してみます - アジャイル管理ツール9選+Pivotal Tracker入門 (2012/5/14)
群雄割拠のアジャイルプロジェクト管理ツールを9つ紹介し、特に注目を集めているPivotal Trackerの基本的な使い方を解説します - サーバサイドJSやJavaでWebアプリが作れるXPages (2012/5/11)
Notes/Dominoの資産をサーバサイドJavaScriptやJavaで操作し、HTMLやJavaScript、CSSをUIにできる技術を紹介
|
|
キャリアアップ
スポンサーからのお知らせ
- - PR -
イベントカレンダー
- - PR -






