본문으로 건너뛰기
Advertisement

6.5 내부 클래스 (Inner Class)

클래스 안에 또 다른 클래스를 정의하는 것을 내부 클래스(Inner Class) 라고 합니다. 외부에서는 불필요하고 특정 클래스와 강하게 연관된 코드를 논리적으로 그룹화할 때 사용합니다.

내부 클래스의 종류

종류선언 위치특징
멤버 내부 클래스클래스 멤버로외부 클래스의 모든 멤버에 접근 가능
정적 내부 클래스static 붙여서외부 클래스의 static 멤버만 접근 가능
지역 내부 클래스메서드 내부에해당 메서드 안에서만 사용
익명 내부 클래스클래스 선언과 동시에 인스턴스 생성일회성 구현에 사용

1. 멤버 내부 클래스

가장 일반적인 형태입니다. 외부 클래스의 모든 멤버(private 포함) 에 접근할 수 있습니다.

public class Outer {
private String outerField = "외부 클래스 필드";

class Inner {
void display() {
// 외부 클래스의 private 필드에도 접근 가능!
System.out.println("내부에서 접근: " + outerField);
}
}
}

// 사용 방법: 외부 클래스 객체를 먼저 만들어야 함
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.display(); // 내부에서 접근: 외부 클래스 필드

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

static이 붙은 내부 클래스입니다. 외부 클래스 인스턴스 없이 사용할 수 있습니다. 외부 클래스의 static 멤버에만 접근할 수 있습니다.

public class Database {
private static String url = "jdbc:mysql://localhost";

// 정적 내부 클래스: Builder 패턴에서 자주 사용
static class Builder {
private String host = "localhost";
private int port = 3306;
private String dbName;

Builder host(String host) { this.host = host; return this; }
Builder port(int port) { this.port = port; return this; }
Builder dbName(String db) { this.dbName = db; return this; }

Database build() {
return new Database(host, port, dbName);
}
}

private Database(String host, int port, String dbName) {
System.out.printf("연결: %s:%d/%s%n", host, port, dbName);
}
}

// 사용: 외부 클래스 객체 없이 바로 사용 가능
Database db = new Database.Builder()
.host("myserver.com")
.port(5432)
.dbName("shop")
.build();

실무에서 가장 많이 쓰이는 형태입니다. Builder 패턴, Entry 클래스(예: Map.Entry<K,V>)가 대표적입니다.


3. 지역 내부 클래스

메서드 안에서 선언되며, 그 메서드 안에서만 사용할 수 있습니다. 잘 사용되지 않으며, 대부분의 경우 람다식으로 대체됩니다.

public class LocalInnerExample {
void doSomething() {
final int localVar = 100; // effectively final이어야 함

class LocalHelper {
void help() {
System.out.println("지역 변수 사용: " + localVar);
}
}

LocalHelper helper = new LocalHelper();
helper.help(); // 지역 변수 사용: 100
}
}

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

이름 없이 선언과 동시에 인스턴스를 만드는 클래스입니다. 인터페이스나 추상 클래스를 일회성으로 구현할 때 사용합니다. 자바 8 이후로는 대부분 람다식으로 대체되었습니다.

interface Greeting {
void greet(String name);
}

public class AnonymousExample {
public static void main(String[] args) {

// 익명 내부 클래스 (Java 8 이전 방식)
Greeting formal = new Greeting() {
@Override
public void greet(String name) {
System.out.println("안녕하세요, " + name + " 님.");
}
};

// 람다식 (Java 8+ 권장)
Greeting casual = name -> System.out.println("안녕, " + name + "!");

formal.greet("홍길동"); // 안녕하세요, 홍길동 님.
casual.greet("철수"); // 안녕, 철수!
}
}

실전 예제: 이벤트 핸들러 (Swing 스타일)

import javax.swing.*;
import java.awt.event.*;

JButton button = new JButton("클릭!");

// 익명 클래스로 버튼 클릭 이벤트 처리
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("버튼이 클릭되었습니다!");
}
});

// 람다식으로 동일하게 (Java 8+)
button.addActionListener(e -> System.out.println("버튼이 클릭되었습니다!"));

고수 팁

내부 클래스 선택 가이드:

  • 멤버 내부 클래스: 외부 인스턴스의 상태에 강하게 의존할 때 (예: 반복자 구현)
  • 정적 내부 클래스: Builder 패턴, 외부 클래스와 논리적으로 연관된 헬퍼 클래스 → 가장 많이 쓰임
  • 익명 내부 클래스: 람다가 지원되지 않는 인터페이스(추상 메서드 2개 이상) 또는 레거시 코드
  • 람다식: 함수형 인터페이스(추상 메서드 1개)를 구현할 때는 무조건 람다 사용 권장

메모리 주의: 멤버 내부 클래스 인스턴스는 외부 클래스 인스턴스에 대한 참조를 암묵적으로 보유합니다. 외부 객체를 오래 살아남게 하여 메모리 누수를 일으킬 수 있으므로, 외부 인스턴스가 필요 없다면 항상 static nested class를 선택하세요.

Advertisement