[JAVA] 중첩 클래스

dooboocookie·2023년 4월 14일
0

이펙티브 자바, 아이템 24 - 멤버 클래스는 되도록 static으로 만들라

중첩 클래스

  • 클래스 안에 정의하는 클래스
  • 중첩 클래스가 있는 클래스에서 감싸고 있는 바깥쪽 클래스를 Outer 클래스, 안쪽 클래스를 Inner 클래스라 한다.
  • Inner 클래스는 Outer 클래스에서만 사용되어야 한다.

중첩 클래스의 종류와 쓰임

정적 멤버 클래스

  • 클래스 내부에 변수, 메서드 처럼 멤버로서 static 클래스를 선언하는 것이다.
public class Outer {

    private final int data;

    public Outer(final int data) {
        this.data = data;
    }

    public static class PublicStaticInner {
        public void run() {
            System.out.println(this);
            System.out.println(CONST); // 접근 가능
            System.out.println(data); // 컴파일 에러
        }
    }

    private static class PrivateStaticInner {
        public void run() {
            System.out.println(this);
            System.out.println(CONST); // 접근 가능
            System.out.println(Outer.this.data); // 컴파일 에러
        }
    }
}
  • Inner 클래스가 static이 때문에 선언되는 시점에서 Outer 클래스의 static 멤버에만 접근할 수 있다
@Test
void test() {
	
    new Outer.PublicStaticInner();
    
    Outer outer = new Outer(10);
	outer.new PublicStaticInner(); // 컴파일 에러
}

  • Outer 클래스의 인스턴스가 없이도 Inner 클래스의 인스턴스를 생성할 수 있다.
  • Outer 클래스가 인스턴스로는 Inner 클래스는 생성할 수 없다.

private static 멤버 클래스

  • Inner 클래스가 Outer 클래스의 객체 한 부분을 나타낼 때 사용
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
	// ...
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
        //...
    }
    // ...
}
  • HashMap의 Node의 관계
  • Node들은 HashMap에 연관되어 있지만, Node들에서는 HashMap의 인스턴스 상태들에 접근할 수 없다.

비정적 멤버 클래스

  • 클래스 내부의 변수, 메서드처럼 멤버로서 클래스를 선언하고, static은 선언하지 않는다.
public class Outer {
    private final static int CONST = 10;

    private final int data;

    public Outer(final int data) {
        this.data = data;
    }
    
    public class PublicInner {
        public void run() {
            System.out.println(this);
            System.out.println(CONST);
            System.out.println(data);
        }
    }

    private class PrivateInner {
        public void run() {
            System.out.println(this);
            System.out.println(CONST);
            System.out.println(data);
        }
    }
}
  • Inner 클래스에서 Outer 클래스의 인스턴스 멤버에 대해서 접근할 수 있다.
    @Test
    void test() {
        Outer outer = new Outer(10);
        outer.new PublicInner();
        new Outer.PublicInner().; // 컴파일 에러
    }

  • Outer 클래스의 인스턴스가 없이는 Inner 클래스의 인스턴스를 생성할 수 없다.
    • 이 경우, Inner 클래스의 인스턴스는 Outer 클래스의 인스턴스와 묵시적으로 연결되게 된다는 것이다.
  • Outer 클래스의 인스턴스.new InnerCalss()를 사용하여 직접 인스턴스를 생성하던가,
public class Outer {
    // ...
    public void userInnerClass() {
        PrivateInner privateInner = new PrivateInner();
        privateInner.run();
    }
    // ...
}
  • Outer의 인스턴스 메서드 내부에서 생성하는 형식으로 Inner 클래스의 인스턴스를 만들 수 있다.
  • 이 경우, Inner 클래스의 인스턴스Outer 클래스의 인스턴스의 관계는 Inner 클래스가 인스턴스화 될 때 확립되고 이를 추후에 변경할 수 없다.

정적 vs 비정적

비정적의 단점

  • 관계 정보는 Inner 클래스 인스턴스 내부에 저장이 되기 때문에 메모리 공간도 더 차지하고
  • Inner 클래스 인스턴스 생성 시간도 오래 걸린다.
  • Inner 클래스 인스턴스 내부에 Outer 클래스 인스턴스의 참조가 있다
    • (참조가 눈에 보이지 않음)
    • ➡️ GC가 Outer 클래스의 인스턴스를 회수하지 못할 수 있다.
    • ➡️ 의도치 않은 메모리 누수가 발생할 수 있다.

되도록이면 멤버 클래스에서는 static을 사용하자

비정적 멤버 클래스는 어탭터를 정의할 때 사용

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
	// ...
    final class KeySet extends AbstractSet<K> {
    	// ...
    }
	// ...
    final class Values extends AbstractCollection<V> {
    	// ...
    }
    // ...
    final class EntrySet extends AbstractSet<Map.Entry<K, V>> {
    	// ...
    }
    // ...
}
  • Outer 클래스의 인스턴스를 감싸 다른 클래스의 인스턴스 처럼 보이게 하는 뷰의 용도로 사용
  • HashMap을 감싸 KeySet, Values, EntrySet 처럼 보이게 하는 뷰 용도로 사용
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
	// ...
    private class Itr implements Iterator<E> {
		// ...
    }
	// ...
}

그외 중첩 클래스

익명 클래스

  • Outer 클래스의 멤버가 아님
  • 사용되는 시점에서 선언인스턴스 생성을 한다.
  • 제약 사항
    • static한 문맥에서
      • Outer 클래스의 인스턴스를 참조할 수 없다.
      • 상수(static final) 외의 static 멤버를 가질 수 없다.
    • 클래스 이름이 없기 때문에, instanceof 사용 불가
    • 인터페이스 다중 구현 불가능
    • 인터페이스 구현하면서 다른 클래스 상속 불가능
    • 선언부와 사용하는 부분이 같이 있기 때문에 짧아야 가독성이 올라간다.
  • 한번만 사용되고 작은 처리에 대해서 사용
  • 람다로 대체하는 것을 고려해보자.

지역 클래스

  • 지역 변수를 사용하는 곳에서 사용할 수 있다.
  • 특징
    • 이름을 가질 수 있으므로, 그 범위 내에서는 여러번 사용할 수 있다.
    • 정적인 문맥에서 Outer 클래스의 인스턴스를 참조할 수 없다.
    • 정적 멤버를 가질 수 없다.
    • 가독성을 위해 짧게 작성할 필요가 있다.
profile
1일 1산책 1커밋

0개의 댓글