0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Java ネストしたクラス

Last updated at Posted at 2024-06-30

Java 11 Gold 取得に向けた学習記録

Enclosingクラス

クラスの内部でさらにクラスを定義した場合、外側のクラスはEnclosingクラスと呼ばれ、内側のクラスはネストしたクラスと呼ばれる(enclose=囲う)。

いくつかの分類がある。

定義場所 public protected private abstract final stataic
インナークラス クラス内
ローカルクラス メソッド内 × × × ×
匿名クラス メソッド内 × × × × × ×

インナークラス

クラス内で定義されたクラスをインナークラスと言う。

インナークラス
class OuterClass {
    class InnerClass {
    
    }
}

インナークラスはEnclosingクラスと同様のアクセス修飾子(public, protected, private, abstract, final)を使用する事ができる。

またインターフェースを実装(implements)することもできる。

インナークラスはEnclosingクラスのインスタンスに属しているため、Enclosingクラスのインスタンスを介してアクセスを行う。

OuterClass outer = new MyClass();
OuterClass.InnerClass inner = outer.new InnerClass();

以下のように1行にまとめて記述することもできる。

OuterClass.InnerClass inner = new OuterClass().new InnerClass();

またimportを行えば、このような記述も可能。

インナークラスのimportもできる
import my.package.OuterClass.InnerClass;

InnerClass inner = new OuterClass().new InnerClass();

特徴

インナークラスはEnclosingクラスで定義されたフィールドやメソッドと同様に、Enclosingクラスのインスタンス変数に直接アクセスする事ができる(ここが個人的に最初全然理解できていなかったところで、インナークラスを理解する上での肝だと思った)。

Enclosingクラスのインスタンス変数に直接アクセスできる
class OuterClass {
    String outerField = "hello";

    class InnerClass {
        String innerFiled = outerField + " world";
    }
}

この特徴からわかるように、インナークラスはEnclosingクラスのインスタンスに依存している。

インナークラスはEnclosingクラスのインスタンスに依存している

staticインナークラス

インナークラスをstatic修飾したものを特にstaticインナークラスと呼ぶ。

staticインナークラス
class OuterClass {
    static class InnerClass {

    }
}

staticインナークラスは、インスタンス化にEnclosingクラスのインスタンスを必要としない。

個人的に混同していたのが、staticインナークラスはあくまでも「クラス」なので、インスタンス化(new)が不要なわけではない。

staticインナークラスはstaticだからと言ってnewしなくても良いわけではない

OuterClass.InnerClass inner = new OuterClass.InnerClass();

以下のような記述も可能。

staticインナークラスもimportできる
import my.package.OuterClass.InnerClass;

InnerClass inner = new InnerClass();

staticインナークラスはEnclosingクラスのインスタンスではなく、Enclosingクラス自体に属するため、Enclosingクラスのインスタンスに依存しない。

そのため、Enclosingクラスの非staticフィールドや、非staticメソッドにはアクセスする事ができない。

class OuterClass {
    String outerField = "hello";

    static class InnerClass {
        // コンパイルエラー
        String innerFiled = outerField + " world";
    }
}

staticインナークラスはEnclosingクラスのインスタンスに依存しない。
Enclosingクラス自体に依存している。

インナークラスでstaticフィールドは定義できない

staticインナークラス内でstaticなフィールドを定義しようとした場合、コンパイルエラーが発生する。

class OuterClass {
    class InnerClass {
        // コンパイルエラー
        static int staticField = 0;

        // 定数は定義可能
        static final int CONSTANT_FIELD = 10;
    }  
}

staticフィールドはクラス自体に依存しているが、非staticインナークラスはEnclosingクラスのインスタンスに依存している。

ここでのstaticFieldは、InnerClassのインスタンスではなくクラス自体に依存する。一方で、InnerClassOuterClassのインスタンスに依存している。そのため、依存関係が一致していない。

そのため、インナークラスをstatic修飾しなければならない。

class OuterClass {
    static class InnerClass {
        static int staticField = 10;
    }        
}

ローカルクラス

メソッド内で定義されるクラスをローカルクラスと言う。

(メソッド内で定義される変数をローカル変数と言う)

ローカルクラス
class OuterClass {
    void outerMethod() {
        class LocalClass {
        
        }
    }
}

ローカルクラスは自身が定義されたスコープ内でのみ、インスタンス化を行う事ができる。

class OuterClass {
    void outerMethod() {
        class LocalClass {
           
        }

        LocalClass local = new LocalClass();
    }
}

Factoryパターンを使用する際に、クラス定義をメソッド内に隠蔽させる使い方などがある。

ローカルクラスのインスタンスは、メソッドの実行後もメモリ上に存在し続ける可能性がある。

interface MyInterface {
    void myMethod();
}
class OuterClass {
    MyInterface outerMethod() {
        class LocalClass implements MyInterface {
            @Override
            public void myMethod() {
            }
        }

        return new LocalClass();
    }
}
MyInterface local = new OuterClass().outerMethod();
// outerMethod()実行後も、LocalClassのインスタンスはまだ生きている
local.myMethod();    

一方で、ローカル変数のスコープはメソッドの終了とともに消滅する(メモリ上から削除される)。

そのためローカルクラス内でEnclosingクラスのローカル変数を使用した場合、実質的にfinal(effectively final)な定数扱いになる(ラムダ式と同様)。

class OuterClass {
    void outerMethod() {
        String localVal = "hello";
        class LocalClass {
            void innerMethod(){
                // コンパイルエラー
                localVal = "world";
            }
        }
    }
}

JavaScriptではクロージャーと呼ばれる外側のスコープ(レキシカルスコープ)を保持し続ける関数を定義する事ができる。

JavaScript
function outer() {
    // レキシカルスコープ
    let outerVar = "hello";
    
    function inner() {
        outerVar = "world";
        console.log(outerVar);
    }
    
    return inner;
}

const innerFn = outer();
innerFn();  
// >> "world"

匿名クラス

名前のないクラスのインスタンスを生成するための特殊な構文。

普通、Javaのクラスは名前とともに宣言され、宣言された名前とnewキーワードを使ってインスタンス化が行われる。

インスタンス化
MyClass obj = new MyClass();

匿名クラスは宣言と同時にインスタンスが生成される。名前を持たないクラスから生成されるため、クラス名を用いて宣言する必要があるコンストラクタを定義することはできない。

匿名クラスにはコンストラクタを定義できない

また、匿名クラスはインターフェースに使用した場合にはインターフェースを「実装(implements)」したクラスになり、通常のクラスに使用した場合にはそのクラスを「継承(extends)」したサブクラスになる。

インターフェースに対して使用する匿名クラス

インターフェースに対して使用する例
public interface MyInterface {
    void method();
}
インターフェースに対して使用する例
public class Main {
    public static void main(String[] args) {
        MyInterface obj = new MyInterface() {
            // ここが MyInterface を「実装」するクラス
            @Override
            public void method() {
                System.out.println("ここでインターフェースを実装する");
            }
        };
    }
}

スレッドの生成時にも利用される。

// java.lang.Thread、java.lang.Runnable を使用

// 匿名クラスを使ってRunnableインターフェースを実装する
var thread = new Thread(new Runnable(){
    @Override
    public void run() {
        System.out.println("非同期的に処理を実行します。");
    }
});

thread.start();
// >> 非同期的に処理を実行します。

クラスに対して使用する匿名クラス

クラスに対して使用する例
public class SuperClass { 
    public void method() {
        System.out.println("hello world");
    }
}
クラスに対して使用する例
public class Main {
    public static void main(String[] args) {
         SuperClass obj = new SuperClass() {
            // ここが SuperClass を「継承」する匿名クラス
            @Override
            public void method() {
                System.out.println("ここでスーパークラスを継承する");
            }
        };
    }
}

匿名クラスは、クラス名やコンストラクタは定義する事ができないため、初期化を行いたい場合インスタンス初期化子を使用する。

インスタンス初期化子についてはこちら。

インスタンス初期化子
public class SuperClass { 
    String field;
    
    public void method() {
        System.out.println(field);
    }
}
インスタンス初期化子
public class Main {
    public static void main(String[] args) {
        SuperClass obj = new SuperClass() {
            // インスタンス初期化子
            {
                field = "hello world";
            }

            @Override
            public void method() {
                System.out.println(field);
            }
        };

        obj.method();
        // >> hello world
    }
}

Listの初期化では匿名クラスの初期化子が利用されている。

List<String> list = new ArrayList<String>(){
    // ここが匿名クラス部分
    {
        // ここが匿名クラスのインスタンス初期化子部分
        add("Apple");
        add("Orange");
        add("Melon");
    }
};

参考

徹底攻略Java SE 11 Gold問題集[1Z0-816]対応

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?