
참고
자바의 정석
내부 클래스는 클래스 내에 클래스를 선언한다는 점을 제외하곤 일반 클래스와 다른 점이 없다. 내부 클래스는 사용빈도가 그렇게 높지가 않아서 어떻게 사용하는지만 알면 문제 없을 것 같다.
내부 클래스란 클래스 내에 선언된 클래스이다. 그러면 왜 클래스 내에 클래스를 선언할까? 그 이유는 바로 클래스간에 긴밀한 관계때문에 선언을 하는 것이다.
내부 클래스를 선언하면 두 클래스들의 멤버간의 접근이 용이하고 외부에는 불필요한 클래스들을 감춤으로 코드의 복잡성을 줄일 수 있다.
내부 클래스의 장점
- 내부 클래스에서 외부 클래스의 멤버를 쉽게 접근할 수 있다.
- 코드의 복잡성을 줄일 수 있다. (캡슐화)
내부 클래스의 종류는 변수의 선언위치에 따른 종류와 같다. 내부 클래스는 마치 변수 선언하는 것과 같은 위치에 선언이 가능하며 변수의 선언 위치에따라 인스턴스 변수, 클래스 변수, 지역변수로 나눠진 것처럼 내부 클래스도 같다. 또한 유효범위 자체도 변수와 유사함으로 아래의 표를 잘 살펴보자.
| 내부 클래스 | 특 징 |
|---|---|
| 인스턴스 클래스 | 외부 클래스의 멤버변수 선언위치에 선언하며, 외부 클래스의 인스턴스 멤버 처럼 다루어 진다. 주로 외부 클래스의 인스턴스 멤버들과 관련죈 작업에 사용 될 목적으로 선언된다. |
| 스태틱 클래스 | 외부 클래스의 멤버변수 선언위치에 선언하며, 외부 클래스의 static 멤버처럼 다루어진다. 주로 외부 클래스의 static멤버, 특히 static 메서드에서 사용될 목적으로 선언된다. |
| 지역 클래스 | 외부 클래스의 메서드나 초기화 블럭 안에 선언하며, 선언된 영역 내부에서만 사용될 수 있다. |
| 익명 클래스 | 클래스의 선언과 객체의 생성을 동시에 하는 이름 없는 클래스 (일회용) |
변수의 선언된 위치에 따라 인스턴스 변수, 클래스 변수, 지역변수로 나누듯이 내부 클래스도 이와 마찬가지로 선언된 위치에 따라 나뉜다. 그리고 내부 클래스의 선언위치에 따라 같은 선언위치의 변수와 동일한 유효범위와 접근성을 갖는다.
class Outer {
class InstanceInner {} // 인스턴스 클래스
static class StaticInner {} // 스태틱 클래스
void myMethod() {
class LocalInner {} // 지역 클래스
}
}
인스턴스 클래스와 스태틱 클래스는 외부 클래스의 멤버변수 선언위치에 선언되며, 멤버변수와 같은 성질을 갖는다. 즉, 내부 클래스가 외부 클래스의 멤버와 같이 취급이 되며, 인스턴스 멤버와 static 멤버 간의 규칙이 내부 클래스와 똑같이 적용된다.
class Outer {
private class InstanceInner {}
protected static class StaticInner {}
void myMethod() {
class LocalInner {}
}
}
그리고 내부 클래스도 클래스이기 때문에 abstract, final과 같은 키워드도 사용가능하며 접근제어자도 사용이 가능하다.
그리고 주의할 점은 내부 클래스 중에서 스태틱 클래스만 static 멤버를 가질 수 있다. 단, final과 static이 동시에 붙은 변수는 상수이므로, 모든 내부 클래스에서 정의 가능하다.
또한, 인스턴스 클래스는 외부 클래스의 인스턴스 멤버를 객체 생성 없이 바로 사용할 수 있지만, 스태틱 클래스는 외부 클래스의 인스턴스 멤버를 객체생성 없이 사용할 수 없다.
인스턴스 클래스는 스태틱 클래스의 멤버들을 객체생성 없이 사용할 수 있지만 스태틱 클래스에서는 인스턴스 클래스의 멤버들을 객체 생성없이 사용이 가능하다.
종합해서 다시한번 정리해보자. 인스턴스 클래스는 외부 클래스의 인스턴스 멤버이기 때문에 인스턴스 변수 사용이 가능하다. 심지어 인스턴스 변수의 접근제어자가 private이라도 사용이 가능하다.
스태틱 클래스는 외부 클래스의 static 멤버이기 때문에 외부 클래스의 인스턴스 멤버는 사용이 불가능 하다. 단지 static 멤버만 사용이 가능하다.
지역 클래스는 외부 클래스의 인스턴스 멤버, 스태틱 멤버 둘다 사용이 가능하다. 또한 지역 클래스가 속한 메서드의 지역 변수도 사용이 가능하다. 단, final이 붙은 지역변수만 사용이 가능하다. 그 이유는 메서드가 수행을 마쳐서 지역변수가 소멸된 시점에도, 지역 클래스의 인스턴스가 소멸된 지역변수를 참조하려는 경우가 발생하기 때문이다. 하지만, JDK1.8부터 내부 클래스가 속한 메서드의 지역변수에 final이 생략이 가능하다. 대신, 컴파일러가 대신 붙여준다. 즉, 모든 지역변수 참조가 가능하게 된 것이다.
만약 다른 클래스에서 내부 클래스들을 정의하고 다른 클래스에서 접근하려면 어떻게 할까? 이 부분은 아래의 예제를 보면 확실히 알 것이다.
class Outer {
class InstanceInner {
int iv = 100;
}
static class StaticInner {
int iv = 200;
static int cv = 300;
}
void myMethod() {
class LocalInner {
int iv = 400;
}
}
}
public class InnerEx4 {
public static void main(String[] args) {
Outer oc = new Outer();
Outer.InstanceInner il = oc.new InstanceInner();
System.out.println("il.iv = " + il.iv);
System.out.println("Outer.StaticInner.cv = " + Outer.StaticInner.cv);
Outer.StaticInner si = new Outer.StaticInner();
System.out.println("si.iv = " + si.iv);
}
}
익명 클래스는 특이하게 다른 내부 클래스들과 같이 이름이 없다. 클래스의 선언과 객체의 생성을 동시에 하기 때문에 단 한번만 사용될 수 있고 오직 하나의 객체만을 생성할 수 있는 일회용 클래스이다.
new 상위 클래스이름() {
// 멤버 선언
}
or
new 구현 인터페이스이름() {
// 멤버 선언
}
익명 클래스는 이름이 없는 클래스이기 때문에 생성자도 가질 수 없으며, 상위 클래스의 이름이나 구현한 인터페이스 이름으로 정의하기 때문에 하나의 클래스로 상속받는 동시에 인터페이스를 구현하거나 둘 이상의 인터페이스를 구현할 수 없다. 오직 단 하나의 인터페이스만을 구현할 수 있다.
여기서 알아야 할 점은 익명클래스는 나중에 람다 표현식으로 쉽게 바꿀 수 있다.