다른 클래스 안에 정의된 클래스를 말한다.
이 중 첫번째를 제외한 나머지 클래스들은 내부 클래스에 해당한다.
정적 멤버 클래스와 비정적 멤버 클래스를 구분하는 기준은 static
키워드가 함께 작성되었는지 여부로 판단할 수 있다.
[정적 멤버 클래스 - 외부 클래스 private 멤버 접근]
class OuterClass {
private int a = 100;
static class InnerClass {
private int b;
void accessOuterClass(){
OuterClass outerClass = new OuterClass();
outerClass.a = 1;
}
}
}
static
키워드와 함께 작성된 InnerClass
는 정적 내부 클래스이다.private
멤버에도 접근할 수 있다.InnerClass
가 OuterClass
의 a
필드에 접근.private
멤버에 접근할 수 있다는점만 빼면 일반 클래스와 똑같다.private
으로 선언하면 바깥 클래스에서만 접근할 수 있다.static
이 붙은 정적 멤버 클래스와 비정적 멤버 클래스의 차이는 외부 인스턴스없이 내부 인스턴스를 바로 생성할 수 있다는 점이다.[정적 멤버 클래스 인스턴스 생성]
public void createClass(){
OuterClass.InnerClass innerClass = new InnerClass();
}
static
키워드가 없이 작성된 클래스는 비정적 내부 클래스이다.
[비정적 멤버 클래스 선언]
class OuterClass {
private int a = 100;
class InnerClass {
private int b;
}
}
static
키워드에 따라 정적 그리고 비정적 멤버 클래스의 생성 방법이 달라진다.this
키워드로 바깥 인스턴스의 메서드를 호출하거나 참조를 가져올 수 있다.this
를 사용하는데 정규화된 this
란 클래스명.this
형태로 바깥 클래스의 이름을 명시하는 용법을 말한다.[비정적 멤버 클래스 바깥 클래스 메서드 호출 - 암묵적 연결]
class OuterClass {
private int a = 100;
public void createClass(){
...
}
class InnerClass {
private int b;
void accessOuterClass(){
OuterClass.this.createClass();
}
}
}
바깥 인스턴스의 클래스.new 클래스명(args)
를 호출해 수동으로 만들기도 한다.[비정적 멤버 클래스 인스턴스화 - 바깥, 비정적 멤버 클래스간 관계 확립]
public class Main {
public static void main(String[] args) {
final OuterClass outerClass = new OuterClass();
outerClass.new InnerClass();
}
}
비정적 멤버 클래스는 어댑터를 정의할 때 자주 사용된다.
어댑터 패턴이란?
어떤 클래스의 인스턴스를 감싸 마치 다른 클래스의 인스턴스처럼 보이게하는 뷰로 사용하는 것.
[어댑터 정의 - 비정적 멤버 클래스 사용(반복자 구현)]
public class MySet<E> extends AbstractSet {
@Override
public Iterator iterator() {
return new MyIterator();
}
private class MyIterator implements Iterator<E>{
...
}
}
비정적 멤버 클래스로 어댑터 구현시
따라서, 멤버 클래스에서 바깥 클래스에 접근할 일이 없다면 무조건 static
을 붙여서 정적 멤버 클래스로 만들자.
[어댑터 정의 - 정적 멤버 클래스 사용(반복자 구현)]
public class MySet<E> extends AbstractSet {
@Override
public Iterator iterator() {
return new MyIterator();
}
private static class MyIterator implements Iterator<E>{
...
}
}
이와 비슷하게 Set
과 List
같은 다른 컬렉션 인터페이스 구현자들도 자신의 반복자를 구현할 때 비정적 멤버 클래스를 주로 사용한다.
private
정적 멤버 클래스는 흔히 바깥 클래스가 표현하는 객체의 한 부분을 나타낼 때 사용한다.
Map
과 Entry
의 관계에서 생각해보면
Map
은 key-value
를 표현하는 Entry
객체들을 가지고 있다. 이 Entry
객체들의 메서드들(getKey
, getValue
등)은 맵을 직접 사용하지 않는다.
따라서, 이럴 때 Entry
클래스를 비정적 멤버 클래스로 만든다면 Entry
는 매번 Map
과의 참조 관계를 유지하고 있을 것이다.
이럴 경우 시간과 공간의 낭비가 발생하기 때문에 static
키워드를 사용해 정적 클래스로 만들어 관리하는 것이 좋다.
[익명 클래스]
public class Main {
Inner innerClass(){
return new Inner() {
@Override
public void hello(final String s) {
System.out.println("hello");
}
};
}
interface Inner{
void hello(String s);
}
}
위 익명 클래스 코드는 람다로도 표현이 가능하다.
[람다로 표현한 익명 클래스]
public class Main {
Inner innerClass() {
return (String s) -> {
System.out.println("hello " + s);
};
}
interface Inner {
void hello(String s);
}
}
이런 익명클래스는 몇가지 단점이 있다.
instanceof
검사나 클래스 이름이 필요한 작업은 수행이 불가능하다.final
기본 타입과 문자열 필드만 가질 수 있다.익명 클래스는 정적 팩터리 메서드를 구현할때 자주 쓰이곤 한다.
[지역 클래스 사용]
static
멤버는 갖지 못하며, 클래스 내부에서 필요한 기능을 정의할 때 사용한다.
class OuterClass {
public void test() {
class LocalClass {
void printHello() {
System.out.println("Local Class Hello!");
}
}
final LocalClass localClass = new LocalClass();
localClass.printHello();
}
}
public class Main {
public static void main(String[] args) {
final OuterClass outerClass = new OuterClass();
outerClass.test();
}
}
중첩 클래스는 4가지 종류가 있고 각각의 성격에 따라 쓰임이 다르다.
메서드 밖에서도 사용해야 하거나 메서드 안에 정의하기에 너무 길다면 멤버 클래스로 만들자.
멤버 클래스의 인스턴스가 바깥 인스턴스를 참조한다면 비정적으로 만들고 그렇지 않다면 정적으로 만들자.
중첩 클래스가 한 메서드 안에서 쓰이면서 그 인스턴스를 생성하는 지점이 한곳이고 해당 타입으로 쓰기에 적합한 클래스나 인터페이스가 이미 있다면 익명 클래스로 그렇지 않다면 지역 클래스로 만들자.
[Reference]
이펙티브 자바 아이템24
멤버 클래스는 되도록 static으로 만들자