프로그래밍 공부
[자바] #16. 네스티드(Nested) 클래스, 이너(Inner) 클래스 본문
🔍 네스티드 클래스란?
네스티드(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)의 인스턴스를 안전하게 생성.
'자바' 카테고리의 다른 글
| [자바] #18. 메서드 참조(+ 람다식에서 접근 가능한 참조변수 제한) (0) | 2025.02.20 |
|---|---|
| [자바] #17. 람다(Lambda)와 함수형 인터페이스 (1) | 2025.02.19 |
| [자바] #15. 열거형, 가변 인자, 어노테이션 (0) | 2025.02.19 |
| [자바] #14. Collections 클래스 안의 여러 알고리즘 구현 메서드들 (0) | 2025.02.19 |
| [자바] #13. 컬렉션 프레임워크 - 4. Map (0) | 2025.02.16 |