내부 클래스는 이름 그대로 클래스 내부에 선언된 클래스를 말한다. 이 점을 제외하면 내부 클래스는 일반적인 클래스와 다르지 않다.
🤔 근데 왜 사용하지?
내부 클래스는 두 클래스가 서로 긴밀한 관계에 있을 때 사용한다. 내부에 클래스를 선언하면 두 클래스의 멤버들이 서로 쉽게 접근할 수 있다.
또한, 하나의 클래스 또는 메서드에서만 사용되는 클래스일 때, 내부 클래스를 사용한다.
내부 클래스는 하나의 클래스 또는 메서드에만 사용되는 클래스일 때 사용한다고 했다. 이는 별도의 클래스를 구현하고 객체를 선언해 사용할 필요가 없다는 의미이다.
class Outer { class Inner {
... ...
Inner inner = new Inner(); int i = 0;
int j = inner.i;
} }
일반적으로는 일반 클래스를 두개를 구현하고, Outer
클래스에서 Inner
클래스의 객체를 인스턴스화 하여 사용했을 것이다.
하지만 Inner
클래스가 Outer
클래스에만 사용된다면 굳이 별도의 클래스를 생성할 필요가 없다.
class Outer {
...
class Inner {
...
}
}
이런식으로 패키지를 간소화할 수 있고, 내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근하여 사용할 수 있다.
내부 클래스에도 일반 클래스처럼 접근 제어자를 사용할 수 있다.
☝️ 한가지 일반 클래스와 다른 점이 있다면, 일반 클래스에는
public
,private
만 사용할 수 있지만 내부 클래스는public
,default
,protected
,private
를 다 사용할 수 있다.
이렇게 내부 클래스의 접근 제어자를 private
으로 설정한다면 더 강한 캡슐화를 할 수 있다.
즉, 외부에서 내부 클래스에 접근을 차단하면서, 내부 클래스에서 외부 클래스의 멤버들을 제약 없이 쉽게 접근할 수 있다.
또한, 클래스의 구조를 숨김으로써 코드의 복잡성도 줄어든다.
내부 클래스는 외부 클래스와 밀접한 관계에 있다. 따라서 별도로 작성하여 관리하는 것 보다 하나의 클래스에서 작성하여 관리하면 조금 더 직관적이고 유지보수도 쉽다.
내부 클래스 | 특징 |
---|---|
인스턴스 클래스 | 외부 클래스의 멤버변수 선언 위치에 선언하며, 외부 클래스의 인스턴스 멤버처럼 다뤄진다. |
Static 클래스 | 외부 클래스의 멤버변수 선언 위치에 선언하며, 외부 클래스의 static 멤버처럼 다뤄진다. |
지역 클래스 | 외부 클래스의 메서드나 초기화 블럭 안에 선언하며, 선언된 영역 내부에서만 사용될 수 있다. |
익명 클래스 | 클래스의 선언과 객체의 생성을 동시에 하는 이름없는 클래스로, 1회성 사용만 가능하다. |
class Outer { // 외부 클래스
class Inner {} // 인스턴스 클래스
static class Static {} // static 클래스
void method() {
class Local{} // 지역 클래스
}
}
클래스의 선언이지만 변수의 선언과 다를게 없다는 것을 기억하자!!
한가지 의문점
인스턴스 내부 클래스를 선언하면 경고가 뜬다!
해당 클래스를 static 클래스로 변경하는 것을 권장한다. 찾아보니 다음과 같은 글을 발견했다.
내부 클래스는 static으로 선언 안하면 큰일 난다☝️ 인스턴스 클래스는 static 클래스보다 메모리를 더 사용하고, 느리고, GC 대상에서 빠져 있어 메모리 관리가 안될 수 있기 때문에 인스턴스 클래스를 사용하지 않는다고 한다.
따라서 내부 클래스가 외부 클래스의 멤버를 가져와 사용하는 경우가 아니라면 static 내부 클래스로 선언하는 것이 좋다고 한다.
static 멤버는 내부 클래스 중 static 내부 클래스에서만 가질 수 있다. 다만 final static
이 붙은 상수는 모든 내부 클래스에서 정의가 가능하다!
☝️단, 지역 내부 클래스의 상수는 외부에서 사용이 불가능하며 메서드 내에서만 사용이 가능하다!
왜?
지역 변수, 클래스는 해당 메서드가 종료되는 동시에 삭제됨
위 코드를 보면 메서드 내에 선언된 상수 TEST
는 메서드 내에 선언된 지역 내부 클래스가 접근할 수 있다.
이 이유는 메서드가 수행을 마쳐서 지역변수가 소멸된 시점에도, 지역 클래스의 인스턴스가 소멸된 지역변수를 참조하려는 경우가 발생하기 때문이다. 또 이때의 상수는 별도의 pool에 저장된다.
익명 클래스는 이름 그대로 이름이 없는 클래스로 선언과 동시에 객체를 생성하여 단 하나의 객체를 생성할 수 있는 일회용 클래스이다.
new 조상클래스이름() {
...
}
new 구현인터페이스이름() {
...
}
나이순 정렬에서 사용한 코드를 살펴보자.
Arrays.sort(person, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
if(o1.age == o2.age) {
return 0;
} else {
return o1.age - o2.age;
}
}
});
배열을 정렬하기 위해 Comparator
인터페이스의 익명 클래스를 만들어 사용하였다. 이처럼 일회성으로 사용하기 위한 클래스를 별도로 정의하는 것보다는 이렇게 조상의 클래스 이름, 인터페이스 이름을 사용하여 익명 클래스를 사용하는 것이 코드를 조금 더 간결하게 만들 수 있다.
☝️ 한가지 더!
인터페이스를 사용하려면 해당 인터페이스를 구현한 클래스가 있어야 한다. 하지만 익명 클래스를 사용할 경우 별도의 클래스를 구현하지 않고도 인터페이스를 구현해 사용할 수 있다!