[???] /
[Java FAQ] / [S016]
S016: 内部クラス - inner class
[S016 Q-01]
インタフェースやクラスの中にインタフェースやクラスがあるのですがこれは何ですか?
[S016 A-01]
ネストした型と呼ばれるものです。
ネストした型とは、他のクラスや他のインタフェース、及びブロック内に宣言された
クラス及びインタフェースのことです。
それぞれネストしたクラス(nested class)、 ネストしたインタフェース(nested interface)と
呼ばれています。
ネストした型は、メンバ型(S016-04)、ローカルクラス(S016-05)、
匿名クラス(S016-06)に分類されます。
また、ネストした型のうち、static修飾子を伴うメンバ型を除いたものを総称して
内部クラス(S016-02)と呼びます。
class TopLevelClass { // トップレベル型
static class StaticMemberClass {} // staticなメンバー型
class NonStaticMemberClass {} // 非staticなメンバー型
static {
class StaticInitializerLocalClass {} // ローカルクラス
}
{
class InstanceInitializerLocalClass {} // ローカルクラス
}
static void staticMethod() {
class StaticMethodLocalClass {} // ローカルクラス
Runnable staticAnonymousClassInstance =
new Runnable() { // 匿名クラス
public void run() {}
};
}
void method() {
class MethodLocalClass {} // ローカルクラス
Runnable anonymousClassInstance =
new Runnable() { // 匿名クラス
public void run() {}
};
}
}
これらの型の分類を示したのが以下の図です。
[型](注1)
│
├[トップレベル型]
│
└[ネストした型]
│
├[メンバ型]
│ │
│ ├[staticなもの]
│ │
│ └[staticでないもの]
│
├[ローカルクラス]
│
└[匿名クラス]
注1: 型にはプリミティブ型、配列型なども含まれるが、図では省略されている
[S016 Q-02]
new の後ろにメソッド宣言が並んでいるのですがこれは何ですか?
[S016 A-02]
匿名クラスと呼ばれるものです
匿名クラスについては S016-06 を参照してください。
[S016 Q-03]
内部クラス (inner class) とは何ですか?
[S016 A-03]
内部クラスとは、
他のクラスの中で宣言されるネストした型 (S016-01) のうち、
static 宣言されていないクラスのことです。
内部クラスには以下の制限があります。
・静的初期化子を宣言できない
・メンバ・インタフェースを宣言できない
・定数とならないstatic変数を宣言できない
なお、「内部クラス」という言葉は、現在の「ネストした型」の意味で使われることもあります。
これは、Java 1.1リリース時に内部クラス仕様(Inner Class Specification)が定められた時点では、
ネストした型全般を指す言葉として「内部クラス」が用いられていたためです。
[S016 Q-04]
メンバ型 (member type) とは何ですか?
[S016 A-04]
メンバ型とは、他のクラスや他のインタフェースの直接のメンバである
クラス及びインタフェースのことです。
メンバ型であるクラスはメンバクラス(member class)、
メンバ型であるインタフェースはメンバインタフェース(member interface)と
呼ばれています。
メンバインタフェースは static 宣言されていない場合でも
常に暗黙的に static として扱われます。
なお、ネストした型であっても、ローカルクラスや匿名クラスは、
他のクラスやインタフェースに直接囲まれている訳ではないので、
メンバ型ではありません。
[S016 Q-05]
ローカルクラス (local class) とは何ですか?
[S016 A-05]
ローカルクラスとは、ブロックに囲まれた名前を持ったクラスです。
ローカルクラスはネストした型(S016-01)であり、
内部クラス(S016-03)でもありますが、
メンバクラス(S016-04)ではありません。
ローカルクラスは、常に private であると見なされ、
public、 protected、private、static などの修飾子を宣言された場合
コンパイルエラーとなります。
[S016 Q-06]
匿名クラス (anonymous class) とは何ですか?
[S016 A-06]
匿名クラスとは、式内(クラス、インスタンス生成式内)に宣言される
名前を持たないクラスのことです。
別の言い方をすると、クラス、インスタンス生成式がクラス本体の記述を
伴っている場合、インスタンス化対象のクラスは匿名クラスということになります。
匿名クラスは、ネストした型 (S016-01) であって、
かつ、内部クラス (S016-03) でもあります。
インスタンス生成式内において
「new スーパークラス名() { クラス宣言 }」または
「new インタフェース名(){ クラス宣言 }」と記述することによって、
匿名クラスを生成することができます。
このとき、コンパイラによって生成される匿名クラスのクラスファイルは
「外側のクラス名$<数字>.class」という名前になります。
匿名クラスは、型に名前がないために、以下のような制限や特徴があります。
・コンストラクタを宣言できない
・サブクラスを宣言することができない
・abstractクラスとすることができない
・暗黙的に final となる
匿名クラスは、既存のクラスを部分的に変更したクラスを、
あるクラスの内部で局所的に使いたい場合などに用いられます。
以下に匿名クラスの例を示します。
(1)既存クラスの挙動を一部変更したクラスを生成する例
// Vectorのaddの挙動を変更する。
// Setと同じように、同一オブジェクトは一つしか含まれないようにする。
Vector vec = new Vector() {
public boolean add(Object o){
remove(o);
return super.add(o);
}
};
(2)インタフェースを匿名クラスで実装する例
// 点の集合を原点からの距離順に並べなおす
java.awt.Point[] array = ..;
final java.awt.Point origin = new java.awt.Point(0,0);
Arrays.sort(array, new Comparator() {
public int compare(Object a, Object b) {
java.awt.Point p1 = (java.awt.Point)a;
java.awt.Point p2 = (java.awt.Point)b;
double d1 = p1.distance(origin);
double d2 = p2.distance(origin);
int comp = 0;
if (d1 > d2) comp = 1;
else if (d1 < d2) comp = -1;
return comp;
}
});
[S016 Q-07]
ネストした型 (nested type) とは何ですか?
[S016 A-07]
他のクラスや他のインタフェース、及びブロック内に宣言された
クラス及びインタフェースのことです。
詳しくは、S016-01「インタフェースやクラスの中にインタフェースや
クラスがあるのですがこれは何ですか?」を参照してください。
[S016 Q-08]
トップレベルクラス (top-level class) とは何ですか?
[S016 A-08]
パッケージメンバである普通の(従来の)クラス、つまり、
ネストした型 (S016-01) 以外のクラスのことです。
内部クラスの機構が Java に取り入れられた Java 1.1 リリース当初では、
パッケージメンバである普通のクラスの他に、
ネストした型 (S016-01)の static 宣言されたものも
トップレベルクラスと呼ばれていましたが、
最新の言語仕様では、パッケージメンバとなる従来のクラスのみが
トップレベルクラスと呼ばれています。
[S016 Q-09]
ネストした型はどういう時に使用すれば良いのですか?
[S016 A-09]
型を宣言したいが、トップレベル型とするのが適切でない場合です。
トップレベル型よりもネストした型のほうが適していると思われる局面には
以下のようなものがあります。
1. あるクラスのインスタンス変数や、インスタンスメソッドと
深く関わっているクラスを作成する場合
2. クラス宣言をそれと強く関係するクラスの近くにおくことで可読性を増したい場合
3. あるクラスのメソッドのローカル変数や引数を使用したクラスを作成する場合
4. 特定の場所でのみインスタンスを生成する、あるインタフェースの実装の
メソッドしか持たないクラスを作成する場合
5. 特定の場所でのみインスタンスを生成する、あるクラスのメソッドのみを
オーバライドするクラスを作成する場合
6. アクセスをパッケージより局所化した型を作成したい場合
それぞれについて、以下に説明をします。
-------------
1. あるクラスのインスタンス変数や、インスタンスメソッドと
深く関わっているクラスを作成する場合
あるクラス Outer に囲われている内部クラス Inner 内では、
Outerのインスタンス変数やインスタンスメソッドを直接使用することができます。
このように記述することにより、コンストラクタやメソッドに渡す引数を
少なくできるという効果があります。
また、private なメンバにもアクセスが可能なので、カプセル化を崩しません。
例えば、以下の例ではOuterクラスのインスタンス変数aを
あたかもInnerクラスのインスタンス変数であるかのようにアクセスしています。
また、privateなメソッドfoo()も呼び出しています。
class Outer {
public int a = 0;
private void foo() {}
class Inner {
public void bar() {
System.out.println("a = " + a);
foo();
}
}
}
-------------
2. クラス宣言をそれと強く関係するクラスの近くにおくことで可読性を増したい場合
あるクラスを、それを使用する場所の近くに宣言しておくと、
どのような処理を行うかが分かるので、可読性が増す場合があります。
例えば、以下の例ではフレームのクローズ処理用のアダプタを匿名クラスを用いて生成し、
フレームの生成直後にリスナ登録することで、クローズ処理を把握しやすくしています。
この場合、WindowAdapter のサブクラスを独立したクラスとしなかった代償として
柔軟性が損なわれることがあるので注意しましょう。
import java.awt.event.*;
import java.awt.*;
class Foo {
public static void main(String[] args) {
Frame frame = new Frame();
:
WindowAdapter winAdapter = new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
};
frame.addWindowListener(winAdapter);
}
}
-------------
3. あるクラスのメソッドのローカル変数や引数を使用したクラスを作成する場合
クラス Inner を メソッド foo のブロック内にネストした型とすると、
クラス Inner からメソッド foo の final 修飾子のついたローカル変数や
引数が使用できます。
詳しくは、S016-14「メソッド内やブロック内で宣言したローカルな内部クラスから、
メソッドの引数やローカル変数をアクセスできますか?」を参照してください。
-------------
4. 特定の場所でのみインスタンスを生成する、あるインタフェースの実装の
メソッドしか持たないクラスを作成する場合
5. 特定の場所でのみインスタンスを生成する、あるクラスのメソッドのみを
オーバライドするクラスを作成する場合
上記のようなクラスはわざわざ名前をつけたクラスにする必要がないことと、
クラス名が介在すると明瞭さを欠いてしまうとのことから、
匿名クラスがよく用いられます。
以下の例では、multi()メソッドの処理をThreadクラスに基づく匿名クラスを用いて
別スレッドで実行しています。
new Thread() {
public void run() {
multi();
}
}.start();
また、同様のものを Runnableインタフェースに基づいて記述すると
以下のようになります。
Runnable r =
new Runnable() {
public void run() {
multi();
}
};
new Thread(r).start();
-------------
6. アクセスをパッケージより局所化した型を作成したい場合
ローカルクラスや private 修飾子のついたメンバクラスは
それを宣言したコンテキスト以外から参照することはできません。
このため、特定の型の使用をそれを囲んでいる型の内部のみに限定する、
つまり、パッケージでグループ化するのと同様の効果をもう一段深いレベルで
実現することができます。
class Outer {
private class Inner {}
}
class Other {
public static void main(String[] args) {
Outer.Inner inner = new Outer().new Inner(); // コンパイルエラー
}
}
[S016 Q-10]
内部クラスから外側のクラスのメソッドや変数を明示的に参照するには
どうすれば良いのですか?
[S016 A-10]
それぞれ、「クラス名.this.メソッド名」、「クラス名.this.変数名」とします。
内部クラスはカレントインスタンスを複数持つ場合があります。
例えば、static コンテキスト内に記述されていない内部クラスは
自分自身のカレントインスタンスと、自身を直接囲う外側のクラスの
カレントインスタンスを持ちます。
自身を直接囲う外側のクラスが static コンテキスト内に記述されてない
内部クラスであるならば、さらにそのクラスを囲う外側のクラスの
カレントインスタンスを持ちます。
通常のクラス同様、内部クラス内で this と指定した場合は、
自分のクラスのカレントインスタンスとなります。
外側のクラスのカレントインスタンスを指定したい場合は、
明示的に this を外側のクラス名で修飾します。
以下に例を示します。
public class Outer {
int var = 0;
class Inner {
int var = 1;
class MostInner {
int var = 2;
public void innerMethod() {
System.out.println("this.var = "+
var);
System.out.println("this.var = "+
this.var);
System.out.println("Outer.this.var = "+
Outer.this.var);
System.out.println("Inner.this.var = "+
Inner.this.var);
}
}
}
public static void main(String[] args) {
Outer.Inner.MostInner inner =
new Outer().new Inner().new MostInner();
inner.innerMethod();
}
}
この例を実行すると次のような結果が得られます。
this.var = 2
this.var = 2
Outer.this.var = 0
Inner.this.var = 1
[S016 Q-11]
ネストした型を他のクラスから new するにはどうすれば良いのですか?
[S016 A-11]
staticなメンバ型の場合はnew Outer.Inner()のように、
それ以外の場合(内部クラスの場合)はそれを囲うクラスのインスタンスを用いて
outer.new Inner()のようにします。
static 修飾子のつかないメンバクラスの場合は、それを囲う外側のクラスの
インスタンスが必要ですが、static 修飾子のついたメンバクラスの場合は
外側のクラスのインスタンスが必要ありません。
なお、ローカルクラスや匿名クラスは暗黙的にスコープ内ローカルなため、
スコープ外からインスタンスを生成することはできません。
以下に例を示します。
class Outer {
class NonStaticNestedClass {}
static class StaticNestedClass {}
}
public class Test {
public static void main(String[] args) {
Outer outer = new Outer();
Outer.NonStaticNestedClass a =
outer.new NonStaticNestedClass();
Outer.StaticNestedClass b =
new Outer.StaticNestedClass();
}
}
[S016 Q-12]
匿名クラスでコンストラクタを宣言したいのですがどうすれば良いですか?
[S016 A-12]
匿名クラスでコンストラクタを定義することはできません。
通常のクラスでコンストラクタ内で行なうような処理は、匿名クラスの場合には
インスタンス初期化子に記述します。
インスタンス初期化子内で例外を throw することもできます。
以下にインスタンス初期化子を用いた例を示します。
import java.util.Enumeration;
class Outer {
Enumeration createSinEnumerator() {
return new Enumeration () {
private double sin[];
private int index;
/* インスタンス初期化子 */
{
sin = new double[360];
for (int i = 0; i < sin.length; i++) {
sin[i] = Math.sin(i*Math.PI/180);
}
}
public Object nextElement() {
index++;
index = index % sin.length;
return new Double(sin[index]);
}
public boolean hasMoreElements() {
return true;
}
};
}
}
[S016 Q-13]
static変数を内部クラス内に宣言したいのですが、どうすれば良いのですか?
[S016 A-13]
static final 変数にすれば定義できます。
内部クラス内には、コンパイル時に定数とならないような static 変数を宣言することは
できません。同様に、静的初期化子 (static initializer) やメンバインタフェースを
宣言することもできません。
class Outer {
class Inner {
static final int a = 0; // OK
/* 以下は宣言できません */
static final int b; // ブランク final は NG
static final int d; static{ d = 0; }// static initializer で初期化しても NG
static int e = 0; // final でない static 変数は NG
interface memberInterface {} // interface は NG
}
}
[S016 Q-14]
メソッドやブロック内で宣言したローカルな内部クラスから、
メソッドの引数やローカル変数をアクセスできますか?
[S016 A-14]
内部クラスからアクセスする引数や変数がfinal宣言されている場合のみアクセスできます。
ローカルな内部クラスのインスタンスのライフタイム(生成されてからGCされるまでの期間)は、
それを宣言するブロックやメソッドのライフタイムより長いことがあり、
その場合、内部クラスから参照しているローカル変数や引数が実際に参照を行なう
タイミングまで存在し続けていないことがあります。
そういったケースでも正しく値が参照できることを保証するため、
参照可能なものを内部クラスが生成される時点以降に値が変更されないもの、
すなわち、final宣言されているローカル変数や引数のみに制限し、
その値をコピーしてインスタンス内部に保持するようになっています。
例えば、与えられた配列のうち、指定した範囲の要素を枚挙する Enumeration を返す
メソッドを考えます。
Enumeration elementsFromTo(final Object[] array,
final int start,
final int end) {
return new Enumeration() {
private int index = start;
public boolean hasMoreElements() {
return index < end;
}
public Object nextElement() {
if (hasMoreElements()) {
return array[index++];
}
throw
new NoSuchElementException("index out of "+
"bounds: "+index+
" >= "+end);
}
};
}
さらに、このメソッドを呼び出す処理を考えます。
void caller() {
String[] fruits = {"Apple", "Orange", "Peach"};
Enumeration e =
elementsFromTo(fruits, 1, fruits.length-1);
while (e.hasMoreElements()) {
System.out.println(e.nextElement());
}
}
この処理の流れは以下のようになります。
1. caller()が呼び出される
2. elementsFromTo()が呼び出される
3. new Enumeration() で内部クラスのインスタンスが生成される
4. 生成されたインスタンスがelementsFromTo()の戻り値として返される
5. 返されてきた内部クラスのインスタンスをcaller()で使用する
hasMoreElements()やnextElement()が呼び出されていることから、
elementsFromTo()メソッドの引数array、start、endは、
このメソッドの実行が完了した後も内部クラスから参照されることがわかります。
このため、これらの引数は final となっている必要があるわけです。
[S016 Q-15]
インタフェース中でネストした型を定義できますか?
[S016 A-15]
可能です。
ただし、インタフェース中で、定義されたクラスやインタフェースは public でかつ、
static であるものとして扱われます。
このためこれらをインスタンス化する場合、外側のインタフェースを実装したクラスの
インスタンスは不要です。
ネストしたクラスのインスタンス化については S016-11 を参照して下さい。
import java.util.Enumeration;
interface Outer {
class NestedClass {}
}
class Test {
public static void main(String[] args) {
Outer.NestedClass nestedClass = new Outer.NestedClass();
}
}
contributor: NONAKA Ai, Kenji Abe
コメントの送り先 Java FAQ BBS