클래스 안에서 클래스를 중첩해 정의하는 것을 중첩 클래스라고 한다
중첩 클래스는 클래스를 정의하는 위치에 따라 분류한다
내부 클래스 (inner class)
↓ 코드 블록에 선언
지역 클래스 (local class)
↓ 이름 없는 클래스
익명 클래스 (anonymous class)
바깥 인스턴스 소속
정적 중첩 클래스 (static nested class)
전혀 다른 인스턴스
중첩 클래스를 정의하는 위치는 변수 선언 위치와 같다
class Outer {
static class StaticNested {
// 정적 중첩 클래스
}
class Inner {
// 내부 클래스
}
}
class Outer {
public void process() {
class Local {
// 지역 클래스
}
}
}
모든 중첩 클래스는 특정 클래스가 특정 클래스 안에서만 사용되거나
아주 긴밀하게 연결되어 있는 특별한 경우에만 사용한다
여러 클래스가 사용한다면 중첩 클래스로 만들지 않는다
static 으로 선언되기 때문에
자신이 선언된 외부 클래스의 멤버에에 직접 접근할 수 없다
static 은 클래스 레벨로 인스턴스 영역이 아닌 메서드 영역의 메모리에 올라간다
class Outer {
private static outClassValue = 0;
private outInstanceValue = 0;
// 정적 중첩 클래스
static class Nested {
private int nestedInstanceValue = 0;
public void print () {
System.out.println(nestedInstanceValue);
// 자신의 멤버에 접근
System.out.println(outInstanceValue);
// 외부 클래스 인스턴스 멤버에는 접근할 수 없다
System.out.println(Outer.outClassValue);
// 외부 클래스의 클래스 멤버에는 private 로 작성되었더라도 접근 가능
}
}
}
public static void main(String[] args) {
Outer outer = new Outer(); // 외부 클래스 생성
Outer.Nested nested = new Outer.Nested(); // 중첩 클래스 생성
}
중첩 클래스의 목적은 자신이 작성된 외부 클래스 내에서만 사용하는 것이기 떄문에
다른 클래스에서 접근을 해야한다면 중첩 클래스로 작성하지 않는 편이 좋다
내부 클래스는 외부 클래스의 인스턴스에 소속된다
Outer outer = new Outer();
Outer.Inner = outer.new Inner(); // 외부 클래스 참조를 통해 생성자 호출
중첩 클래스는 하나의 클래스가 다른 클래스 안에서만 사용되거나
긴밀하게 연결되어 있는 경우에만 사용한다
외부 클래스와 내부 클래스의 인스턴스 변수 이름이 같은 경우
Outer.this.value
지역 클래스는 내부 클래스의 특별한 종류 중 하나
외부 클래스의 메서드 내에 내부 클래스를 작성하는 것으로
메서드 내에서 지역 클래스의 인스턴스를 생성하여 사용
지역 클래스가 접근하는 지역 변수의 값은 변경되면 안된다
지역 클래스에서 지역 변수를 사용한다면
인스턴스의 생명 주기가 지역 변수의 생명 주기보다 짧기 때문에
지역 클래스는 지역 변수를 사용할 수 없어야 한다
하지만 자바에서는 이런 문제를 해결하기 위해
변수 캡쳐를 지원한다
인스턴스를 생성하는 시점에 필요한 지역 변수를 복사해
생성한 인스턴스와 함께 넣어둔다
이 때 접근이 필요한 변수만을 캡쳐하여 두었다가 사용한다
지역 클래스가 접근하는 지역 변수는 절대 중간에 값이 변하면 안된다
따라서 final 로 선언하거나 effectively final 이어야 한다
중간에 값이 변한다면
인스턴스에서 변수 캡처를 한 값과
지역 변수의 값이 달라지는 문제가 생기기 떄문이다
이 것을 동기화 문제라고 한다
지역 클래스인데 이름이 없는 클래스
익명 클래스는 선언과 생성을 한 번에 처리할 수 있다
인터페이스를 구현 하면서 구현체의 인스턴스를 바로 만든다
public interface Printer {
void print();
}
public class Outer {
private int outInstalceVar = 3;
public void process() {
int localVar = 1;
Printer printer = new Printer(int paramVar) {
int value = 0;
@Override
public void print() {
System.out.println(value);
System.out.println(localVar);
System.out.println(paramVar);
System.out.println(outInstanceVar);
}
}
}
}
내부 클래스와 기능은 같은데
이름 없이 간결하게 작성할 수 있다는 장점이 있다
new Interface() {...}
클래스를 별도로 정의하지 않고 인터페이스나 추상 클래스를 즉시 구현할 수 있어
코드가 간결해지지만 복잡하거나 재사용이 필요한 경우 사용하지 않는다
public interface Printer {
void print();
}
public class Outer {
private int outInstalceVar = 3;
// 상황에 따라 변하는 값은 매개변수로 받아서 활용
public void process(String str) {
int localVar = 1;
// 익명 클래스에서 참조하는 변수는 변하지 않는 값만을 활용
Printer printer = new Printer(int paramVar) {
int value = 0;
@Override
public void print() {
System.out.println(value);
System.out.println(localVar);
System.out.println(paramVar);
System.out.println(outInstanceVar);
}
}
}
}