作成済みのクラス名を書き換えたらエラーになったデバッグのヒント教えます(6)

» 2006年07月27日 00時00分 公開
[中越智哉ナレッジエックス]

 7月も後半に入ってくると、だんだん、平日の電車の中で子供の姿を見かけることも多くなってくるのではないでしょうか。どうしてこんな時間から子供が電車に? と思ってよく考えてみると、学校は7月末から夏休みなんですね。そういえば、子供の頃は夏の暑い時期に1ヶ月近くも休みがあったんだなぁ、と思うと懐かしくもあり、今となっては信じられないような生活です。子供に戻りたい! なんて思ってしまう人もいるのではないでしょうか。そんな子供たちの暮らしを支える私たちエンジニアは、とりあえず8月のお盆休みを励みに、目前のデバッグに熱く取り組みましょう。では今月もどうぞお付き合いください。

作成済みのソースのクラス名やパッケージ名を書き換えたらコンパイルエラーになった

分類:コンパイルエラー

 プログラムを作成していると、当初の要件が変更されたりして、設計を見直す必要が出てくることもよくあると思います。設計が変更された結果、すでに作成されているクラスのクラス名が変更になったり、所属するパッケージ名が変更になったりすることもあるでしょう。

 EclipseなどのIDE(統合開発環境)を利用している場合、クラスの作成はウィザード(図1)で簡単に行うことができますが、クラス名やパッケージ名が変更される場合は、注意が必要です。

図1 Eclipseのクラス作成ウィザード 図1 Eclipseのクラス作成ウィザード

 例えば、すでに作成済みのクラスのソースを直接書き換えて、クラス名を変更したり(図2)、パッケージ名を変更したり(図3)するとコンパイルエラーになってしまいます(図4図5)。なぜこのようなコンパイルエラーになるのでしょうか。

図2 作成済みクラスの名称を「Tips3_5」から「Sample3_5」に変更 図2 作成済みクラスの名称を「Tips3_5」から「Sample3_5」に変更
図3 作成済みクラスのパッケージ名を「kx」から「atmarkit」に変更 図3 作成済みクラスのパッケージ名を「kx」から「atmarkit」に変更
図4 クラス名を変更した場合のコンパイルエラー 図4 クラス名を変更した場合のコンパイルエラー
図5 パッケージ名を変更した場合のコンパイルエラー 図5 パッケージ名を変更した場合のコンパイルエラー

 まずクラス名の変更についてです。Javaの文法では、(内部クラスや匿名クラスなどを除いて)ソースファイル中に定義されているクラスのクラス名と、ソースファイルのファイル名は一致していないといけないルールになっています。ところがEclipseのソースエディター上でクラス名を書き換えただけでは、ソースファイルのファイル名までは変更されません(図6)。そのため、コンパイルエラーとなってしまいます。

図6 ソース上でクラス名を書き換えても、ファイル名は変更されない 図6 ソース上でクラス名を書き換えても、ファイル名は変更されない

 パッケージ名の変更についても同様です。Javaの文法ではパッケージ宣言でパッケージ名を指定した場合、ソースファイルやクラスファイルの配置場所がパッケージ名を基にしたディレクトリ構造になります。つまり「atmarkit」というパッケージ名を指定した場合、そのソースファイルは「atmarkit」という名称のディレクトリに配置されていないといけませんが、Eclipseのソースエディター上でパッケージ名を書き換えただけでは、ソースファイルの配置場所の移動は行われないために、コンパイルエラーとなります。

 というわけで、これらのコンパイルエラーを解消するためには、ソースファイル名を変更したり、ソースファイルの配置場所を正しく移動すればよいことになります。Windowsの場合、エクスプローラーを使って変更することももちろん可能ですが、Eclipseを使ってこのような変更を行うこともできます。Eclipseの「リファクタリング」という機能を使えば、クラス名やパッケージ名の変更を行う場合に、必要なファイル名の変更や配置場所の変更をEclipseが行ってくれますので非常に便利です。

 リファクタリング機能を使うには、変更したいと思っているクラス名、パッケージ名(そのほかにもメソッド名や変数名に対しても使えます)を右クリックします。次にポップアップメニューの中に「リファクタリング」という項目がありますので選択し、さらに「名前変更」を選択してください。すると「型の名前変更」ダイアログが表示されます(図7)。

図7 「型の名前変更」ダイアログ 図7 「型の名前変更」ダイアログ

 「新しい名前」という項目に、変更後の新しい名称を入力し、「OK」ボタンをクリックします。クリック後、確認のためのダイアログが表示されますが、ここではそのまま「継続」ボタンをクリックしてください。すると、Eclipseがクラス名とそのソースファイル名をともに変更してくれますので、コンパイルエラーが発生しません(図8)。

図8 エディタウィンドウの上部(ファイル名)も正しく変更されている 図8 エディタウィンドウの上部(ファイル名)も正しく変更されている

 パッケージ名をリファクタリング機能で変更する場合は、同じようにクラス名の部分で右クリックし「リファクタリング」→「移動」と選択すると、移動先のパッケージを選択することができます。この操作でパッケージ名を変更すれば、名称と配置場所が正しく修正・移動されます。

 さらに、Eclipseのリファクタリング機能では、名称を変更したクラスやメソッドがほかのクラスで参照されている場合に、ほかのクラスに書かれている名称も一緒に変更してくれるようになっています。ですから、すでにあるクラスがほかの多数のクラスから利用されている場合でも、元のクラス(のクラス名やパッケージ名など)を変更するだけで、他のクラスをいちいち修正していく必要がありませんので、実装後にこういった設計上の変更を行いたい場合にとても便利です。

クラス名やパッケージ名の変更=
(1)ソースファイル名や、ファイルの配置場所についても併せて変更する
(2)Eclipseでは「リファクタリング」機能を使うとそれらの操作を省略できる


「ClassNotFoundException」が発生したら

分類:ランタイムエラー

 システム開発をしていく中では、自分のマシンで動いたコードやモジュールを別の環境に移して実行するとうまく動かない、ということがあります。原因は、もちろんその状況によって異なりますが、よく見られる原因の1つとして、モジュールの構成や環境設定などが自分のマシンと同一になっていないことが挙げられます。「ClassNotFoundException」や別の項で紹介する「NoClassDefFoundError」、「NoSuchMethodException」は、まさにそういった原因で発生します。

 まず「ClassNotFoundException」ですが、JDKのドキュメントによれば、アプリケーションがクラスの文字列名を使用して次のメソッドでロードしようとしたが、指定された名前のクラスの定義が見つからなかった場合にスローされます。

  • Class クラスの forName メソッド
  • ClassLoader クラスの findSystemClass メソッド
  • ClassLoader クラスの loadClass メソッド

 とあります。ここに説明されている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)指定しているクラス名の記述が間違っている


「NoClassDefFoundError」「NoSuchMethodException」が発生したら

 「NoClassDefFoundError」「NoSuchMethodError」についても「ClassNotFoundException」と同じことがいえます(前項を参照)。すなわち、開発時には問題なく動いていたのに、実行環境になると動かない、というパターンです。「NoClassDefFoundError」や、「NoSuchMethodError」は、主に(=開発者が通常目にするところでは)実行時に発生します。

 「ClassNotFoundException」と「NoClassDefFoundError」は名称がよく似ていますが、違いは何なのでしょうか。「ClassNotFoundException」は、ClassクラスのforNameメソッドのように、クラス名を文字列で表現する場合に起きる例外ですが、「NoClassDefFoundError」は、通常どおりに表記したクラス名について、そのクラスが見つからない場合に起きる例外です。ですが、通常どおりクラス名を表記してそのクラス名の記述が間違っているとか、クラスが実際に存在していなければ、そのプログラムをコンパイルするときにコンパイルエラーになるはずです。

 コンパイル時にエラーにならず、開発環境(自分が開発しているマシン)では動作したのに実行環境(実際にシステムを稼働させるマシン)では動作しないというようなことがあるとすれば、考えられる理由は限られます。つまり、開発環境には含まれていたクラスが、実行環境には存在していないということです。その原因としては、次のようなパターンが考えられます。

  1. 指定されているクラスのクラスファイルを開発環境から実行環境に移していない(クラスファイルの不足)
  2. 指定されているクラスのクラスファイルは実行環境のマシンに存在はしているものの、CLASSPATHにそのクラスファイルのある場所を登録していない
  3. 指定されているクラスを含むJARファイルを開発環境から実行環境に移していない(JARファイルの不足)
  4. 指定されているクラスを含むJARファイルは実行環境のマシンに存在はしているものの、CLASSPATHにそのファイルの場所を登録していない
  5. 指定されているクラスを含むJARファイルのバージョンが、開発環境と実行環境で異なっているために、開発環境側のJARにはそのクラスが含まれるが、実行環境側のJARにはそのクラスが含まれていない

 では、「NoSuchMethodError」はどのようなときに発生するのでしょうか。こちらは、クラスファイルは存在しているものの、指定されたメソッドが存在していない(もしくは引数が違う)場合に発生します。開発時にコンパイルエラーにならず、実行時にNoSuchMethodErrorになってしまう場合、原因としては次のようなものが考えられます。

  1. 実行環境に存在しているクラスが、開発環境にある最新のクラスファイルでないために、最近追加された(もしくは引数の変更された)メソッドが存在していない
  2. 指定されているクラスを含むJARファイルのバージョンが、開発環境と実行環境で異なっているために、開発環境側のJARに含まれるクラスと、実行環境側のJARに含まれるクラスで、メソッドの定義内容が異なっている

 Javaに限らず、システム開発では自分たちですべてのプログラムを書かずに、ある機能については外部で作成されたライブラリで実現する、というようなケースが多くあります。その場合、ライブラリのバージョンが異なっていると、前のバージョンでは存在していたクラスやメソッドが新しいバージョンではなくなっていたり、メソッドに渡す引数が変更されてしまっている場合などがあります。開発環境と実行環境でバージョンの異なるライブラリを使って動作させるのは非常に危険が伴いますので特に理由がない限りは原則として避け、必ず同じバージョンのライブラリを使いましょう。

「NoClassDefFoundError」「NoSuchMethodError」の発生=開発時と実行時で、クラスの構成や利用しているライブラリのバージョンが一致しているかを確認



Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

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

メールマガジン登録

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