Notice
Recent Posts
Recent Comments
Link
«   2026/03   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Tags more
Archives
Today
Total
관리 메뉴

프로그래밍 공부

[자바] #16. 네스티드(Nested) 클래스, 이너(Inner) 클래스 본문

자바

[자바] #16. 네스티드(Nested) 클래스, 이너(Inner) 클래스

하 냥 2025. 2. 19. 17:59

🔍 네스티드 클래스란?

네스티드(Nested) 클래스는 다른 클래스 내부에 정의된 클래스.

코드의 가독성캡슐화를 높이고, 논리적으로 관련된 클래스를 묶을 수 있도록 만들어준다.

외부 클래스의 멤버와 밀접하게 연관된 기능을 캡슐화할 때 사용한다.


🏗️ 네스티드 클래스의 종류

분류 설명 예시 키워드
정적 네스티드 클래스 외부 클래스의 인스턴스 없이 사용 가능한 클래스 static class
비정적(내부) 클래스 외부 클래스의 인스턴스와 연관된 클래스 class, private class, public class 등

내부 클래스 (Inner Class)의 세부 분류

  • 인스턴스 내부 클래스 (Instance Inner Class) = 멤버 클래스 (Member Class)
  • 로컬 클래스 (Local Class, 메서드 내부에 정의)
  • 익명 클래스 (Anonymous Class, 이름이 없는 클래스)

✍ 정적 네스티드 클래스 (Static Nested Class)

🔧 특징

  • static 키워드를 사용하여 정의된다.
  • 외부 클래스의 인스턴스 없이 사용된다.
  • 정적 멤버(static field/static method)에만 접근할 수 있다.
  • 정적 문맥에서 외부 클래스와 관계를 맺는다.
public class OuterClass {
    private static String staticVar = "정적 변수";

    static class StaticNestedClass {
        void display() {
            System.out.println("정적 네스티드 클래스 접근: " + staticVar);
        }
    }

    public static void main(String[] args) {
        // 외부 클래스의 인스턴스 없이 직접 사용
        StaticNestedClass nested = new StaticNestedClass();
        nested.display();
    }
}
정적 네스티드 클래스 접근: 정적 변수

💡 언제 사용해야 할까?

  • 외부 클래스와 논리적으로 관련 있지만, 외부 클래스의 인스턴스와 직접적인 관계가 필요 없는 경우
  • 예: 빌더 패턴, 유틸리티 클래스 정의 시 자주 사용된다.

 

🌈 인스턴스 내부 클래스 (Inner Class) = 멤버 클래스 (Member Class)

🔧 특징

  • 외부 클래스의 인스턴스에 종속된다.
  • 외부 클래스의 모든 멤버(static/non-static)에 접근할 수 있다.
  • static 멤버를 가질 수 없다.
interface Displayable {
	void display();
}

public class OuterClass {
    private String outerField = "외부 클래스 필드";
    
	public Displayable getDisplay() {
		return new InnerClass(); // 멤버 클래스 인스턴스 생성 후 반환
    }
    
    private class InnerClass implements Displayable {
        void display() {
            System.out.println("내부 클래스 접근: " + outerField);
        }
    }

    public static void main(String[] args) {
        OuterClass outer = new OuterClass();
        Displayable dis = outer.getDisplay();
        dis.display();
    }
}
내부 클래스 접근: 외부 클래스 필드

💡 언제 사용해야 할까?

  • 내부 클래스가 외부 클래스의 인스턴스 데이터와 밀접하게 연관되어야 할 때
  • 클래스의 정의를 감추어야 할 때
  • 예: GUI 컴포넌트 처리, 콜백 핸들링

 

클래스의 정의를 감추어야 할 때 추가 설명)

위 예제에서와 같이 멤버 클래스가 private으로 선언되면, 이 클래스 정의를 감싸는 클래스 내에서만 인스턴스를 생성할 수 있다. 그래서 InnerClass 인스턴스는 다음과 같은 방법으로만 참조가 가능하다.

public static void main(String[] args) {
        OuterClass outer = new OuterClass();
        Displayable dis = outer.getDisplay();
}

이제 OuterClass 클래스의 외부에서는 getDisplay 메서드가 어떠한 인스턴스의 참조값을 반환하는지 모른다.

반환되는 참조값의 인스턴스가 Displayable을 구현하고 있기 때문에, Displayable의 참조변수로 참조할 수 있다는 사실만 알 뿐이다. 이러한 상황을 '클래스의 정의가 감추어졌다'라고 한다.

이렇게 클래스의 정의를 감추면, getDisplay 메서드가 반환하는 인스턴스가 다른 클래스의 인스턴스로 바뀌어도 OuterClass 클래스 외부의 코드는 조금도 수정할 필요가 없다. (코드에 유연성 부여!)


🔥 로컬 클래스 (Local Class)

🔧 특징

  • 메서드, 생성자, 블록 내부에 정의된다.
  • 지역 변수에 접근할 수 있으나, 해당 변수는 final이거나 실질적 final(effective final)이어야 한다.
  • 외부 클래스의 멤버에도 접근할 수 있다.

멤버 클래스와 로컬 클래스는 정의된 위치에 따라 구분할 수 있다.

class Outer {
	class Member {} // 멤버 클래스
    
    void method() {
    	class Local {} // 로컬 클래스
    }
}

 

📄 예제: 로컬 클래스

public class OuterClass {
    void localClassExample() {
        final String localVar = "지역 변수";

        class LocalClass {
            void display() {
                System.out.println("로컬 클래스 접근: " + localVar);
            }
        }

        LocalClass local = new LocalClass();
        local.display();
    }

    public static void main(String[] args) {
        OuterClass outer = new OuterClass();
        outer.localClassExample();
    }
}
로컬 클래스 접근: 지역 변수

💡 언제 사용해야 할까?

  • 특정 메서드 내에서만 사용되는 임시적 클래스 정의가 필요할 때
  • 예: 메서드의 보조 기능 구현, 이벤트 처리

⚡ 익명 클래스 (Anonymous Class)

🔧 특징

  • 이름이 없는 클래스.
  • 즉석에서 클래스 선언과 객체 생성을 동시에 수행한다.
  • 주로 인터페이스추상 클래스의 메서드를 즉각 구현할 때 사용한다.
  • 람다식과 유사하지만, 람다식은 함수형 인터페이스에만 사용된다.
interface Greeting {
    void sayHello();
}

public class AnonymousClassExample {
    public static void main(String[] args) {
        Greeting greeting = new Greeting() {
            @Override
            public void sayHello() {
                System.out.println("안녕하세요! 익명 클래스입니다.");
            }
        };

        greeting.sayHello();
    }
}
안녕하세요! 익명 클래스입니다.

💡 언제 사용해야 할까?

  • 클래스를 재사용할 필요가 없고, 짧은 코드로 특정 작업을 수행할 때.
  • 예: 이벤트 핸들러, 콜백 구현

익명 클래스와 람다 표현식의 차이점

public class Test {
    String name = "Outer";

    void run() {
        Runnable r1 = new Runnable() {
            String name = "Inner";
            public void run() {
                System.out.println(this.name); // 👉 Inner
            }
        };

        Runnable r2 = () -> {
            // String name = "Lambda"; // 변수 shadowing 안 됨
            System.out.println(this.name); // 👉 Outer
        };

        r1.run();
        r2.run();
    }
}

 

✅ 언제 뭘 쓰면 좋을까?

 

상황 추천
추상 메서드가 2개 이상 필요 익명 클래스
메서드 하나만 구현 (예: Runnable, Comparator 등) 람다식
바깥 클래스의 this를 쓰고 싶다 람다식
클래스 이름 없이 빠르게 객체 만들고 싶다 둘 다 가능, 람다 선호 (가독성 ↑)

 

익명 클래스는 "클래스를 정의하고 그 자리에서 객체 생성",
람다식은 "함수를 간단히 표현"하는 함수형 스타일 문법.


💎 비교 요약

종류 특징 외부 클래스 접근성 사용 시점
정적 네스티드 클래스 static, 외부 인스턴스 불필요 static 멤버만 접근 가능 외부 클래스와
독립적 기능 정의
멤버 클래스 외부 클래스의 인스턴스와
종속 관계
외부 멤버 접근 가능 외부 인스턴스 데이터에
밀접 연관
로컬 클래스 메서드 내 정의, 실질적 final 지역 변수 접근 가능 외부 멤버 접근 가능 메서드 내 한정적 기능 구현
익명 클래스 이름 없음, 즉석 구현 외부 멤버 접근 가능 일회성 구현
(인터페이스/추상 클래스)

❓ 질문

정적 네스티드 클래스와 인스턴스 내부 클래스의 차이점은 무엇인가?

  • 정적 네스티드 클래스는 외부 인스턴스 없이 사용 가능하지만, 인스턴스 내부 클래스는 외부 인스턴스와 연결된다.

로컬 클래스가 메서드의 지역 변수에 접근할 때 final이 필요한 이유는 무엇인가?

  • 메서드 종료 후에도 로컬 클래스가 참조할 수 있도록 지역 변수의 불변성을 보장하기 위해서이다.

🌈 실전 사용 예제: 정적 네스티드 클래스를 활용한 빌더 패턴

public class Person {
    private final String name;
    private final int age;

    private Person(Builder builder) {
        this.name = builder.name;
        this.age = builder.age;
    }

    static class Builder {
        private String name;
        private int age;

        public Builder setName(String name) {
            this.name = name;
            return this;
        }

        public Builder setAge(int age) {
            this.age = age;
            return this;
        }

        public Person build() {
            return new Person(this);
        }
    }

    @Override
    public String toString() {
        return name + ", " + age + "세";
    }

    public static void main(String[] args) {
        Person person = new Person.Builder()
                            .setName("홍길동")
                            .setAge(30)
                            .build();
        System.out.println(person);
    }
}
홍길동, 30세

정적 네스티드 클래스(Builder) 를 활용해 외부 클래스(Person)의 인스턴스를 안전하게 생성.