1. ネストしたクラスとは?
クラス内に定義したクラスのことを総称した表現です。
対してネストしたクラスを囲む外側のクラス「ヱンクロージングクラス」「トップレベルクラス」と表現します。
なぜ必要?
「情報隠蔽」及び「カプセル化」を強化するためです。
情報隠蔽とは
「情報隠蔽」とは、そのソフトウェアがどのような構造で成り立っているのか、
またそのプログラムモジュールがどのような機能を持っているのかを隠蔽し、公開する範囲を最小化することです。クラスが想定外の方法で使用されないようにします。
カプセル化とは
「カプセル化」とは関係するものを1つにまとめたモジュール(部品)を定義することです。
関係性の深い2つのクラスがある時、片方をもう片方のネストしたクラスにしてまとめることで、
関係するクラスを隈無く探す必要がなくなります。
2. ネストしたクラスの分類
ネストしたクラスは以下4分類に分けられます。
- インナークラス
- staticインナークラス
- ローカルクラス
- 匿名クラス
定義とアクセス制御の方法
インナークラスやstaticインナークラスは、エンクロージングクラスのメンバとして定義します。
これらのクラスは、publicからprivateまでの4つのアクセス修飾子のいずれかで修飾することにより、
他のクラスからのアクセスを制御できます。なおこれらのクラスは、同じエンンクロージングクラス内にサブクラスを作ることもできるので、abstractやfinalで修飾することもできます。
ローカルクラスや匿名クラスは、メソッド内で定義する一時的なクラスであって、エンクロージングクラスのメンバではありません。そのため他のクラスからアクセスすることはできず、アクセス修飾子で修飾することもできません。
種別 | 定義方法 | public | protected | private | abstract | final | static |
---|---|---|---|---|---|---|---|
インナークラス | エンクロージングクラスのメンバとして定義する | ○ | ○ | ○ | ○ | ○ | × |
staticインナークラス | エンクロージングクラスのメンバとして定義する | ○ | ○ | ○ | ○ | ○ | ○ |
ローカルクラス | メソッド内で一時的に定義する | × | × | × | ○ | ○ | × |
匿名クラス | メソッド内で一時的に定義する | × | × | × | ○ | ○ | × |
3. インナークラス
クラスの中に定義したクラスです。
public class Outer {
private class Inner {
public void test() {
System.out.println("test");
}
}
}
4. staticインナークラス
staticで修飾したインナークラスです。
public class Outer {
private static class Inner {
public void test() {
System.out.println("test");
}
}
}
ポイント
インナークラスとstaticインナークラスを使用する際は以下2点を考慮する必要があります。
(1) staticメソッドから非staticなフィールドやメソッドにアクセスできない
(2) インスタンス化するためには、インナークラスでは予めエンクロージングクラスのインスタンス作成が必要であり、staticインナークラスでは不要。
これはstaticと非staticでは配置されるメモリ領域と使えるタイミングが異なるためです。
staticなフィールドやメソッドは、クラスがロードされたタイミングでstatic領域に配置されます。
非staticなフィールドやメソッドはヒープ領域にインスタンスを生成して初めて、使えるようになります。
以下は(1)のコード例です。
public class Outer {
void test() {
// ★★★ [コンパイルエラー] staticなメソッドから非staticなフィールドやメソッドにアクセスしている ★★★
Inner.message = "Hello, Java";
}
class Inner {
private static String message;
public void test() {
System.out.println(message);
}
}
public static void main(String[] args) {
Outer outer = new Outer();
outer.test();
outer.new Inner().test();
}
}
以下は(2)のコード例です。
public class SampleFactory {
public static Sample create() {
// ★★★ [コンパイルエラー] エンクロージングクラスを作成せずに、インナークラスをインスタンス化している ★★★
return new SampleImpl();
// → ○ return new SampleFactory().new SampleImpl();
}
// ★★★ インナークラス ★★★
private class SampleImpl implements Sample {
public void test() {
System.out.println("test");
}
@Override
public void execute() {
System.out.println("execute");
}
}
}
public class SampleFactory {
public static Sample create() {
// ★★★ [コンパイルエラー] エンクロージングクラスを作成してから、staticインナークラスをインスタンス化している ★★★
return new SmapleFactory().new SampleImpl();
// → ○ return new SampleImpl();
}
// ★★★ インナークラス ★★★
private static class SampleImpl implements Sample {
public void test() {
System.out.println("test");
}
@Override
public void execute() {
System.out.println("execute");
}
}
}
4. ローカルクラス
利用可能な範囲を特定のメソッドだけに絞ったクラスです。
メソッド毎に実装内容を変えることができます。
public class Factory {
public static Test hello() {
class Hello implements Test {
@Override
public void execute() {
System.out.println("hello.");
}
}
return new Hello();
public static Test bye() {
class Bye Implements Test {
@Override
public void execute() {
System.out.println("bye");
}
}
}
}
ローカル変数とのライフサイクルの違い
ローカルクラスのインスタンスは、その参照が残っていればメソッドの処理が終了してもメモリ上に存在し続けます。一方ローカル変数は、メソッドの処理が終わるとメモリ上から消えます。
参照可能なローカル変数
前述の通り、ローカルクラスとローカル変数はライフサイクルが異なるため、ローカルクラスからは以下2つの条件を満たすローカル変数のみ参照できます。
- ローカルクラスの定義より前に宣言されている
- ローカル変数が実質的にfinalである
public class IllegalAccessSample {
public void test() {
class Sample {
public void hello() {
System.out.println("Hello, " + name);
}
}
// ★★★ [コンパイルエラー] ローカルクラスの定義後にローカル変数を宣言している ★★★
String name = "sample";
new Sample().hello();
}
}
public class IllegalAccessSample {
public void test() {
String name = "sample";
class Sample {
public void hello() {
System.out.println("Hello, " + name);
}
}
// ★★★ [コンパイルエラー] ローカル変数の値を変更している ★★★
name = "test";
new Sample().hello();
}
}
5. 匿名クラス
名前のないクラスのことです。
名前がないため、コンストラクタを定義できません。
インタフェースを実現したクラス、抽象クラスか具象クラスを継承したサブクラスとして、名前のないクラスを宣言し、そのクラスがどのようなフィールドやメソッドを持つべきか、というクラスの実装内容だけを定義したものです。
参考文献
徹底攻略Java SE 11 Gold問題集