클래스 내부에 선언한 클래스
클래스가 여러 클래스와 관계를 맺는 경우에는 독립적으로 선언하는 것이 좋지만, 특정 클래스만 관계를 맺을 경우에는 중첩 클래스로 선언하는 것이 유지보수에 도움이 될 수 있다.
중첩 클래스를 사용하면 클래스의 멤버를 쉽게 사용할 수 있고, 외부에는 중첩 관계 클래스를 감춤으로써 코드의 복잡성을 줄일 수 있다는 장점이 있다.
| 클래스 명 | 선언 위치 | 객체 생성 조건 |
|---|---|---|
| 멤버 클래스(인스턴스) | 클래스의 멤버로서 선언 | A객체를 생성해야만 B객체를 생성할 수 있다 |
| 멤버 클래스(static) | 클래스의 멤버로서 선언 | A객체를 생성하지 않아도 B객체를 생성할 수 있다 |
| 로컬 클래스 | method 내부에서 선언 | method가 실행할 때만 B객체를 생성할 수 있다 |
중첩 클래스도 하나의 클래스이기 때문에 컴파일 과정에서 바이트코드 파일(.class)가 별도로 생성된다.
바깥클래스 $ 중첩클래스.class
위와 같은 형태로 클래스명이 결정된다.
[public] class A{
//인스턴스 클래스 A
[public | private] class B{
//중첩 인스턴스 클래스 B
}
}
| 구분 | 접근 범위 |
|---|---|
public class B{} | 다른 패키지에서 B 클래스를 사용할 수 있다 |
class B{} | 같은 패키지에서만 B 클래스를 사용할 수 있다 |
private class B{} | A 클래스 내부에서만 B 클래스를 사용할 수 있다 |
클래스 내부에서 사용되는 경우 보통 private 접근 제한을 가지는 것이 일반적이다. A 클래스 내부 어디서든 B 클래스를 생성할 수 있지만, 항상 A 객체가 먼저 존재해야만 B 객체를 생성할 수 있다. B객체는 A객체에 소속되어 생성되므로, B객체를 만들기 전에 반드시 A객체를 먼저 생성해야 한다.
만약 B객체를 A클래스 외부에 생성하기 위해서는 default 또는 public 접근 제한을 가져야 하고, A객체를 먼저 생성한 다음에 B객체를 생성해야 한다.
A a = new A();
A.B b = a.new B();
static 키워드와 함께 외부 클래스의 멤버로 선언된 내부 클래스
[public] class A{
[public | private] static class B{
}
}
static 멤버 클래스는 외부 클래스 내부에서 사용되기도 하지만, 외부 클래스 외부에서 A와 함께 사용되는 경우가 많기 때문에 주로 default 또는 public의 접근 제한을 가진다.
A클래스 외부에서 B객체를 생성하기 위해서는 A객체 생성 없이 A클래스로 접근해서 B객체를 생성할 수 있다.
A.B b = new A.B();
생성자 또는 method 내부에서 다음과 같이 선언된 클래스
[public] class A{
//생성자
public A(){
class B{} //로컬클래스
}
//method
public void method(){
class B{} //로컬클래스
}
}
로컬 클래스 내부에서 사용하는 로컬 클래스는 final의 특성을 가진다.
로컬 클래스 객체는 method가 끝난 뒤에도 Heap 메모리에 살아남아 있을 수 있다. 하지만 로컬 변수는 method 호출이 끝나면 바로 Stack 메모리에서 사라진다. 이 경우 로컬 클래스 객체는 Heap에 계속 살아남아있고, 그 안에서 method의 로컬 변수에 접근하려고 하면 사라진 로컬 변수를 참조하려는 오류가 발생한다.
로컬 클래스에서 사용하는 외부 지역 변수는 절대 변경될 수 없다.
로컬 변수를 읽기 전용으로만 사용하도록 제한하기 위해 final 특성을 부여했다. 이를 통해 메모리 안정성을 확보한다.
중첩 클래스는 바깥 클래스와 긴밀한 관계를 맺으면서 바깥 클래스의 멤버에 접근할 수 있다.
| 구분 | 바깥 클래스의 사용 가능한 멤버 |
|---|---|
| 인스턴스 멤버 클래스 | 바깥 클래스의 모든 필드와 method |
| static 멤버 클래스 | 바깥 클래스의 static 필드와 정적 method |
static 멤버 클래스는 바깥 객체가 없어도 사용 가능해야 하므로, 외부 클래스의 인스턴스 필드와 인스턴스 method는 사용하지 못한다.
인스턴스 멤버를 사용하려면 반드시 어떤 객체를 참조해야만 한다.
중첩 클래스 내부의 this는 해당 중첩 클래스를 가리킨다. 만약 중첩 내부 클래스에서 바깥 내부 클래스의 객체를 얻기 위해서는 네이밍 충돌이 발생할 수 있기 때문에 바깥 클래스의 이름에 this를 붙여주면 된다.
this //중첩 클래스를 가리킴
바깥클래스이름.this //바깥객체를 가리킴
클래스의 멤버로 선언된 인터페이스
class A{
[public | private] [static] interface B{
//상수 필드
//abstract method
//디폴트 method
//static method
}
}
해당 버튼이 눌렸을 때 무엇을 할지는 버튼 자신이 결정하지 않고, 외부에서 전달해준 객체가 대신 처리한다. Button 안에 중첩 인터페이스를 정의해서, 리스너 객체는 이런 method를 구현해야 한다는 틀을 만들어준다.
중첩 인터페이스는 암시적으로 static이기 때문에 static을 생략해도 항상 A 객체 없이도 B 인터페이스를 사용할 수 있다.
package ch09.sec06_multi.exam03;
public class Button {
//정적 멤버 인터페이스
**public static interface ClickListener** {
//abstract method
void onClick();
}
//필드
private **ClickListener** clickListener;
//method
public void **setClickListener**(ClickListener clickListener) {
**this.clickListener = clickListener;**
}
public void **click()** {
this.clickListener.onClick();
}
}
package ch09.sec06_multi.exam03;
public class ButtonExample {
public static void main(String[] args) {
//Ok 버튼 객체 생성
Button btnOk = new Button();
//Ok 버튼 클릭 이벤트를 처리할 ClickListener 구현 클래스(로컬 클래스)
class OkListener implements Button.ClickListener {
**@Override
public void onClick() {
System.out.println("Ok 버튼을 클릭했습니다.");
}**
}
//Ok 버튼 객체에 ClickListener 구현 객체 주입
btnOk.setClickListener(new OkListener());
//Ok 버튼 클릭하기
btnOk.click();
//Cancel 버튼 객체 생성
Button btnCancel = new Button();
//Cancel 버튼 클릭 이벤트를 처리할 ClickListener 구현 클래스(로컬 클래스)
class CancelListener implements Button.ClickListener {
@Override
public void onClick() {
System.out.println("Cancel 버튼을 클릭했습니다.");
}
}
//Cancel 버튼 객체에 ClickListener 구현 객체 주입
btnCancel.setClickListener(new CancelListener());
//Cancel 버튼 클릭하기
btnCancel.click();
}
}
public이면서 Button 객체 없이 사용할 수 있는 static 중첩 인터페이스로 ClickListener로 선언하고 그 내부에 abstract method onClick()을 선언했다.Button클래스에 ClickListener 타입의 필드와 Setter를 추가해서 외부에서 Setter를 통해 ClickListener 구현 객체를 필드에 저장할 수 있도록 한다.Button이 클릭됐을 때 실행할 method로 click()을 추가한다. ClickListener 인터페이스 필드를 이용해서 onClick() abstract method를 호출한다.Setter를 통해 제공된 ClickListener 구현 객체의 참조를 갖고 있다. onClick() method를 호출하면 ClickListener 구현 객체의 onClick() method가 실행된다.이름이 없는 객체
명시적으로 클래스를 선언하지 않기 때문에 쉽게 객체를 생성할 수 있다. 주로 필드값, 로컬 변수값, 매개변수값으로 사용된다.
클래스를 상속하거나 인터페이스를 구현해야만 생성할 수 있다.
부모 클래스를 상속해서 만든 객체
new 부모생성자(매개값, ...){
//필드
//method
}
부모 타입의 필드, 로컬 변수, 매개변수의 값으로 대입할 수 있다.
익명 자식 객체 내에 선언된 멤버는 중괄호 블록 내에서만 사용이 가능하다. 주로 부모 method를 재정의하는 코드가 온다.
//익명 자식 객체가 대입된 매개변수 사용
car.run3(new Tire() {
@Override
public void roll() {
System.out.println("익명 자식 Tire 객체 3이 굴러갑니다.");
}
});
인터페이스 타입의 필드, 로컬변수, 매개변수의 값으로 대입할 수 있다. 안드로이드와 같은 UI 프로그램에서 이벤트를 처리하는 객체로 많이 사용된다.
new 인터페이스(){
//필드
//method
}
익명 구현 객체의 멤버는 중괄호 블록 안에서만 사용이 가능하다. 익명 구현 객체는 인터페이스 타입에 대입되므로 인터페이스 타입에 선언된 멤버만 접근할 수 있다. 중괄호 안에는 주로 인터페이스의 abstract method를 재정의하는 코드가 온다.
package ch09.sec07_noname.exam02;
public interface RemoteControl {
//abstract method
void turnOn();
void turnOff();
}
package ch09.sec07_noname.exam02;
public class Home {
//필드에 익명 구현 객체 대입
private RemoteControl rc **= new RemoteControl() {
@Override
public void turnOn() {
System.out.println("TV를 켭니다.");
}
@Override
public void turnOff() {
System.out.println("TV를 끕니다.");
}
};**
//method(필드 이용)
public void use1() {
rc.turnOn();
rc.turnOff();
}
//method(로컬 변수 이용)
public void use2() {
//로컬 변수에 익명 구현 객체 대입
RemoteControl rc = new RemoteControl() {
@Override
public void turnOn() {
System.out.println("에어컨을 켭니다.");
}
@Override
public void turnOff() {
System.out.println("에어컨을 끕니다.");
}
};
rc.turnOn();
rc.turnOff();
}
//method(매개변수 이용)
public void use3(RemoteControl rc) {
rc.turnOn();
rc.turnOff();
}
}
package ch09.sec07_noname.exam02;
public class HomeExample {
public static void main(String[] args) {
//Home 객체 생성
Home home = new Home();
//익명 구현 객체가 대입된 필드 사용
home.use1();
//익명 구현 객체가 대입된 로컬 변수 사용
home.use2();
//익명 구현 객체가 대입된 매개변수 사용
home.use3(new RemoteControl() {
@Override
public void turnOn() {
System.out.println("난방을 켭니다.");
}
@Override
public void turnOff() {
System.out.println("난방을 끕니다.");
}
});
}
}
