[Codeit Review]/객체 지향 프로그래밍

[04] 내부 클래스와 익명 클래스, 그리고 열거 타입(ENUM)의 활용

uzlru 2026. 1. 26. 15:18

01. 내부 클래스 (Inner Class)

1. 내부 클래스

내부 클래스란 클래스 내부에 선언된 또 다른 클래스를 뜻한다. 내부 클래스는 외부 클래스 멤버에 자유롭게 접근할 수 있으며, 밀접하게 관련된 기능을 함께 묶어 설계하는 것을 돕는다. 또한 내부 클래스를 활용하면 코드의 응집도를 높이고 캡슐화를 강화할 수 있다.

 

class Outer {			// 외부 클래스

	class Inner {
				// 인스턴스 내부 클래스
	}
}

 

2. 내부 클래스를 사용하는 이유

내부 클래스를 사용하는 이유  
외부 클래스와 밀접한 기능을 묶기 위해 두 클래스가 강하게 연관된 관계라면 외부 클래스 내부에 클래스를 선언하는 것이 자연스러움
캡슐화 (정보 은닉) 강화 내부 클래스를 private, protected 로 선언하면 외부에서 접근 불가능
응집도 증가 및 유지보수 향상 관련된 클래스를 물리적으로 하나의 파일 내부에 위치시켜 가독성 및 관리성 향상 가능
>> GUI, 이벤트 처리 등에서 자주 사용

 

// 1. 밀접한 기능 묶기 예제: 버튼 클릭 이벤트 리스너는 버튼 클래스 외부에서 사용되지 않으므로 내부에 정의
class Button {
    class ClickListener {
        void onClick() {
            System.out.println("버튼이 클릭됨");
        }
    }
}

// 2. 캡슐화 (정보 은닉) 강화 예제
class Outer {
    private class HiddenHelper {
        void help() {
            System.out.println("숨겨진 내부 로직 수행");
        }
    }
}

 

3. 내부 클래스의 종류

  인스턴스 내부 클래스
(Instance Inner Class)
정적 내부 클래스
(Static Nested Class)
지역 내부 클래스
(Local Inner Class)
익명 내부 클래스
(Anomous Inner Class)
선언 위치 외부 클래스의 멤버 위치 외부 클래스의 멤버 위치 외부 클래스의 메서드
초기화 블록 내부
동시에 클래스 정의 + 인스턴스 생성
외부 클래스 접근 인스턴스 변수 접근 가능 static 변수만 접근 가능 finall / effectively final
지역 변수만 접근 가능
클래스 멤버 접근 가능
특징 외부 클래스와 강한 결합 static 키워드 사용
높은 독립성
메서드 내에서만 사용 이름 없는 일회성 클래스

 

// 1. 인스턴스 내부 클래스
public class Outer {
    private String outerField = "Outer Field";

    class Inner {
        void print() {
            System.out.println(outerField);
        }
    }

    public static void main(String[] args) {
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner();
        inner.print();
    }
}

// 2. 정적 내부 클래스
public class Outer {
    private static String staticField = "Static Field";

    static class StaticInner {
        void print() {
            System.out.println(staticField); 
        }
    }

    public static void main(String[] args) {
        Outer.StaticInner inner = new Outer.StaticInner();
        inner.print(); // 출력: Static Field
    }
}

// 3. 지역 내부 클래스
public class Outer {
    void method() {
        final String localVar = "Local Variable";

        class LocalInner {
            void print() {
                System.out.println(localVar);
            }
        }

        LocalInner inner = new LocalInner();
        inner.print();
    }

    public static void main(String[] args) {
        new Outer().method();
    }
}

4. 익명 내부 클래스
interface Greeting {
    void sayHello();
}

public class Outer {
    public static void main(String[] args) {
        Greeting greeting = new Greeting() {
            public void sayHello() {
                System.out.println("Hello from anonymous class");
            }
        };

        greeting.sayHello();
    }
}

 

 

 

 

 


 

02. 맴버 내부 클래스

멤버 내부 클래스란 클래스의 멤버처럼 외부 클래스의 내부에 선언된 클래스를 뜻한다. 변수와 마찬가지로, 인스턴스 내부 클래스(non-static)과 정적 내부 클래스(static)으로 구분된다.

 

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

인스턴스 내부 클래스는 외부 클래스의 인스턴스가 있어야만, 즉 외부 클래스의 객체를 생성한 후에 사용할 수 있는 클래스로, 주로 외부 클래스의 내부에서 사용된다. 또한 모든 외부 클래스 멤버에 접근하는 것이 가능하지만, 정적 멤버의 선언은 불가능하다.

 

class Outer {
    private int num = 1;         		// 외부 클래스의 인스턴스 변수
    private static int sNum = 2; 		// 외부 클래스의 static 변수

    private InClass inClass;     		// 내부 클래스 자료형 변수 선언

    public Outer() {
        inClass = new InClass(); 		// 외부 클래스 생성자에서 내부 클래스 초기화
    }

    class InClass {
        int inNum = 10; 			// 내부 클래스의 인스턴스 변수

        void test() {
            System.out.println("Outer num = " + num + "(외부 클래스의 인스턴스 변수)");
            System.out.println("Outer sNum = " + sNum + "(외부 클래스의 정적 변수)");
        }
    }

    public void testClass() {
        inClass.test();
    }
}

public class Main {
    public static void main(String[] args) {
        Outer outer = new Outer();
        System.out.println("외부 클래스를 통해 내부 클래스 메서드 호출");
        outer.testClass();
    }
}

 

 

2. 정적 내부 클래스 (Static Inner Class)

정적 내부 클래스는 외부 클래스의 인스턴스 없이도 생성 가능한 클래스를 뜻한다. 외부 클래스의 static 멤버와 메서드에만 접근할 수 있지만, 일반 멤버와 메서드에는 접근하는 것은 불가능하다. 주로 정적 컨텍스트에서 코드 분리와 유틸성 클래스 구현에 자주 활용된다.

 

class Outer {
    private int num = 3;         		// 외부 클래스의 인스턴스 변수
    private static int sNum = 4; 		// 외부 클래스의 static 변수

    void getPrint() {
        System.out.println("인스턴스 메서드");
    }

    static void getPrintStatic() {
        System.out.println("스태틱 메서드");
    }

    static class StaticInClass {
        void test() {
            System.out.println("Outer sNum = " + sNum + "(외부 클래스의 정적 변수)");
            getPrintStatic(); 			// static 메서드 호출 가능
            // System.out.println(num);         // 인스턴스 멤버 접근 불가
            // getPrint();                      // 인스턴스 메서드 접근 불가
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Outer.StaticInClass a = new Outer.StaticInClass();	// 외부 클래스 인스턴스 없이 사용
        a.test();
    }
}

 

3. 비교표

  인스턴스 내부 클래스 정적 내부 클래스
외부 클래스 인스턴스 필요 필요함 필요 없음
접근 가능한 외부 멤버 인스턴스 + 정적 정적만
static 멤버 선언 가능 불가능 가능
생성 방법 예시 new Outer().new Inner() new Outer.StaticInner()

 


 

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

지역 내부 클래스란 외부 클래스의 메서드 내에 선언된 클래스를 뜻한다. 선언된 메서드 내에서만 유효하기 때문에 외부 클래스의 인스턴스 변수와 해당 메서드의 지역 변수에만 접근할 수 있으며, 일반적으로 해당 메서드 안에서 정의 → 객체 생성 → 사용의 흐름으로 처리된다.

 

이때 메서드의 지역 변수는 반드시 fianl이거나 effectively final로 선언되어야 한다. 특히 effective final이란 final 키워드를 명시하지 않아도, 처음 값이 할당된 이후 해당 값이 변경되지 않은 변수를 뜻한다.

 

class Outer {
    int num = 5; 				// 외부 클래스의 인스턴스 변수

    void test() {
        int num2 = 6; 				// 지역 변수

        class LocalInClass {
            void getPrint() {
                System.out.println(num);  	 // 외부 클래스 인스턴스 변수 접근
                System.out.println(num2); 	 // 지역 변수 접근 (final 또는 effectively final)
            }
        }

        LocalInClass localInClass = new LocalInClass(); // 지역 내부 클래스 객체 생성
        localInClass.getPrint();
    }
}

public class Main {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.test(); 					// 지역 내부 클래스 실행
    }
}

 

지역 내부 클래스 특징  
선언 위치 외부 클래스의 메서드 또는 초기화 블록 내부
접근 가능한 변수 외부 클래스 인스턴스 변수 / 메서드 내 final 또는 effectively final 변수
접근 제어자 사용 불가능
사용 범위 선언된 메서드 내에서만 사용 가능
실무 활용 익명 클래스, 람다 표현식과 함께 다뤄질 수 있으며, 주로 이벤트 핸들링, 콜백, 임시 도우미 클래스 등에서 자주 사용

 


 

04. 익명 클래스 (Anonymous Class)

익명 클래스란 이름이 없는 클래스를 뜻한다. 이러한 익명 클래스는 별도의 클래스를 만들지 않고도 간단하게 원하는 동작을 구현할 수 있기 때문에 일회성으로 사용될 클래스를 정의할 때 유용하며, 주로 인터페이스나 추상 클래스의 구현체를 즉석에서 정의하거나 이벤트 처리, 콜백 처리 등에 자주 활용된다. 외부 메서드의 final 또는 effective final 변수에만 접근할 수 있다.

 

또한 Java 8 이후, 람다식을 이용하여 익명 클래스를 구현하는 것이 가능해졌는데, 이때 람다식으로 표현하고자 하는 익명 클래스는 반드시 메서드가 하나 선언된 인터페이스어야 한다.

 

// 1-1. 기본 형식 인터페이스 ver
인터페이스타입 변수 = new 인터페이스타입() {
    // 추상 메서드 구현
};

// 1-2. 기본 형식 추상 클래스 ver
추상클래스 변수 = new 추상클래스() {
    // 추상 메서드 구현
};

// 2-1. 버튼 클릭 리스너 예제
Button btn = new Button();

btn.setOnClickListener(new OnClickListener() {
    public void onClick() {
        System.out.println("클릭됨");
    }
});

// 2-2. 버튼 클릭 리스너를 람다로 변환
btn.setOnClickListener(() -> System.out.println("클릭됨"));

 

 

익명 클래스  
정의 방식 클래스 선언과 객체 생성을 동시에
주요 사용처 이벤트 처리, 콜백, 임시 구현 등
장점 간결함, 코드 줄임
단점 재사용 어려움, 긴 코드엔 부적합
람다식과의 관계 Java 8 이후 함수형 인터페이스에서 람다로 대체 가능

 


 

05. 열거 타입 (ENUM)

열거 타입(ENUM)이란 상수들의 집합을 하나의 타입으로 승격시킨 것을 의미한다. 이러한 열거 타입은 static final 상수가 갖는 문제점을 해결할 수 있으며, 특히 switch문에서 유용하게 사용된다.

 

열거 타입 클래스 내부의 상수들은 모두 대문자 스네이크 케이스로 작성해야 한다. 각 상수는 public static final 형태의 상수처럼 동작하지만, 일반 상수가 아닌 고유한 Enum 인스턴스라는 차이가 있으며, 문자열과 달리 비교 연산자 (== . !=)를 통한 비교가 가능하다.

 

또한 열거 타입 클래스 또한 하나의 객체로 취급되므로, 필드 및 생성자, 메서드를 구현하는 것이 가능하다. 만약 열거 타입 클래스 내부에 필드를 선언했다면, priavate 접근 제어자를 갖는 생성자를 필수로 생성해야 한다.

 

// 1. 기본 형태 예제
public enum Day {
    MONDAY,    		// 월요일
    TUESDAY,   		// 화요일
    WEDNESDAY, 		// 수요일
    THURSDAY,  		// 목요일
    FRIDAY,    		// 금요일
    SATURDAY,  		// 토요일
    SUNDAY     		// 일요일
}

// 2. 필드, 메서드, 생성자가 정의된 예제
public enum OrderStatus {
    READY("RD"),       			// 준비중 -> 괄호 안에 줄임말 삽입
    IN_PROGRESS("IP"), 			// 진행중
    DONE("DN");       			// 완료됨

    private final String code; 		// 각 Enum 상수의 고유 코드값

    OrderStatus(String code) {		// 생성자
        this.code = code;
    }

    public String getCode() {		// getter 메서드
        return code;
    }
}

 

1. 열거 타입을 사용하는 이유

static final 상수의 위험성  
타입 안전성 제로 말도 안 되는 값이 들어와도 컴파일러가 감지할 수 없음
불분명한 값의 의미 상수에 할당된 숫자의 의미를 파악하기 어려움
오타에 취약 문자열 관리 시, 오타로 인한 컴파일 에러 발생 가능
  타입 안전성
(Type Safety)
상수 그룹화 IDE 지원
정의 해당 Enum 타입에 정의된 값만 대입 가능 관련 있는 상수를 하나의 집합으로 묶음 자동완성 기능 강력
효과 엉뚱한 값 할당 시 컴파일 에러 코드의 구조가 명확해져 관리 용이 오타 방지

 

2. 열거 타입 메서드

메서드   실무 사용 여부
name() Enum 상수의 정확한 이름을 문자열로 반환 조건부 사용
ordinal() Enum 상수가 정의된 순서를 0부터 반환
>> 순서가 바뀌면 값도 바뀌기 떄문에 위험도가 높음
사용 비추천
values() 모든 상수들을 선언된 순서대로 배열에 담아 반환 조건부 사용