Java Language
入れ子クラスと内部クラス
サーチ…
前書き
Javaを使用すると、開発者は別のクラス内でクラスを定義することができます。そのようなクラスはネストされたクラスと呼ばれます。ネストされたクラスは、非静的であると宣言された場合は内部クラスと呼ばれ、そうでない場合は静的なネストされたクラスと呼ばれます。このページは、Javaの入れ子クラスと内部クラスの使用方法の例を文書化し、詳細を提供するためのものです。
構文
- パブリッククラスOuterClass {public class InnerClass {}} //インナークラスはprivateでもかまいません
- パブリッククラスOuterClass {public static class StaticNestedClass {}} //静的にネストされたクラスもプライベートにすることができます
- public void method(){private class LocalClass {}} //ローカルクラスは常にprivateです。
- SomeClass anonymousClassInstance = new SomeClass(){}; //匿名の内部クラスには名前を付けることができないため、アクセスは疑わしいです。 'SomeClass()'が抽象クラスの場合、ボディはすべての抽象メソッドを実装する必要があります。
- SomeInterface anonymousClassInstance =新しいSomeInterface(){}; //ボディはすべてのインターフェイスメソッドを実装する必要があります。
備考
用語と分類
Java言語仕様(JLS)は、さまざまな種類のJavaクラスを次のように分類します。
トップレベルのクラスは、ネストされたクラスではないクラスです。
ネストされたクラスは、他のクラスまたはインタフェースの本体内で宣言が発生する任意のクラスです。
内部クラスは、静的に明示的または暗黙的に宣言されていないネストされたクラスです。
内部クラスは、 非静的メンバークラス 、 ローカルクラス 、または匿名クラスです。インターフェイスのメンバクラスは暗黙的に静的であるため、内部クラスとはみなされません。
実際にプログラマーは、内部クラスを「外部クラス」として含む最上位レベルのクラスを参照します。また、静的ネストされたクラスのみを(明示的または暗黙的に)参照するために「ネストされたクラス」を使用する傾向があります。
匿名の内部クラスとlambdaは密接な関係にありますが、lambdasはクラスです。
意味の違い
トップレベルのクラスは「基本ケース」です。これらは、アクセス修飾子のセマンティクスに基づいた通常の可視性規則の対象となるプログラムの他の部分にも見えます。非抽象クラスの場合、関連するコンストラクタがアクセス修飾子に基づいて表示されるコードでインスタンス化できます。
静的なネストされたクラスは、次の2つの例外を除いて、トップレベルのクラスと同じアクセスおよびインスタンス化のルールに従います。
- ネストされたクラスは
private
クラスとして宣言することができ、そのクラスを囲むトップレベルクラスの外部ではアクセスできなくなります。 - ネストされたクラスは、囲んでいるトップレベルクラスの
private
メンバーとテスト済みのクラスにアクセスできます。
静的なネストされたクラスは、厳密な抽象境界内に複数の「エンティティタイプ」を表現する必要がある場合に便利です。たとえば、ネストしたクラスを使用して「実装の詳細」を隠す場合
- ネストされたクラスは
内部クラスは、囲みスコープで宣言された非静的変数にアクセスする機能を追加します。
- 非静的メンバークラスはインスタンス変数を参照できます。
- メソッド内で宣言されたローカルクラスは、メソッドのローカル変数を参照することもできます(ただし、それらが
final
。 (Java 8以降では、 効果的に最終的に使用できます)。 - 匿名の内部クラスは、クラスまたはメソッド内で宣言することができ、同じ規則に従って変数にアクセスできます。
内部クラス・インスタンスが、囲むクラス・インスタンス内の変数を参照できるという事実は、インスタンス化に意味を持ちます。具体的には、内部クラスのインスタンスが作成されるときに、包含するインスタンスを暗黙的または明示的に提供する必要があります。
ネストされたクラスを使用した単純なスタック
public class IntStack {
private IntStackNode head;
// IntStackNode is the inner class of the class IntStack
// Each instance of this inner class functions as one link in the
// Overall stack that it helps to represent
private static class IntStackNode {
private int val;
private IntStackNode next;
private IntStackNode(int v, IntStackNode n) {
val = v;
next = n;
}
}
public IntStack push(int v) {
head = new IntStackNode(v, head);
return this;
}
public int pop() {
int x = head.val;
head = head.next;
return x;
}
}
そして、(特に)ネストされたクラスの存在を全く認めない、その使用。
public class Main {
public static void main(String[] args) {
IntStack s = new IntStack();
s.push(4).push(3).push(2).push(1).push(0);
//prints: 0, 1, 2, 3, 4,
for(int i = 0; i < 5; i++) {
System.out.print(s.pop() + ", ");
}
}
}
静的クラスと非静的クラス
ネストされたクラスを作成するときは、そのネストされたクラスを静的にする選択に直面します:
public class OuterClass1 {
private static class StaticNestedClass {
}
}
または非静的:
public class OuterClass2 {
private class NestedClass {
}
}
そのコアでは、静的ネストされたクラスは 、外部クラスの周囲のインスタンスを持たないのに対し、非静的なネストされたクラスはそのようにします。これは、ネストされたクラスをインスタンス化することが許可されている場所とその場所と、そのネストされたクラスのインスタンスにアクセスが許可されている場所の両方に影響します。上記の例に加えて:
public class OuterClass1 {
private int aField;
public void aMethod(){}
private static class StaticNestedClass {
private int innerField;
private StaticNestedClass() {
innerField = aField; //Illegal, can't access aField from static context
aMethod(); //Illegal, can't call aMethod from static context
}
private StaticNestedClass(OuterClass1 instance) {
innerField = instance.aField; //Legal
}
}
public static void aStaticMethod() {
StaticNestedClass s = new StaticNestedClass(); //Legal, able to construct in static context
//Do stuff involving s...
}
}
public class OuterClass2 {
private int aField;
public void aMethod() {}
private class NestedClass {
private int innerField;
private NestedClass() {
innerField = aField; //Legal
aMethod(); //Legal
}
}
public void aNonStaticMethod() {
NestedClass s = new NestedClass(); //Legal
}
public static void aStaticMethod() {
NestedClass s = new NestedClass(); //Illegal. Can't construct without surrounding OuterClass2 instance.
//As this is a static context, there is no surrounding OuterClass2 instance
}
}
したがって、静的と非静的の主な決定は、主に外部クラスのフィールドとメソッドに直接アクセスできる必要があるかどうかによって異なりますが、ネストされたクラスをいつどこで構築できるかにも影響します。
経験則として、外部クラスのフィールドやメソッドにアクセスする必要がないかぎり、ネストされたクラスを静的にしてください。公開が必要な場合を除いて、フィールドを非公開にするのと同様に、(外部インスタンスへのアクセスを許可しないことで)ネストされたクラスで使用可能な可視性を減らし、エラーの可能性を減らします。
内部クラスのアクセス修飾子
Javaにおけるアクセス修飾子の完全な説明は、ここで見つけることができます 。しかし、彼らはどのようにInnerクラスとやりとりしますか?
public
は、いつものように、型にアクセスできるスコープに無制限のアクセス権を与えます。
public class OuterClass {
public class InnerClass {
public int x = 5;
}
public InnerClass createInner() {
return new InnerClass();
}
}
public class SomeOtherClass {
public static void main(String[] args) {
int x = new OuterClass().createInner().x; //Direct field access is legal
}
}
protected
とdefault修飾子(何もしていない)の両方が、ネストされていないクラスに対しても同じように動作します。
興味深いことに、 private
、それが属するクラスに制限されません。むしろ、コンパイル単位(.javaファイル)に制限されます。つまり、OuterクラスはInnerクラスのフィールドとメソッドにフルアクセスできますが、 private
クラスとマークされていても使用できます。
public class OuterClass {
public class InnerClass {
private int x;
private void anInnerMethod() {}
}
public InnerClass aMethod() {
InnerClass a = new InnerClass();
a.x = 5; //Legal
a.anInnerMethod(); //Legal
return a;
}
}
インナークラス自体は、 public
以外の可視性を持つことができます。それをprivate
または他の制限付きアクセス修飾子にマークすることにより、他の(外部の)クラスはその型をインポートして割り当てることができません。しかし、それらは依然としてそのタイプのオブジェクトへの参照を得ることができます。
public class OuterClass {
private class InnerClass{}
public InnerClass makeInnerClass() {
return new InnerClass();
}
}
public class AnotherClass {
public static void main(String[] args) {
OuterClass o = new OuterClass();
InnerClass x = o.makeInnerClass(); //Illegal, can't find type
OuterClass.InnerClass x = o.makeInnerClass(); //Illegal, InnerClass has visibility private
Object x = o.makeInnerClass(); //Legal
}
}
匿名の内部クラス
匿名の内部クラスは、宣言され、単一のステートメントでインスタンス化される内部クラスの形式です。結果として、プログラムのどこかで使用できるクラスの名前はありません。つまり、それは匿名です。
匿名クラスは、通常軽量クラスを作成してパラメータとして渡せるようにする必要がある場合に使用します。これは、通常、インタフェースで行われます。例えば:
public static Comparator<String> CASE_INSENSITIVE =
new Comparator<String>() {
@Override
public int compare(String string1, String string2) {
return string1.toUpperCase().compareTo(string2.toUpperCase());
}
};
この匿名クラスは、大文字と小文字の違いを無視して2つの文字列を比較するComparator<String>
オブジェクト( CASE_INSENSITIVE
)を定義します。
匿名クラスを使用して頻繁に実装されインスタンス化されるその他のインタフェースは、 Runnable
およびCallable
です。例えば:
// An anonymous Runnable class is used to provide an instance that the Thread
// will run when started.
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello world");
}
});
t.start(); // Prints "Hello world"
匿名の内部クラスは、クラスに基づくこともできます。この場合、匿名クラスは暗黙的に既存のクラスをextends
します。拡張されるクラスが抽象クラスの場合、匿名クラスはすべての抽象メソッドを実装する必要があります。非抽象メソッドをオーバーライドすることもできます。
コンストラクタ
匿名クラスは明示的なコンストラクタを持つことはできません。代わりに、拡張されているクラスのコンストラクタにパラメータを渡すためにsuper(...)
を使用する暗黙のコンストラクタが定義されています。例えば:
SomeClass anon = new SomeClass(1, "happiness") {
@Override
public int someMethod(int arg) {
// do something
}
};
SomeClass
匿名サブクラスの暗黙のコンストラクタは、呼び出しシグネチャSomeClass(int, String)
と一致するSomeClass
コンストラクタを呼び出します。コンストラクタが利用できない場合は、コンパイルエラーが発生します。一致したコンストラクタによってスローされた例外も、暗黙のコンストラクタによってスローされます。
もちろん、これはインターフェイスを拡張するときには機能しません。インターフェイスから匿名クラスを作成する場合、クラススーパークラスは引数なしのコンストラクタのみを持つjava.lang.Object
です。
メソッドローカルの内部クラス
メソッドローカルインナークラスと呼ばれるメソッド内に記述されたクラス 。その場合、内部クラスのスコープはメソッド内で制限されます。
メソッドローカルの内部クラスは、内部クラスが定義されているメソッド内でのみインスタンス化できます。
メソッドローカル内部クラスの使用例:
public class OuterClass {
private void outerMethod() {
final int outerInt = 1;
// Method Local Inner Class
class MethodLocalInnerClass {
private void print() {
System.out.println("Method local inner class " + outerInt);
}
}
// Accessing the inner class
MethodLocalInnerClass inner = new MethodLocalInnerClass();
inner.print();
}
public static void main(String args[]) {
OuterClass outer = new OuterClass();
outer.outerMethod();
}
}
実行すると、出力が得られます。 Method local inner class 1
。
非静的な内部クラスから外部クラスにアクセスする
外部クラスへの参照はクラス名を使用しthis
public class OuterClass {
public class InnerClass {
public void method() {
System.out.println("I can access my enclosing class: " + OuterClass.this);
}
}
}
外部クラスのフィールドとメソッドに直接アクセスできます。
public class OuterClass {
private int counter;
public class InnerClass {
public void method() {
System.out.println("I can access " + counter);
}
}
}
しかし、名前の衝突の場合は、外部クラス参照を使用できます。
public class OuterClass {
private int counter;
public class InnerClass {
private int counter;
public void method() {
System.out.println("My counter: " + counter);
System.out.println("Outer counter: " + OuterClass.this.counter);
// updating my counter
counter = OuterClass.this.counter;
}
}
}
外部から非静的な内部クラスのインスタンスを作成する
このクラスからも、任意の外部クラスから見える内部クラスを作成することができます。
内部クラスは外部クラスに依存し、そのインスタンスへの参照を必要とします。内部クラスのインスタンスを作成するには、 new
演算子は外部クラスのインスタンスに対してのみ呼び出す必要があります。
class OuterClass {
class InnerClass {
}
}
class OutsideClass {
OuterClass outer = new OuterClass();
OuterClass.InnerClass createInner() {
return outer.new InnerClass();
}
}
使用法はouter.new
注意してouter.new
。