DEV/Java

[java] 중첩 클래스(Nested Class)의 이해와 구현

Bi3a 2023. 10. 9. 17:16

728x90

중첩 클래스(Nested Class)의 이해와 구현
java 기초 깨부시기

 

 

같이 보면 좋은 글 (static 개념의 이해)

 

[java 기초] 기타 제어자(static, final, abstract)의 이해와 구현

Table of Contents 주인장 학습용 포스팅입니다. 열람에 참고해주세요 [java 기초] java final, static + private 1. final 접근제한자 final : final로 선언된 클래스, 메소드, 변수는 수정 불가능하게끔 그 값과 의

doinitright.tistory.com

 


 

중첩 클래스

중첩 클래스의 정의

말 그대로 클래스 내부에 클래스가 선언된 형태를 중첩 클래스라 합니다.
프로그램에서 구조적으로 클래스 간의 계층이 필요할 때 사용합니다.

 

 

중첩 클래스의 장점

  • 캡슐화로 인한 장점을 얻을 수 있습니다.
    • 관련 있는 클래스를 계층 구조로 묶어 유지보수성이 증가
    • 외부에서 불필요한 내부 클래스에 접근할 수 없어 코드 복잡성과 가독성이 증가

 

 

중첩 클래스의 구조

외부에 선언되어 있는 클래스와 그 내부에 선언되어 있는 클래스의 이중구조입니다.
class Outer { // 외부
	// .. 생략
    
    class Nested { // 중첩
    // .. 생략 
    }

}

 

 

중첩 클래스의 종류

중첩 클래스는 선언부에 붙은 키워드와, 선언 위치에 따라 총 4가지 종류로 나뉩니다.

 

중첩 클래스의 종류
중첩 클래스, 크게 4가지로 나뉩니다.

분류 기준

  1. 중첩 클래스가 정적(static)으로 선언되었는가 → 정적 중첩 (Static Nested) 클래스
  2. 중첩 클래스가 비정적(non-static)으로 선언되었는가
    1. 중첩 클래스가 외부 클래스의 필드나 메서드처럼 선언되었는가 → 인스턴스 내부 (Instance Inner) 클래스
      • 인스턴스 클래스, 멤버 (Member) 클래스, Inner 클래스 등 여러가지로 불림
    2. 중첩 클래스가 외부 클래스 메서드나 초기화 블록 내부에서 지역변수처럼 선언되었는가 → 지역 (Local) 클래스
    3. 외부 클래스 객체의 선언과 동시에 오버라이딩해 중첩 클래스를 선언하는가 →  익명(Anonymous) 클래스

 

 

중첩 클래스별 구현

정적 중첩 클래스(Static Nested Class)

내부 클래스가 static으로 선언된 경우 정적 중첩 클래스라고 합니다.
주로 외부 클래스의 클래스 메소드로서 사용될 목적으로 선언됩니다.

 

 

특징

 

1. 외부 클래스의 객체를 생성하지 않고, 바로 정적 내부 클래스의 객체를 생성해 사용할 수 있습니다.

    * static으로 인해 클래스 멤버로서 프로그램 시작 후 메모리에 이미 올라가 있기 때문

package JavaPractice;

class Outer {
    /* 1. 정적 중첩 클래스 */
    static class StaticNested {
        void exec(){
            System.out.println("I am Static Nested Class");
        }
    }
}

public class NestedClassTest {
    public static void main(String[] args) {
        /* 1. 정적 중첩 클래스 */
        // 객체 생성 시 외부 클래스 객체 생성이 필요 없음
            // → 이미 static으로 인해 클래스 멤버로 메모리에 올라가 있기 때문
        // 생성 방법 : 외부클래스.내부클래스 참조변수 = new 외부 클래스.내부클래스();
        Outer.StaticNested sn = new Outer.StaticNested();
        sn.exec(); // out : "I am Static Nested Class;
    }
}

 

 

2. 외부 클래스의 static 멤버에만 접근할 수 있습니다. (메모리를 할당받는 시점의 차이 문제로 인해)

   * static 필드는 프로그램 시작 시 바로 할당받지만, 일반 멤버는 객체 생성 후 할당받기 때문

package JavaPractice;

class Outer {
    int a = 10;
    static int sa = 10;
    /* 1. 정적 중첩 클래스 */
    static class StaticNested {
        // 정적 클래스이므로 외부 클래스의 정적 멤버에만 접근 가능하다.
        void printA(){
//          System.out.println(a); // 컴파일 에러 : a는 non-static이므로 접근 불가
            System.out.println(sa);
        }
    }
}

public class NestedClassTest {
    public static void main(String[] args) {
        /* 1. 정적 중첩 클래스 */
        Outer.StaticNested sn = new Outer.StaticNested();
        sn.printA(); // out : 10
    }
}

 

 

인스턴스 내부 클래스 (Instance Inner Class)

내부 클래스가 non-static으로 선언된 경우 인스턴스 클래스라고 합니다.
내부 클래스 (Inner Class) 용어 자체가 해당 개념으로 통칭되는 경우가 있습니다.
* 용이한 설명을 위해 인스턴스 내부 클래스로 설명하겠음

다른 명칭으로는 멤버 클래스, 멤버 이너 클래스, 내부 중첩 클래스, 비정적 중첩 클래스 등이 있습니다.

 

 

특징

 

1. 생성을 위해 외부 클래스 객체를 먼저 생성 후에 내부 클래스를 생성할 수 있습니다.

package JavaPractice;

class Outer {
    /* 2. 인스턴스 내부 클래스 */
    class Instance {
        void exec() {
            System.out.println("I am Instance Inner Class");
        }
    }
}

public class NestedClassTest {
    public static void main(String[] args) {
        /* 2. 인스턴스 내부 클래스 */
        // 객체 생성 시 외부 클래스 객체를 먼저 생성함
        // 생성 방법 : 외부클래스.내부클래스 참조변수 = new 외부클래스.내부클래스();
        Outer outer = new Outer();
        Outer.Instance is = outer.new Instance();
        is.exec(); // out : "I am Instance Inner Class"
    }
}

 

2. 외부 클래스에서 멤버와 같은 기능을 하므로 외부 클래스 멤버로의 접근이 가능합니다.

package JavaPractice;

class Outer {
    int a = 10;

    /* 2. 인스턴스 내부 클래스 */
    class Instance {
        // 외부 클래스의 멤버 변수 접근이 가능하다.
        void printA(){
            System.out.println(a);
        }
    }
}

public class NestedClassTest {
    public static void main(String[] args) {
        Outer outer = new Outer();
        Outer.Instance is = outer.new Instance();
        is.printA(); // out : 10
    }
}

 

 

지역 내부 클래스 (Local Inner Class)

외부 클래스의 메소드나 초기화 블록으로 인해 선언되는 클래스입니다.
선언된 영역 내부에서만 사용되고 메서드가 종료될 시 삭제됩니다.
클래스 내부에서 지역 변수와 같은 역할을 합니다.
  * 예)  for(int i loop)에서 int i와 같은 역할 : loop 종료 시 i는 삭제

 

 

지역 내부 클래스의 구현 방법은 두가지가 있습니다.

 

 

1. 외부 클래스의 메소드 내부에서 선언하는 방법

package JavaPractice;

class Outer {
    /* 3. 지역 내부 클래스 */
    // 생성 방법 : 클래스 구현부를 통한 구현(메소드, 생성자)

    // 3-1. 메소드 내부에서 생성되는 지역 내부 클래스
    // 외부 클래스 객체 생성 후 메소드 호출 시 지역 클래스가 생성, 메소드가 실행
    void createLocal(){
        class Local{
            String s = "I am Local Inner Class, created by Method";
            void exec(){
                System.out.println(s);
            }
        }
        // 메소드 내부에서 지역 클래스가 생성, 메모리 로딩된 후 메소드 종료 시 삭제된다.
        Local local = new Local();
        local.exec();
    }
}

public class NestedClassTest {
    public static void main(String[] args) {
     	/* 3. 지역 내부 클래스 */
        // 외부 클래스 객체 생성 후 지역 내부 클래스를 생성하는 메소드, 생성자 호출 시 메모리에 로딩
        Outer outer1 = new Outer(); 
        outer1.createLocal(); // I am Local Inner Class, created by Method
    }
}

 

2. 외부 클래스의 생성자 내부에서 선언하는 방법

package JavaPractice;

class Outer {
    /* 3. 지역 내부 클래스 */
    // 생성 방법 : 클래스 구현부를 통한 구현(메소드, 생성자)

    // 3-2. 생성자를 통해서 생성되는 지역 내부 클래스
    // 외부 클래스 객체 생성과 동시에 지역 클래스가 생성, 메소드가 실행
    Outer(){
        class Local{
            void exec(){
                System.out.println("I am Local Inner Class, created by Constructor");
            }
        }
        Local local = new Local();
        local.exec();
    }
}

public class NestedClassTest {
    public static void main(String[] args) {
        /* 3. 지역 내부 클래스 */
        // 외부 클래스 객체 생성 후 지역 내부 클래스를 생성하는 메소드, 생성자 호출 시 메모리에 로딩
        Outer outer1 = new Outer(); // I am local Inner Class, created by Constructor
    }
}

 

 

익명 내부 클래스 (Anonymous Inner Class)

지역 클래스의 변형된 형태로, 클래스명을 가지지 않으며 객체 생성과 메서드 선언만을 정의합니다.
클래스 선언과 동시에 객체 생성이 이뤄져 일회용으로 사용되는 클래스이며, 사용된 이후 메모리에서 삭제됩니다.

일회성이므로 딱 한번만 사용되기 위해 선언되는 클래스이며,
주로 인터페이스나 추상 클래스의 메서드 내용을 구현하는 클래스로서 많이 활용됩니다.

 

 

익명 클래스의 구현 유형은 총 2가지입니다.

 

 

1. 일회성으로 선언되어 추상 클래스 / 인터페이스의 메서드를 구현

package JavaPractice;
/* 4. 익명 내부 클래스 */
// 4-1. 익명 내부 클래스를 통해 일회성으로 구현할 인터페이스
abstract interface abInterface{
    abstract void method();
}

public class NestedClassTest {
    public static void main(String[] args) {
        /* 4. 익명 내부 클래스 */
        // 객체 생성과 동시에 메소드 정의(오버라이딩)을 통해 사용되고 제거되는 클래스

        // 4-1. 익명 클래스를 활용한 인터페이스의 메소드를 일회성으로 구현
        abInterface ano1 = new abInterface() {
            @Override
            public void method() {
                System.out.println("I am anonymous class, from Interface");
            }
        }; // 세미콜론을 붙여 익명 클래스의 선언이 끝났음을 표현
        ano1.method(); // out : "I am anonymous class, from Interface"
    }
}

 

2. 일회성으로 선언되어 외부 클래스의 메소드를 구현

  •  익명 클래스에서의 외부 변수는 final인 경우 참조가 가능합니다. (final로 선언된 변수의 무결성이 보장되므로)
  •  일반 외부 변수도 참조가 가능하나, 익명 클래스 동작 이후 해당 변수의 값이 변화하면 컴파일 에러가 발생합니다.
package JavaPractice;

class Outer {
    /* 4. 익명 내부 클래스 */
    // 4-2. 익명 내부 클래스를 통해 일회성으로 구현할 부모 클래스 메소드
    void exec() {
    }
}

public class NestedClassTest {
    public static void main(String[] args) {
        // 4-2. 익명 클래스를 활용한 외부 클래스의 메소드 오버라이딩
        // ★ 익명 클래스는 final로 선언된 외부 변수를 참조할 수 있다.
        int finalValue = 40;
        int externalValue = 30;
        Outer outer2 = new Outer() {
            @Override
            void exec() {
                System.out.println("I am anonymous class, from Outer method");
                System.out.println("I can read external finalValue : " + finalValue);
                
                // 익명 클래스에서 참조한 외부 변수의 값을 변경 시 컴파일 에러
                // 사실상 해당 변수는 final로 동작됨
                // System.out.println("I can read ExternalValue : " + externalValue);
            }
        }; // 세미콜론을 붙여 익명 클래스의 선언이 끝났음을 표현
        outer2.exec();
        // Out : "I am anonymous class, from Outer method"
        // Out : "I can read external finalValue : 40"
        
        // 외부 변수 값의 변화로 인해 externalValue는 익명 클래스 내부에서 참조 불가
        externalValue = 31;
    }
}

 

 


# 구현 코드

클릭 시 깃허브로 이동합니다.