Javaのインナークラス

Javaの内部クラスは、別のクラスの本体内で定義されます。Javaの内部クラスはprivate、public、protected、またはデフォルトのアクセスで宣言することができますが、外部クラスはpublicまたはデフォルトのアクセスのみを持つことができます。Javaのネストされたクラスは2つのタイプに分けられます。

  1. staticなネストされたクラス

    ネストされたクラスがstaticの場合、それはstaticなネストされたクラスと呼ばれます。staticなネストされたクラスは、外部クラスのstaticメンバーのみにアクセスすることができます。staticなネストされたクラスは、他のトップレベルクラスと同じであり、パッケージングの便宜のためにネストされているだけです。staticなクラスオブジェクトは、以下の文で作成することができます。

    OuterClass.StaticNestedClass nestedObject =
         new OuterClass.StaticNestedClass();
    
  2. Java内部クラス

    任意の非静的ネストクラスはJavaでは内部クラスとして知られています。Javaの内部クラスはクラスのオブジェクトと関連付けられており、外部クラスのすべての変数とメソッドにアクセスできます。内部クラスはインスタンスと関連付けられているため、静的変数を持つことはできません。Javaの内部クラスのオブジェクトは外部クラスのオブジェクトの一部であり、内部クラスのインスタンスを作成するにはまず外部クラスのインスタンスを作成する必要があります。Javaの内部クラスは次のようにインスタンス化できます;

    OuterClass outerObject = new OuterClass();
    OuterClass.InnerClass innerObject = outerObject.new InnerClass();
    

Javaの内部クラスには2つの特別な種類があります。

  1. ローカル内部クラス

    クラスがメソッド本体で定義されている場合、それはローカル内部クラスとして知られています。ローカル内部クラスはオブジェクトに関連付けられていないため、それに対してprivate、public、またはprotectedアクセス修飾子を使用することはできません。許可されている修飾子は、abstractまたはfinalのみです。ローカル内部クラスは、その定義されているスコープ内の外部クラスのすべてのメンバーとローカルのfinal変数にアクセスできます。さらに、それは、定義されているメソッドの非finalローカル変数にもアクセスできますが、それらを変更することはできません。したがって、非finalローカル変数の値を印刷しようとすると許可されますが、メソッドローカル内部クラス内からその値を変更しようとすると、コンパイル時エラーが発生します。ローカル内部クラスは次のように定義できます:

    package com.journaldev.innerclasses;
    
    public class MainClass {
    
    	private String s_main_class;
    
    	public void print() {
    		String s_print_method = "";
    		// メソッド内のローカル内部クラス
    		class Logger {
    			// 外部クラス変数にアクセスできる
    			String name = s_main_class; 
    			// 非finalメソッド変数にアクセスできる
    			String name1 = s_print_method; 
    
    			public void foo() {
    				String name1 = s_print_method;
    				// 以下のコードはコンパイル時エラーをスローします:
    				// Local variable s_print_method defined in an enclosing scope must be final or effectively final 
    				// s_print_method= ":";
    			}
    		}
    		// メソッド内のローカル内部クラスをインスタンス化して使用する
    		Logger logger = new Logger();
    
    	}
    }
    

    ブロック内にもローカル内部クラスを定義できます。staticブロック、if-elseブロックなどです。ただし、この場合、クラスのスコープは非常に限られます。

    public class MainClass {
    
    	static {
    		class Foo {
    			
    		}
    		Foo f = new Foo();
    	}
    	
    	public void bar() {
    		if(1 < 2) {
    			class Test {
    				
    			}
    			Test t1 = new Test();
    		}
    		// スコープのため、以下はエラーが発生します
    		//Test t = new Test();
    		//Foo f = new Foo();
    	}
    }
    
  2. 匿名内部クラス

    名前のないローカル内部クラスは、匿名内部クラスとして知られています。匿名クラスは、1つの文で定義とインスタンス化が行われます。匿名内部クラスは常にクラスを拡張するか、インターフェースを実装します。匿名クラスには名前がないため、匿名クラスのコンストラクタを定義することはできません。匿名内部クラスは、定義された場所でのみアクセス可能です。匿名内部クラスの作成方法を定義するのは少し難しいですが、以下のテストプログラムでそのリアルタイムの使用方法を見てみましょう。

以下は、Javaの内部クラス、静的ネストクラス、ローカル内部クラス、および匿名内部クラスの定義方法を示すJavaクラスです。 OuterClass.java

package com.journaldev.nested;

import java.io.File;
import java.io.FilenameFilter;

public class OuterClass {
    
    private static String name = "OuterClass";
    private int i;
    protected int j;
    int k;
    public int l;

    //OuterClass constructor
    public OuterClass(int i, int j, int k, int l) {
        this.i = i;
        this.j = j;
        this.k = k;
        this.l = l;
    }

    public int getI() {
        return this.i;
    }

    //static nested class、OuterClassの静的変数/メソッドにアクセスできます
    static class StaticNestedClass {
        private int a;
        protected int b;
        int c;
        public int d;

        public int getA() {
            return this.a;
        }

        public String getName() {
            return name;
        }
    }

    //内部クラス、非静的で外部クラスのすべての変数/メソッドにアクセスできます
    class InnerClass {
        private int w;
        protected int x;
        int y;
        public int z;

        public int getW() {
            return this.w;
        }

        public void setValues() {
            this.w = i;
            this.x = j;
            this.y = k;
            this.z = l;
        }

        @Override
        public String toString() {
            return "w=" + w + ":x=" + x + ":y=" + y + ":z=" + z;
        }

        public String getName() {
            return name;
        }
    }

    //ローカル内部クラス
    public void print(String initial) {
        //ローカル内部クラス inside the method
        class Logger {
            String name;

            public Logger(String name) {
                this.name = name;
            }

            public void log(String str) {
                System.out.println(this.name + ": " + str);
            }
        }

        Logger logger = new Logger(initial);
        logger.log(name);
        logger.log("" + this.i);
        logger.log("" + this.j);
        logger.log("" + this.k);
        logger.log("" + this.l);
    }

    //匿名内部クラス
    public String[] getFilesInDir(String dir, final String ext) {
        File file = new File(dir);
        //匿名内部クラス implementing FilenameFilter interface
        String[] filesList = file.list(new FilenameFilter() {

            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(ext);
            }

        });
        return filesList;
    }
}

これが、Javaの内部クラスをインスタンス化して使用する方法を示すテストプログラムです。 InnerClassTest.java

package com.journaldev.nested;

import java.util.Arrays;
//ネストされたクラスは、インポートで使用して簡単にインスタンス化できます
import com.journaldev.nested.OuterClass.InnerClass;
import com.journaldev.nested.OuterClass.StaticNestedClass;

public class InnerClassTest {

    public static void main(String[] args) {
        OuterClass outer = new OuterClass(1,2,3,4);
        
        //静的ネストクラスの例
        StaticNestedClass staticNestedClass = new StaticNestedClass();
        StaticNestedClass staticNestedClass1 = new StaticNestedClass();
        
        System.out.println(staticNestedClass.getName());
        staticNestedClass.d=10;
        System.out.println(staticNestedClass.d);
        System.out.println(staticNestedClass1.d);
        
        //内部クラスの例
        InnerClass innerClass = outer.new InnerClass();
        System.out.println(innerClass.getName());
        System.out.println(innerClass);
        innerClass.setValues();
        System.out.println(innerClass);
        
        //ローカル内部クラスを使用したメソッドの呼び出し
        outer.print("Outer");
        
        //匿名内部クラスを使用したメソッドの呼び出し
        System.out.println(Arrays.toString(outer.getFilesInDir("src/com/journaldev/nested", ".java")));
        
        System.out.println(Arrays.toString(outer.getFilesInDir("bin/com/journaldev/nested", ".class")));
    }

}

上記のJava内部クラスの例プログラムの出力は次のとおりです。

OuterClass
10
0
OuterClass
w=0:x=0:y=0:z=0
w=1:x=2:y=3:z=4
Outer: OuterClass
Outer: 1
Outer: 2
Outer: 3
Outer: 4
[NestedClassTest.java, OuterClass.java]
[NestedClassTest.class, OuterClass$1.class, OuterClass$1Logger.class, OuterClass$InnerClass.class, OuterClass$StaticNestedClass.class, OuterClass.class]

OuterClassがコンパイルされると、内部クラス、ローカル内部クラス、および静的ネストクラスのために別々のクラスファイルが作成されることに注意してください。

Java内部クラスの利点

  1. クラスが1つのクラスにのみ有用である場合、そのクラスをネストして一緒に保持することは合理的です。これにより、クラスのパッケージ化が促進されます。
  2. Javaの内部クラスはカプセル化を実装します。内部クラスは外部クラスのプライベートメンバーにアクセスでき、同時に内部クラスを外部の世界から隠すことができます。
  3. 小さなクラスをトップレベルクラスの中に保持すると、コードが使用される場所に近くなり、コードがより読みやすく保守可能になります。

これでJava内部クラスに関する説明は終わりです。

Source:
https://www.digitalocean.com/community/tutorials/java-inner-class