この記事では、Eclipse JDT (Java™ Development Tools) で使用できる各種のリファクタリング機能を、それぞれのリファクタリングの内容、使用する場面、そして使用方法と併せて説明します。さらに、ライブラリー開発者がコードのリファクタリングをクライアントと共有することを可能にする Eclipse のリファクタリング・スクリプト機能についても取り上げます。

Prashant Deva, Founder, Chronon Systems

Prashant DevaPrashant Deva は Chronon Systems の創立者であり、この会社で Java では初の、実行時間の前後に移動できるデバッガーを作成しました。また、Eclipse 関連のコンサルティングも行っています。以前は Java 開発ツールの作成と販売に携わっていました。


developerWorks 貢献著者レベル

2009年 11月 24日

Eclipse はそのリファクタリング機能によって、通常のテキスト・エディターとは一線を画する現代の Java 統合開発環境 (IDE) となっています。リファクタリング機能を利用すれば、コードの別の部分を壊してしまうことを心配せずに、簡単にコードを変更することができます。また、コードが機能するかどうかを確かめるために、コードの見た目は気にせずにコードを作成し、機能することを確認したら、リファクタリング・ツールを使って素早く簡単に、簡潔かつ極めてモジュール化されたコードに変換することができます。この記事では、Eclipse で使用できるこの強力なリファクタリング機能について、その使用方法と併せて説明します。

さまざまなリファクタリングの種類

名前変更

「名前変更」は、おそらく Eclipse で最も頻繁に使用されているリファクタリングでしょう。このリファクタリングでは、変数、クラス、メソッド、パッケージ、フォルダー、そしてほとんどすべての Java 識別子の名前を変更することができます。識別子を変更すると、その識別子へのすべての参照についても同じく名前変更が行われます。「名前変更」リファクタリングを呼び出すためのショートカットは Alt+Shift+R です。Eclipse エディターで識別子に対してこのショートカットを呼び出すと、エディターの内側に小さなボックスが表示され、そこで識別子の名前を変更することができます。変更した後に Enter を押すと、その識別子への参照のすべてで名前が変更されます。

移動

「移動」は、あるパッケージのクラスを別のパッケージに移動するために使用します。このリファクタリングではクラスを別のパッケージに対応するフォルダーに物理的に移動するとともに、移動したクラスへの参照のすべてが新しいパッケージを参照するように変更されます。

クラスを移動するには、「パッケージ・エクスプローラー」ビューで、クラスを移動先の新しいパッケージにドラッグ・アンド・ドロップします。すると、リファクタリングが自動的に行われます。

ローカル変数の抽出

「ローカル変数の抽出」は、Java 式の結果を新しいローカル変数に割り当てるためのリファクタリングです。このリファクタリングを使用すれば、複雑な Java 式を複数の行に素早く分割して単純化することができます。また、コードを編集しているときには、最初に式を入力し、それからこのリファクタリングを使用することで、自動的に新規ローカル変数を作成し、式の結果をその変数に割り当てることもできます。この方法は、変数の型が自動的に生成されるために戻り値の型が複雑になる場合に便利です。

このリファクタリングはエディターから起動することができます。変数に割り当てる式を入力した後、Ctrl+1 を押して「選択された式のすべての出現箇所をローカル変数への参照で置換」を選択します。すると、適切な型を持つ新しい変数が自動的に作成されます。

定数の抽出

「定数の抽出」リファクタリングでは、コード内の数値または文字列リテラルを静的な final フィールドに変換することができます。リファクタリングすると、クラス内で使用されている対象の数値または文字列リテラルは、それ自体を参照するのではなく、その静的フィールドを参照するようになります。この方法では、数値または文字列リテラルをコード全体でいちいち検索して置換することなく、1 箇所 (フィールドの値) で変更することができます。

このリファクタリングを使用するには、エディターで数値または文字列リテラルを選択してから Ctrl+1 を押し、「定数の抽出」を選択します。

ローカル変数をフィールドに変換

その名前からわかるように、「ローカル変数をフィールドに変換」リファクタリングはローカル変数を取り、そのローカル変数をクラスの private フィールドに変換します。変換後は、そのローカル変数へのすべての参照がこのフィールドを参照します。

このリファクタリングを使用するには、ローカル変数を選択してから Ctrl+1 を押し、「ローカル変数をフィールドに変換」を選択します。

匿名クラスをネストに変換

「匿名クラスをネストに変換」リファクタリングは、匿名クラスを取り、その匿名クラスをそのクラスが元々含まれていたメソッドにネストされたクラスへと変換します。

このリファクタリングを使用するには、カーソルを匿名クラスの内側に置き、メニューから「リファクタリング」 > 「匿名クラスをネストに変換」の順に選択します。すると、新規クラスの名前を入力するよう求めるダイアログ・ボックスが表示されます。このダイアログ・ボックスでは、このクラスへのアクセスを「public」、「protected」、「private」、または「デフォルト」のいずれかに指定するなど、クラスのプロパティーも設定することができます。さらに、クラスを final、static、またはこの両方の組み合わせに指定することも可能です。

一例として、リスト 1 に匿名クラスを使用してスレッド・ファクトリーを作成するコードを記載します。

リスト 1. 「匿名クラスをネストに変換」リファクタリングを実行する前
void createPool() {
	threadPool = Executors.newFixedThreadPool(1, new ThreadFactory()
		{

			@Override
			public Thread newThread(Runnable r)
			{
				Thread t = new Thread(r);
				t.setName("Worker thread");
				t.setPriority(Thread.MIN_PRIORITY);
				t.setDaemon(true);
				return t;
			}

		});
}

リスト 1 のコードは、匿名クラスをインナー・クラスとして独立させると遥かに簡潔になるはずです。そこで、新規クラスに「MyThreadFactory」という名前を指定して、「匿名クラスをネストに変換」リファクタリングを実行してみます。リスト 2 に記載するリファクタリング後のコードは、ご覧のとおり、かなり簡潔になっています。

リスト 2. 「匿名クラスをネストに変換」リファクタリングを実行した後
private final class MyThreadFactory implements ThreadFactory
	{
		@Override
		public Thread newThread(Runnable r)
		{
			Thread t = new Thread(r);
			t.setName("Worker thread");
			t.setPriority(Thread.MIN_PRIORITY);
			t.setDaemon(true);
			return t;
		}
	}
void createPool(){
	threadPool = Executors.newFixedThreadPool(1, new MyThreadFactory());
}

メンバー・タイプをトップ・レベルに変換

「メンバー・タイプをトップ・レベルに変換」リファクタリングは、ネストされたクラスを取り、そのクラスを固有の Java ファイルを持つトップ・レベルのクラスに変換します。

このリファクタリングを使用するには、カーソルをネストされたクラスの内側に置き、「リファクタリング」 > 「メンバー・タイプをトップ・レベルに変換」の順に選択します。ネストされたクラスが静的クラスであれば、この操作によってすぐに、リファクタリングのプレビューを表示するボックスが開きます。静的クラスでない場合、ネストされたクラスの親クラスへの参照を保持するフィールドの名前を宣言してからでないと、プレビュー・ボックスは表示されません。このボックスでは、フィールドを final として宣言することもできます。

インターフェースの抽出

「インターフェースの抽出」リファクタリングは、クラスに定義されたメソッドからインターフェースを抽出します。

このリファクタリングを使用するには、メニューから「リファクタリング」 > 「インターフェースの抽出」の順に選択します。この操作により、新規インターフェース名の入力を求めるダイアログ・ボックスが表示されます。このダイアログ・ボックスでは、新規インターフェースで宣言するクラスのメソッドにチェック・マークを付けたり、クラスへの有効な参照をすべてインターフェースへの参照に変換するように設定したりすることもできます。注意する点として、このリファクタリングはクラスへの有効な参照のみを、新規インターフェースへの参照に変換します。つまり、クラスのなかのあるメソッドをインターフェースに組み込まないことにした場合、Eclipse はクラスへの参照がそのメソッドを使用しているのを検出すると、その参照に関しては新規インターフェースへの参照に変換しないということです。したがって、クラスへの参照のすべてが新規インターフェースへの参照に変更されるものと勘違いしないようにしてください。

スーパークラスの抽出

「スーパークラスの抽出」リファクタリングは、前述の「インターフェースの抽出」リファクタリングと似ていますが、このリファクタリングが抽出するのはインターフェースではなく、スーパークラスです。クラスがすでにスーパークラスを使用している場合には、そのクラスをスーパークラスとして持つスーパークラスが新しく生成されるため、クラス階層が維持されます。

このリファクタリングを使用するには、対象のクラスのメソッド宣言またはフィールドのいずれかにカーソルが置かれていることを確認してから、「リファクタリング」 > 「インターフェースの抽出」の順に選択します。「インターフェースの抽出」ダイアログ・ボックスと同じようなダイアログ・ボックスが表示されるので、そこに新規スーパークラスの名前を入力し、そのスーパークラスに組み込むメソッドとフィールドを選択することができます。

スーパークラスの抽出とインターフェースの抽出とで大きく異なる点は、スーパークラスに組み込まれるメソッドは、実際にスーパークラスに移動されることです。したがって、移動されたメソッドのなかに元のクラスのフィールドへの参照が 1 つでもあると、スーパークラスにはそれが見えないため、コンパイラーがエラーを出力する結果になります。このような場合に対する最善策は、メソッドが参照する元のクラスのフィールドも、同じくスーパークラスに移動することです。

メソッドの抽出

「メソッドの抽出」リファクタリングでは、コードのブロックを選択し、それをメソッドに変換することができます。メソッドの引数と戻り値の型は、Eclipse が自動的に推測します。

このリファクタリングが役立つのは、大きすぎるメソッドを複数のブロックに分割し、そのそれぞれを独立したメソッドにする場合です。また、多数のメソッドで同じコードが何度も使用されている場合にも役立ちます。再利用されているコード・ブロックのいずれか 1 つを選択してリファクタリングすると、Eclipse がそのコード・ブロックが使われている他の箇所も探し出して、新規メソッドへの呼び出しに置き換えてくれます。

このリファクタリングを使用するには、コード・ブロックを選択し、Alt+Shift+M を押します。これによって表示されたダイアログ・ボックスで、新規メソッドの名前を入力し、可視性 (「public」、「private」、「protected」、または「デフォルト」) を選択します。さらに、引数や戻り値の型を変更することもできます。選択されたコード・ブロックで新規メソッドが作成されると、そのブロック内でも新規メソッドの引数と戻り値が適切に使用されるようにリファクタリングが行われます。これにより、リファクタリングの元となったメソッドには新規メソッドの呼び出しが組み込まれます。

例えば、リスト 3 の map.get() 呼び出し後のコード・ブロックを独立したメソッドとして移動させたいとします。

リスト 3. 「メソッドの抽出」リファクタリングを実行する前
@Override
	public Object get(Object key)
	{
		TimedKey timedKey = new TimedKey(System.currentTimeMillis(), key);
		Object object = map.get(timedKey);

		if (object != null)
		{
			/** 
			 * if this was removed after the 'get' call by the worker thread
			 * put it back in
			 */
			map.put(timedKey, object);
			return object;
		}

		return null;
	}

それにはエディターで対象のコード・ブロックを選択し、Alt+Shift+M を押します。新規メソッドの名前は、「putIfNotNull」に設定します。すると、Eclipse は自動的に正しい引数と戻り値を突き止めて、リスト 4 のコードを生成します。

リスト 4. 「メソッドの抽出」リファクタリングを実行した後
@Override
	public Object get(Object key)
	{
		TimedKey timedKey = new TimedKey(System.currentTimeMillis(), key);
		Object object = map.get(timedKey);

		return putIfNotNull(timedKey, object);
	}

	private Object putIfNotNull(TimedKey timedKey, Object object)
	{
		if (object != null)
		{
			/** 
			 * if this was removed after the 'get' call by the worker thread
			 * put it back in
			 */
			map.put(timedKey, object);
			return object;
		}

		return null;
	}

インライン化

「インライン化」リファクタリングでは、変数またはメソッドへの参照をインライン化することができます。このリファクタリングを使用すると、変数への参照あるいはメソッドへの参照が、それぞれ変数に割り当てられた値、メソッドの実装に置き換えられます。以下のような場合には、このリファクタリングがコードのクリーンアップに役立ちます。

  • 別のメソッドによって 1 回だけ呼び出されるメソッドであるため、コードのブロックとして使用したほうが妥当な場合
  • 式を複数の行に分割してさまざまな変数に値を割り当てるよりも、1 行の式としてまとめたほうが簡潔に見える場合

このリファクタリングを使用するには、カーソルを変数またはメソッドに置き、Alt+Shift+I を押します。すると、リファクタリングの確認を求めるダイアログ・ボックスが表示されます。リファクタリング対象がメソッドの場合は、このダイアログ・ボックスに、リファクタリング実行後にメソッドを削除するためのオプションも表示されます。

例えばリスト 5 の 2 番目の行は、単に式の値を timedKey 変数に割り当てるだけに過ぎません。

リスト 5. 「インライン化」リファクタリングを実行する前
public Object put(Object key, Object value)
	{
		TimedKey timedKey = new TimedKey(System.currentTimeMillis(), key);
		return map.put(timedKey, value);
	}

リスト 6 に、「インライン化」リファクタリングを実行した後のコードを記載します。以前は 2 行だったコードが、1 行のコードに簡潔に収まっていることに注目してください。

リスト 6. 「インライン化」リファクタリングを実行した後
@Override
	public Object put(Object key, Object value)
	{
		return map.put(new TimedKey(System.currentTimeMillis(), key), value);
	}

メソッド・シグニチャーの変更

「メソッド・シグニチャーの変更」リファクタリングでは、メソッドのシグニチャーを変更することができます。このリファクタリングは、そのメソッドへの呼び出しすべてが新しいシグニチャーを使用するように変更します。

このリファクタリングを使用するには、「リファクタリング」 > 「メソッド・シグニチャーの変更」の順に選択します。これによって表示されるのが、図 1 に示すダイアログ・ボックスです。このダイアログ・ボックスでは、パラメーターの追加または除去、パラメーターの順序の変更、戻り値の型の変更、メソッド宣言への例外の追加、さらにはメソッド自体の名前の変更も含め、メソッドに関するあらゆる内容を変更することができます。

図 1. 「メソッド・シグニチャーの変更」ダイアログ・ボックス
図 1 には、パラメーターの追加や除去、パラメーターの順序の変更など、メソッドに関するあらゆる内容を変更可能なダイアログ・ボックスが示されています。

例えばパラメーターの追加や戻りの型の変更など、メソッドに対する一部の変更では、新規パラメーターに何が入力されるのかを Eclipse が認識しないために、リファクタリングされたコードにはコンパイラーによるエラーを含んだ状態となる場合があることに注意してください。

総称型引数の推測

「総称型引数の推測」リファクタリングは、加工されていない形で使用されているクラスに対し、そのクラスに適した総称型を自動的に推測しようとします。このリファクタリングは一般に、Java 5 より前のコードを Java 5 以降のコードに変換する際に使用されます。

このリファクタリングは、「パッケージ・エクスプローラー」から呼び出すこともできます。その方法は単に、「パッケージ・エクスプローラー」で任意のプロジェクト、パッケージ、またはクラスを右クリックして、「リファクタリング」 > 「総称型引数の推測」の順に選択するだけです。

リスト 7 のコードには総称型引数を取ることのできる ConcurrentHashMap がありますが、このコードでは型のパラメーターが指定されていません。

リスト 7. 「総称型引数の推測」リファクタリングを実行する前
private final ConcurrentHashMap map = new ConcurrentHashMap();

「総称型引数の推測」リファクタリングを実行すると、Eclipse が自動的に正しい型のパラメーターを判別し、リスト 8 に記載するコードを生成します。

リスト 8. 「総称型引数の推測」リファクタリングを実行した後
private final ConcurrentHashMap<TimedKey, Object> map = 
     new ConcurrentHashMap<TimedKey, Object>();

JAR ファイルのマイグレーション

「JAR ファイルのマイグレーション」リファクタリングでは、プロジェクトのビルド・パスの JAR (Java Archive) ファイルを簡単にアップグレードすることができます。通常、ビルド・パスの JAR ファイルを新しいバージョンにアップグレードするには以下の作業を行わなければなりません。

  1. プロジェクトのプロパティーに進み、既存の JAR ファイルをビルド・パスから除去します。
  2. JAR ファイルをそのファイルが含まれているフォルダーから手動で削除します。
  3. 新しい JAR ファイルをコピーし、ファイル名を、すべてのビルド・スクリプトで参照される名前を反映するように変更します。
  4. 新規 JAR ファイルを手動でビルド・パスに追加します。

「JAR ファイルのマイグレーション」リファクタリングを使用すれば、上記の作業をまとめて 1 つのステップで実行することができます。このリファクタリングを起動するには、「リファクタリング」 > 「JAR ファイルのマイグレーション」の順に選択します。表示されたダイアログ・ボックスで、新規 JAR ファイルの場所を選択し、プロジェクトの下にあるツリーから新しいバージョンにアップグレードする JAR を選択します。「Jar ファイルのコンテンツを置換するが、既存のファイル名を保存する」チェック・ボックスを選択すると、新規 JAR ファイルの名前が古い JAR ファイルと同じ名前になるように変更されるので、JAR ファイルを古い名前で参照するビルド・スクリプトが壊れることがありません。いずれにしても、「完了」をクリックすると、前の JAR ファイルが削除され、その場所に新しい JAR ファイルがコピーされて自動的にプロジェクトのビルド・パスに追加されるため、プロジェクトがこの新しい JAR ファイルを使用するようになります。

リファクタリング・スクリプト

リファクタリング・スクリプトを使用することによって、リファクタリング・アクションをエクスポートし、共有することができます。この方法は、新しいバージョンのライブラリーを配布する際にとても役に立つもので、古いバージョンを使用しているユーザーが新しいバージョンを適用する際に誤った操作をする可能性が考えられる場合に極めて有効です。新しいバージョンのライブラリーと併せてリファクタリング・スクリプトを配布することで、古いバージョンのライブラリーを使用しているユーザーは自分のプロジェクトにスクリプトを適用するだけで、コードが新規バージョンのライブラリーを使用するようにできるからです。

リファクタリング・スクリプトを作成するには、「リファクタリング」 > 「スクリプトの作成」の順に選択します。これによって表示される図 2 のウィンドウには、これまでワークスペースで行われたすべてのリファクタリングの履歴が示されます。そのなかから対象のリファクタリングを選択し、スクリプトを生成する場所を指定します。これで「作成」をクリックすると、スクリプトが生成されます。

図 2. 「スクリプトの作成」ウィンドウ
図 2 のウィンドウには、これまでワークスペースで行われたすべてのリファクタリングの履歴が示されています。

既存のリファクタリング・スクリプトをワークスペースに適用するには、「リファクタリング」 > 「スクリプトの適用」の順に選択します。表示されたダイアログ・ボックスで、スクリプトの場所を選択します。「次へ」をクリックすると、このスクリプトが実行するリファクタリングが表示されるので、これらのリファクタリングを適用するには「完了」をクリックします。

一例として、JAR ファイルのバージョン 2 で、com.A クラスが com.B という名前に変更されたとします。バージョン 1 の JAR ファイルを使用しているユーザーのコードでは com.A が参照されているため、単に新しいバージョンのライブラリーにアップグレードしただけでは、既存のコードが壊れてしまいます。けれどもリファクタリング・スクリプトを JAR ファイルと一緒に配布すれば、このスクリプトによって自動的に com.A クラスへの参照は com.B クラスへの参照に名前変更されるため、ユーザーは簡単に新規バージョンの JAR ファイルにアップグレードできるというわけです。

まとめ

Eclipse で使用できるさまざまなリファクタリングによって、醜いコードを美しく芸術的なコードに簡単に変身させることができます。さらにリファクタリング・スクリプトを利用すれば、コードが壊れている原因を突き止めるためにクライアントが何時間もドキュメントを調べなければならないといった事態を心配することなく、簡単にアプリケーションをアップグレードすることができます。このようなリファクタリング機能の数々がまさに、Eclipse を他のテキスト・エディターや IDE より一段と優れた IDE にしています。

参考文献

学ぶために

製品や技術を入手するために

議論するために

  • Eclipse に関する質問を投じる最初の場所として、Eclipse Platform newsgroups があります (このリンクをクリックすると、デフォルト Usenet ニュース・リーダー・アプリケーションが起動され、eclipse.platform が開きます)。
  • Eclipse newsgroups には Eclipse を利用し、拡張することに関心を持つ人達のために、さまざまなリソースが用意されています。
  • developerWorks blogs から developerWorks コミュニティーに加わってください。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source
ArticleID=458804
ArticleTitle=Eclipse JDT のリファクタリング機能を探る
publish-date=11242009