ゆーたんのつぶやき

株式会社ノークリサーチにてIT関連のシニアアナリストとして活動しています。

内部クラス



Javaの内部クラスは基本的かつ古くからある言語仕様(実際には
内部クラスの仕組みを実現しているのはJVMではなくコンパイラ)
ですが、混乱しやすいのでちょっとまとめてみました。


定義場所<名称>
入れ子元クラスのクラス定義内(static付)staticな入れ子クラス
入れ子元クラスのクラス定義内(static無)内部クラス
入れ子元クラスのメソッド定義内内部クラス(無名クラスが一般的)
あるクラス定義の中で定義されているクラスは「入れ子クラス(Nested Class)」と呼びますが
その中でも特に「親クラスのクラス定義の中で定義され、staticでないもの」と「親クラスの
メソッド定義の中で定義されているもの(無名クラスであることが多い)」の2つを内部クラスと
呼びます。表の中の一番目のクラスも内部クラスっぽいのですが、厳密にはこれは内部クラス
とは呼ばず、一般的なクラスと同じもの(トップレベルクラス)と分類されています。ときどき
「staticな内部クラス」という表現をされることがありますが、厳密な定義では間違いという
ことになります。とは言え、適切な用途がわかっていさえいれば細かい呼称はそれほど問題で
はないので、あまり神経質になることもないかなと思います。


一般的な内部クラスの用途はスタックを実装したりといったようにあるクラスの中に別のクラス
インスタンスを含ませたいけれども、入れ子になったクラス側からは入れ子元クラスに対して
フリーにアクセスさせたいというケースかと思います。クラス内の実装をきちんと部品化するた
めには欠かせないコーディング作法ではありますが、うっかりすると下記のようなコードを書い
てしまうこともあります。

public class Parent {

class Child{}

public static void createChild(){
Child b = new Child();
}
}

内部クラスのインスタンスを生成するメソッドをstatic定義するというのはいろいろとやって
いるうちに思わず書いてしまいそうなコードですが、内部クラスのインスタンス入れ子元の
インスタンスがあって初めてそれにくっつく形で存在できます。ですので、上記のコードでは
コンパイルエラーとなります。このあたりを気をつけないといけないですね。


メソッド内で定義する内部クラスはもっぱら無名クラスとして書かれることが多いでしょう。
メソッド定義のブロック内でのみ有効ですので参照の必要性がさほどありません。下記のように
GUIのアクションリスナーのハンドラ実装として使われることが多いかと思います。

Display display = new Display();
Shell shell = new Shell(display);
Button button = new Button(shell, SWT.PUSH);
button.setText("押しボタン");
button.addSelectionListener( new SelectionAdapter(){
public void widgetSelected(SelectionEvent event){
//ボタンが押された時の処理をここに書く
}
});
static付きの入れ子クラスですが、以外と便利なのがComparatorのような特定の処理用に用意
されたインタフェースの実装をするような場合です。Comparatorインターフェースの実装は
比較対象となるクラスとは別に定義することもできますが、比較を行なうcompare()メソッドの
中では当然ながら比較対象クラスのフィールドへアクセスする必要があります。Comparator実装
を比較対象クラスのstaticな入れ子クラスにしておけば、そのあたりの実装が楽になります。
Comparator実装は比較対象クラスのインスタンス毎に個々に必要になるわけでもないので、
こうした用途にはまさにstatic付きの入れ子クラスがぴったりなわけです。
コード例を書いてみます。

public class Employee {

private int age;
private String name;

public static void main(String[] args){
Employee[] EmpList = new Employee[3];
EmpList[0] = new Employee("山田", 25);
EmpList[1] = new Employee("田中", 45);
EmpList[2] = new Employee("山本", 30);

Arrays.sort(EmpList, new Employee.Comparator());

for(int i=0; i<3; i++){
System.out.println(EmpList[i]);
}
}

public Employee(String name, int age){
this.name = name;
this.age = age;
}

public String toString(){
return name+"("+age+")";
}

static class Comparator implements java.util.Comparator{
public int compare(Object left, Object right) throws ClassCastException{

Integer leftAge = new Integer(((Employee)left).age);
Integer rightAge = new Integer(((Employee)right).age);
return leftAge.compareTo(rightAge);
}
}
}

このようにEmployeeクラス定義の中にComparator実装も一緒にいれておけば、
Employeeクラスに対してリファクタリングをかけたりした場合でも一緒に修正
をかけることができるので安全ではないかと思います。ただし、Comparatorの
ような特殊なインターフェースであるからこそ適用可能なパターンなので注意
が必要です。例えば、Hibernateを使ってO/Rマッピングをするような場合に
永続化対象クラスの中にデータアクセス操作に関連するメソッドまで含めてし
まうのは避けるべきかと思います。永続化対象クラスはシンプルなPOJOにして
おいて、データアクセス操作は別途DAOクラスを作成する方が望ましいでしょう。


ここのところ、ビジネスネタが続いたので今日はちょっとオーソドックスな
コーディングネタを書いてみました(^^)