Java 내부 클래스

Java 내부 클래스는 다른 클래스의 본문 내에 정의됩니다. Java 내부 클래스는 private, public, protected 또는 기본 액세스로 선언될 수 있지만 외부 클래스는 오직 public 또는 기본 액세스만 가질 수 있습니다. Java 중첩 클래스는 두 가지 유형으로 나뉩니다.

  1. 정적 중첩 클래스

    중첩 클래스가 정적인 경우 정적 중첩 클래스라고합니다. 정적 중첩 클래스는 외부 클래스의 정적 멤버에만 액세스 할 수 있습니다. 정적 중첩 클래스는 다른 최상위 클래스와 동일하며, 단지 패키징 편의를 위해 중첩됩니다. 정적 클래스 객체는 다음 문장으로 생성할 수 있습니다.

    OuterClass.StaticNestedClass nestedObject =
         new OuterClass.StaticNestedClass();
    
  2. 자바 내부 클래스

    모든 비정적 중첩 클래스를 자바에서 내부 클래스라고합니다. 자바 내부 클래스는 클래스의 객체와 관련이 있으며 외부 클래스의 모든 변수 및 메서드에 액세스 할 수 있습니다. 내부 클래스는 인스턴스와 관련이 있기 때문에 이러한 클래스에는 정적 변수가 없을 수 있습니다. 자바 내부 클래스의 객체는 외부 클래스 객체의 일부이며 내부 클래스의 인스턴스를 만들려면 먼저 외부 클래스의 인스턴스를 만들어야합니다. 자바 내부 클래스는 다음과 같이 인스턴스화 할 수 있습니다;

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

두 가지 특별한 종류의 자바 내부 클래스가 있습니다.

  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. 익명 내부 클래스

    이름이없는 로컬 내부 클래스는 익명 내부 클래스로 알려져 있습니다. 익명 클래스는 정의 및 인스턴스화가 단일 문에서 이루어집니다. 익명 내부 클래스는 항상 클래스를 확장하거나 인터페이스를 구현합니다. 익명 클래스에는 이름이 없으므로 익명 클래스에 대한 생성자를 정의할 수 없습니다. 익명 내부 클래스는 정의된 지점에서만 액세스할 수 있습니다. 익명 내부 클래스를 어떻게 생성하는지 정의하는 것은 약간 어렵습니다. 아래의 테스트 프로그램에서 실시간 사용법을 살펴보겠습니다.

이것은 자바 내부 클래스, 정적 중첩 클래스, 로컬 내부 클래스 및 익명 내부 클래스를 정의하는 방법을 보여주는 자바 클래스입니다. 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 생성자
    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 중첩 클래스, 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;
    }
}

여기에는 자바에서 내부 클래스를 인스턴스화하고 사용하는 방법을 보여주는 테스트 프로그램이 있습니다. InnerClassTest.java

package com.journaldev.nested;

import java.util.Arrays;
//중첩 클래스는 쉬운 인스턴스화를 위해 import에서 사용할 수 있음
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);
        
        //static 중첩 클래스 예제
        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")));
    }

}

위의 자바 내부 클래스 예제 프로그램의 출력은 다음과 같습니다.

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를 컴파일할 때 내부 클래스, 로컬 내부 클래스 및 static 중첩 클래스에 대해 별도의 클래스 파일이 생성됨에 유의하십시오.

Java 내부 클래스의 이점

  1. 클래스가 하나의 클래스에만 유용한 경우 중첩 및 함께 유지하는 것이 합리적입니다. 클래스의 패키징에 도움이 됩니다.
  2. Java 내부 클래스는 캡슐화를 구현합니다. 내부 클래스는 외부 클래스의 private 멤버에 접근할 수 있으며 동시에 내부 클래스를 외부 세계로부터 숨길 수 있습니다.
  3. 작은 클래스를 최상위 클래스 내에 유지함으로써 코드를 사용하는 곳에 가깝게 유지하고 코드를 더 읽기 쉽고 유지보수하기 쉽게 만듭니다.

자바 내부 클래스에 대한 설명은 여기까지입니다.

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