J-S-12 중첩 클래스부터 익명 객체
글 정보
- 카테고리
- Programming/Java/Starter
- 태그
- JavaLevel1
1. 중첩 클래스 (Nested Class)
개요 및 정의
객체화가 필요한 대상이 존재하지만, 그 사용 범위가 특정 클래스 내부로 제한될 때 사용합니다.
클래스 내부에 또 다른 클래스를 선언하는 형태입니다.
설계 목적
논리적 그룹화 (Logical Grouping)
- 다른 객체가 필요하지만, 특정 클래스와 긴밀한 관계를 맺고 있을 때 하나로 묶습니다.
캡슐화 (Encapsulation)
- 외부에는 그 존재를 감추고, 내부적인 구현 용도로만 사용하고 싶을 때 유용합니다.
종류
1. 정적 중첩 클래스 (Static Nested Class)
- 외부 클래스 내부에 있지만 사실상 독립적인 클래스처럼 동작합니다.
2. 비정적 중첩 클래스 (Non-static / Inner Class)
- Inner Class (일반 내부 클래스)
- Local Class (지역 클래스)
- Anonymous Class (익명 클래스/객체)
2. 정적 중첩 클래스 (Static Nested Class)
특징
외부 클래스의 인스턴스 없이도 생성할 수 있습니다.
외부 클래스의 private 정적 멤버(static member)에는 접근할 수 있습니다.
하지만 외부 클래스의 인스턴스 멤버에는 접근할 수 없습니다.
호출 방법
선언 시 클래스 내부에 static 키워드를 붙여 정의합니다.
호출 시에는 온점(.)을 사용하여 외부클래스.내부클래스 형태로 접근하는 것이 핵심입니다.
코드 예시
public class Outer {
private static String staticData = "공용 데이터";
private String instanceData = "개별 데이터";
// static 키워드를 붙여 정의합니다.
public static class StaticNested {
public void print() {
// 1. 외부의 private static 멤버에는 접근 가능합니다.
System.out.println("접근 가능: " + staticData);
// 2. 외부의 인스턴스 멤버에는 접근할 수 없습니다. (컴파일 에러 발생)
// System.out.println(instanceData);
}
}
}
public class Main {
public static void main(String[] args) {
// 3. 외부 클래스의 객체(new Outer())를 만들지 않아도 바로 생성할 수 있습니다.
// 문법: new 외부클래스.내부클래스();
Outer.StaticNested nested = new Outer.StaticNested();
nested.print();
}
}
3. 내부 클래스 (Inner Class)
특징
외부 클래스의 인스턴스에 종속된 클래스입니다.
외부 클래스의 private 멤버를 포함한 모든 멤버에 자유롭게 접근할 수 있습니다.
참조 및 생성 방법
내부 클래스 안에서 외부 클래스의 인스턴스를 명시적으로 가리킬 때는 외부클래스명.this 형식을 사용합니다.
생성 방식이 다소 독특합니다.
- 내부 클래스는 외부 클래스의 '멤버' 취급을 받습니다.
- 따라서 반드시 외부 클래스의 객체를 먼저 생성한 후, 그 참조변수를 통해 내부 클래스를 생성해야 합니다.
코드 예시
public class Outer {
private String name = "외부 클래스 데이터";
public class Inner {
private String name = "내부 클래스 데이터";
public void print() {
// 1. 자신의 멤버 접근 (this 생략 가능)
System.out.println("Inner: " + this.name);
// 2. 외부 클래스의 멤버 접근 (Outer.this 문법 사용)
// 이름이 겹칠 때 명시적으로 외부 인스턴스를 가리키는 방법입니다.
System.out.println("Outer: " + Outer.this.name);
}
}
}
public class Main {
public static void main(String[] args) {
// 1. 외부 클래스의 인스턴스를 먼저 생성해야 합니다.
Outer outer = new Outer();
// 2. 생성된 외부 객체(outer)를 통해 내부 객체를 생성합니다.
// 문법: 참조변수.new 내부클래스();
Outer.Inner inner = outer.new Inner();
inner.print();
}
}
4. 지역 클래스 (Local Class)
정의
메서드 바디(블록) 내부에 정의하는 클래스입니다.
특징 및 수명 문제
익명 객체의 원리를 이해하기 위한 필수 이론입니다.
메서드 내부의 지역 변수에 접근할 수 있습니다.
- 단, 해당 변수는
final이거나effectively final(사실상 상수로 취급되는 변수)이어야 합니다.
[변수 캡처링 (Variable Capturing)]
- 지역 변수(Stack 영역)는 메서드가 종료되면 메모리에서 사라집니다.
- 반면, 생성된 객체 인스턴스(Heap 영역)는 GC 전까지 더 오래 살아남을 수 있습니다.
- 이 수명 불일치를 해결하기 위해, 인스턴스 생성 시 필요한 변수 값을 복사해두는 과정을 '캡처링'이라고 합니다.
코드 예시
public class LocalClassExample {
public void execute() {
// 1. 지역 변수 (Stack 영역): 메서드가 끝나면 사라질 운명입니다.
int localVar = 100;
// 2. 지역 클래스 선언: 메서드 블록({}) 내부에서만 존재합니다.
class LocalPrinter {
public void print() {
// 3. 변수 캡처링 (Variable Capturing)
// 사라질 localVar의 값을 객체 내부로 복사해옵니다.
// 따라서 localVar는 변경되지 않는 'final' 상태여야 합니다.
System.out.println("Captured Value: " + localVar);
}
}
// 객체 생성 및 메서드 호출
LocalPrinter printer = new LocalPrinter();
printer.print();
}
}
5. 내부 인터페이스 (Inner Interface)
개요
클래스 내부에 interface를 선언하는 형태입니다.
주로 정적(static) 멤버 인터페이스로 활용됩니다.
활용 예시: 이벤트 리스너
GUI 개발(Android, Swing 등)에서 이벤트 처리를 규격화하기 위해 자주 사용됩니다.
사용자의 클릭(Click) 등의 이벤트를 감지하고, 이에 대응하는 핸들러(Handler) 코드를 작성할 때 일정한 규격을 제공합니다.
- 이는 GUI 프레임워크의 가장 보편적인 형태입니다.
코드 예시
public class Button {
// 1. 내부 인터페이스 선언 (규격 정의)
// 클래스 내부에 선언된 인터페이스는 자동으로 static으로 간주됩니다.
public interface OnClickListener {
void onClick();
}
private OnClickListener listener;
// 2. 외부에서 구현한 동작(Handler)을 받아옵니다.
public void setListener(OnClickListener listener) {
this.listener = listener;
}
// 버튼이 눌렸을 때, 등록된 동작을 실행합니다.
public void touch() {
if (listener != null) {
listener.onClick();
}
}
}
public class Main {
public static void main(String[] args) {
Button btn = new Button();
// 3. 인터페이스 구현 및 주입
// "클릭되면 무슨 일을 할지"를 여기서 결정합니다.
btn.setListener(new Button.OnClickListener() {
@Override
public void onClick() {
System.out.println("버튼이 클릭되었습니다!");
}
});
btn.touch(); // 실행 결과: 버튼이 클릭되었습니다!
}
}
6. 익명 객체 (Anonymous Object)
정의
이름이 없는 지역 클래스(Local Class)입니다.
특정 클래스를 상속받거나, 인터페이스를 구현하는 객체를 선언과 동시에 생성합니다.
목적 및 장점
클래스를 별도로 정의하기에는 재사용성이 없고, 딱 한 번만 사용할 때 유용합니다.
하나의 구문 안에 클래스 정의와 생성이 포함되어 코드가 간결해집니다.
GUI 프로그래밍에서 이벤트 처리 코드를 획기적으로 줄여줍니다.
특징
이름이 없기 때문에 생성자(Constructor)를 가질 수 없습니다.
코드 예시
public class AnonymousExample {
// 예제를 위한 간단한 인터페이스
interface Greeter {
void sayHello();
}
public static void main(String[] args) {
// 1. 클래스 이름 없이, 인터페이스를 구현하는 동시에 객체를 생성합니다.
// 문법: new 인터페이스이름() { ... 구현 내용 ... };
Greeter guest = new Greeter() {
@Override
public void sayHello() {
System.out.println("안녕하세요! 저는 이름 없는 객체입니다.");
}
};
// 2. 생성된 객체 사용
guest.sayHello();
}
}