중첩 클래스 (Nested Class) : 클래스 내부에 선언한 클래스 / 두 클래스 간 멤버들은 서로 쉽게 접근 가능하며 외부에 불필요한 관계 클래스를 감춰 코드의 복잡성을 줄일 수 있음
중첩 인터페이스 : 클래스 내부에 선언된 인터페이스 / 해당 클래스와 긴밀한 관계를 맺는 구현 클래스를 만들기 위해 사용 / 주로 이벤트 처리 목적
1. 중첩 클래스
멤버 클래스 : 클래스의 멤버로서 선언되는 중첩 클래스
로컬 클래스 : 메소드 내부에서 선언되는 중첩 클래스
| 선언 위치에 따른 분류 | 선언 위치 | 설명 | |
| 멤버 클래스 | 인스턴스 멤버 클래스 | class A { class B { ... } } |
A 객체를 생성해야만 사용할 수 있는 B 중첩 클래스 |
| 정적 멤버 클래스 | class A { static class B { ... } } |
A 클래스로 바로 접근할 수 있는 B 중첩 클래스 | |
| 로컬 클래스 | class A { void method() { class B { ... } } } |
method()가 실행할 때만 사용할 수 있는 B 중첩 클래스 | |
- 멤버 클래스, 로컬 클래스 모두 컴파일 시 바이트 코드 파일 (.class)이 별도로 생성됨
인스턴스 멤버 클래스
- static 키워드 없이 선언된 클래스
- 인스턴스 필드와 메소드만 선언 가능 / 정적 필드와 메소드는 선언 불가능
class A {
// 인스턴스 멤버 클래스
class B {
// 생성자
B() {}
// 인스턴스 필드
int field;
// 인스턴스 메소드
void method() {}
}
}
A a = new A();
A.B b = a.new B();
b.field = 3;
b.method();
- A 클래스 외부에서 인스턴스 멤버 클래스 B의 객체를 생성하려면 먼저 A 객체를 생헝하고 B 객체를 생성해야 함
정적 멤버 클래스
- static 키워드로 선언된 클래스
- 모든 종류의 필드와 메소드 선언 가능
class A {
// 정적 멤버 클래스
static class C {
// 생성자
C() {}
// 인스턴스 필드
int field1;
// 정적 필드
static int field2;
// 인스턴스 메소드
void method1() {}
// 정적 메소드
static void method2() {}
}
}
A.C c = new A.C();
c.field1 = 3; // 인스턴스 필드 사용
c.method1(); // 인스턴스 메소드 호출
A.C.field2 = 3; // 정적 필드 사용
A.C.method2(); // 정적 메소드 호출
- A 클래스 외부에서 정적 멤버 클래스 C의 객체를 생성하기 위해서는 A 객체를 생성할 필요가 없음
로컬 클래스
- 메소드 내부에서만 사용 > 접근 제한자 (public, private) 및 static을 붙일 수 없음
- 인스턴스 필드와 메소드만 선언 가능
- 메소드가 실행될 때 메소드 내에서 객체를 생성하고 사용해야 함
void method() {
// 로컬 클래스
class D {
// 생성자
D() {}
// 인스턴스 필드
int field;
// 인스턴스 메소드
void method();
}
D d = new D();
d.field = 3;
d.method();
}
2. 중첩 클래스의 접근 제한
바깥 필드와 메소드에서 사용 제한
public class A {
// 인스턴스 필드
B field1 = new B();
C field2 = new C();
// 인스턴스 메소드
void method1() {
B var1 = new B();
C var2 = new C();
}
// 정적 필드 초기화
// static B field3 = new B();
static C field4 = new C();
// 정적 메소드
static void method2() {
// B var1 = new B();
C var2 = new C();
}
// 인스턴스 멤버 클래스
class B {}
// 정적 멤버 클래스
static class C {}
}
- 인스턴스 멤버 클래스 (B)는 바깥 클래스의 인스턴스 필드 (field1)의 초기값이나 인스턴스 메소드 (method1())에서 객체를 생성할 수 있으나, 정적 필드 (field3)의 초기값이나 정적 메소드 (method2())에서는 객체를 생성할 수 없음
- 정적 멤버 클래스 (C)는 모든 필드의 초기값이나 모든 메소드에서 객체 생성 가능
멤버 클래스에서 사용 제한
public class A {
int field1;
void method1() {}
static int field2;
static void method2() {}
class B {
void method() {
field1 = 10;
method1();
field2 = 10;
method2();
}
}
static class C {
void method() {
// field1 = 10;
// method1();
field2 = 10;
method2();
}
}
}
- 인스턴스 멤버 클래스 (B) 안에서는 바깥 클래스의 모든 필드와 메소드에 접근 가능하지만, 정적 멤버 클래스 (C) 안에서는 바깥 클래스의 정적 필드 (field2)와 정적 메소드 (method2())에만 접근 가능함
로컬 클래스에서 사용 제한
- 로컬 클래스 내부에서는 바깥 클래스의 필드나 메소드를 제한 없이 사용 가능
- 매개 변수나 로컬 변수를 final로 선언하여 수정을 막음 > 로컬 클래스에서 사용 가능한 것은 final로 선언된 매개 변수와 로컬 변수 뿐
- 자바 8 이후 final 선언을 하지 않아도 final 특성을 가짐
중첩 클래스에서 바깥 클래스 참조 얻기
public class Outter {
String field = "Outter-field";
void method() {
System.out.println("Outter-method");
}
class Nested {
String field = "Nested-field";
void method() {
System.out.println("Nested-method");
}
void print() {
System.out.println(this.field);
this.method();
System.out.println(Outter.this.field);
Outter.this.method();
}
}
}
public class Example {
public static void main(String[] args) {
Outter outter = new Outter();
Outter.Nested nested = outter.new Nested();
nested.print();
}
}
// 출력 결과
Nested-field
Nested-method
Outter-field
Outter-method
- 중첩 클래스에서 this 키워드를 사용하면 중첩 클래스의 객체가 참조됨
- 중첩 클래스 내부에서 바깥 클래스의 객체를 참조하려면 바깥 클래스의 이름을 this 앞에 붙이면 됨
3. 중첩 인터페이스
public class Button {
// 인터페이스 타입 필드
OnClickListener listener;
// 매개 변수의 다형성
void setOnClickListener(OnClickListener listener) {
this.listener = listener;
}
// 구현 객체의 onClick() 메소드 호출
void touch() {
listener.onClick();
}
// 중첩 인터페이스
interface OnClickListener {
void onClick();
}
}
public class CallListener implements Button.OnClickListener {
@Override
public void onClick() {
System.out.println("전화를 겁니다.");
}
}
public class Example {
public static void main(String[] args) {
Button btn = new Button();
btn.setOnClickListener(new CallListenr());
btn.touch(); // 전화를 겁니다.
}
}
- 중첩 인터페이스 (OnClickListener) 타입으로 필드 (listener)를 선언하고 Setter 메소드 (setOnClickListener())로 구현 객체를 받아 필드에 대입
- 버튼 이벤트 (touch())가 발생했을 때 인터페이스를 통해 구현 객체의 메소드를 호출(listener.onClick())
4. 익명 객체
- 단독으로 생성할 수 없고 클래스를 상속하거나 인터페이스를 구현해야만 생성 가능
- 필드의 초기값이나 로컬 변수의 초기값, 매개 변수의 매개값으로 주로 대입됨
익명 자식 객체 생성
부모클래스 [필드|변수] = new 부모클래스(매개값, ...) {
// 필드
// 메소드
};
- 자식 클래스가 재사용되지 않고, 오로지 해당 필드와 변수의 초기값으로만 사용할 경우, 익명 자식 객체를 생성해 초기값으로 대입하는 것이 좋음
- 익명 자식 객체 생성문은 하나의 실행문이므로 끝에 세미클론 (;)을 붙여야 함
- 부모클래스(매개값, ...) {}은 중괄호 내부에 필드나 메소드를 선언하거나 부모 클래스의 메소드를 오버라이딩 하여 자식 클래스를 선언함
- 일반 클래스와 차이점은 생성자를 선언할 수 없음
- 메소드의 매개 변수가 부모 타입일 경우 메소드 호출 코드에서 익명 자식 객체를 생성해서 매개값으로 대입 가능
public class Person {
void wake() {
System.out.println("7시 기상");
}
}
public class Anonymous {
// 필드 초기값으로 대입
Person field = new Person() {
void work() {
System.out.println("출근");
}
@Override
void wake() {
System.out.println("6시 기상");
work();
}
};
void method1() {
// 로컬 변수값으로 대입
Person localVar = new Person() {
void walk() {
System.out.println("산책");
}
@Override
void wake() {
System.out.println("7시 기상");
walk();
}
};
// 로컬 변수 사용
localVar.wake();
}
void method2(Person person) {
person.wake();
}
}
public class Example {
public static void main(String[] args) {
Anonymous anony = new Anonymous();
// 익명 객체 필드 사용
anony.field.wake(); // 6시 기상 출근
// 익명 객체 로컬 변수 사용
anony.method1(); // 7시 기상 산책
// 익명 객체 매개값 사용
anony.method2( // 8시 기상 공부
new Person() {
void study() {
System.out.println("공부");
}
@Override
void wake() {
System.out.println("8시 기상");
study();
}
}
);
}
}
익명 구현 객체 생성
- 구현 클래스가 재사용되지 않고, 오로지 해당 필드와 변수의 초기값으로만 사용하는 경우라면 익명 구현 객체를 초기값으로 대입하는 것이 좋음
인터페이스 [필드|변수] = new 인터페이스() {
// 인터페이스에 선언된 추상 메소드의 실체 메소드 선언
// 필드
// 메소드
};
- 중괄호 {}에는 인터페이스에 선언된 모든 추상 메소드들의 실체 메소드를 작성해야 함
- 추가적으로 필드와 메소드를 선언할 수 있지만, 실체 메소드에서만 사용 가능하고 외부에서는 사용 불가능
public class Button {
// 인터페이스 타입 필드
OnClickListener listener;
// 매개 변수의 다형성
void setOnClickListener(OnClickListener listener) {
this.listener = listener;
}
// 구현 객체의 onClick() 메소드 호출
void touch() {
listener.onClick();
}
// 중첩 인터페이스
interface OnClickListener {
void onClick();
}
}
public class Window {
Button button1 = new Button();
Button button2 = new Button();
Button.OnClickListener listener = new Button.OnClickListener() { // 필드 선언과 초기값 대입
@Override
public void onClick() {
System.out.println("전화를 겁니다.");
}
};
Window() {
button1.setOnClickListener(listener); // 매개값으로 필드 대입
button2.setOnClickListener(new Button.OnClickListenr() { // 매개값으로 익명 구현 객체 대입
@Override
public void onClick() {
System.out.println("메시지를 보냅니다.");
}
});
}
}
public class Example {
public static void main(String[] args) {
Window w = new Window();
w.button1.touch(); // 전화를 겁니다.
w.button2.touch(); // 메시지를 보냅니다.
}
}
- 중첩 인터페이스 (OnClickListener) 타입으로 필드 (listener)를 선언하고 Setter 메소드 (setOnClickListener())로 외부에서 구현 객체를 받아 필드에 대입
- 버튼 이벤트 (touch())가 발생했을 때 인터페이스를 통해 구현 객체의 메소드 (listener.onClick())를 호출
- button1의 클릭 이벤트 처리는 필드로 선언한 익명 구현 객체가 담당
- button2의 클릭 이벤트 처리는 setOnClickListener()를 호출할 때 매개값으로 준 익명 구현 객체가 담당
익명 객체의 로컬 변수 사용
- 익명 객체 내부에서는 바깥 클래스의 필드나 메소드를 제한 없이 사용 가능
- 메소드 내에서 생성된 익명 객체는 메소드 실행이 끝나도 힙 메모리에 존재해서 계속 사용할 수 있지만, 매개 변수나 로컬 변수는 메소드 실행이 끝나면 스택 메모리에서 사라지기 때문에 익명 객체에서 사용할 수 없다는 문제가 발생 > 익명 객체에서 사용된 매개 변수나 로컬 변수는 모두 final 특성을 갖기 때문에 관련 문제를 해결 가능
'언어 > Java' 카테고리의 다른 글
| 예외 처리 (0) | 2023.10.03 |
|---|---|
| 인터페이스 (1) | 2023.08.27 |
| 클래스의 타입 변환과 다형성 (0) | 2023.08.27 |
| 메소드 재정의 (오버라이딩) (0) | 2023.08.24 |
| 상속 (0) | 2023.08.24 |