중첩 클래스는 다른 클래스 안에 정의된 클래스다. 중첩 클래스는 자신을 감싼 바깥 클래스에서만 쓰여야 하며, 그 외의 쓰임새가 있다면 톱레벨 클래스로 만들어야 한다.
중첩 클래스 종류
정적 멤버 클래스를 제외한 나머지는 내부 클래스(inner class)다.
public class Animal {
private String name = "cat";
// 열거 타입도 암시적 static
public enum Kinds {
MAMMALS, BIRDS, FISH, REPTILES, INSECT
}
private static class PrivateSample {
private int temp;
public void method() {
Animal outerClass = new Animal();
System.out.println("private" + outerClass.name); // 바깥 클래스인 Animal의 private 멤버 접근
}
}
public static class PublicSample {
private int temp;
public void method() {
Animal outerClass = new Animal();
System.out.println("public" + outerClass.name); // 바깥 클래스인 Animal의 private 멤버 접근
}
}
}
Map 안의 Entry는 interface다.
Map을 구현하는 구현체인 HashMap에서 해당 interface를 implements하여 새로운 클래스를 정의한다.
멤버 클래스에서 바깥 인스턴스에 접근할 일이 없으면 무조건 static을 붙여서 정적 멤버 클래스로 만들자. static을 생략하면 바깥 인스턴스로의 숨은 참조가 생기고, 참조를 저장하기 위해 시간과 공간이 소비된다. 또한 가비지 컬렉션이 바깥 클래스의 인스턴스를 수거하지 못한다.
클래스명.this
형태로를 사용해 바깥 인스턴스의 메서드를 호출하거나 바깥 인스턴스의 참조를 가져올 수 있다. public class TestClass {
private String name = "yeonlog";
public class PublicSample {
public void printName() {
// 바깥 클래스의 private 멤버 가져오기
System.out.println(name);
}
public void callTestClassMethod() {
// 바깥 클래스의 메소드 호출하기
TestClass.this.testMethod();
}
}
public PublicSample createPublicSample() {
return new PublicSample();
}
public void testMethod() {
System.out.println("hello world");
}
}
따라서 개념상 중첩 클래스의 인스턴스와 바깥 클래스의 인스턴스가 독립적이라면 정적 멤버 클래스로 만들어야 한다.
비정적 멤버 클래스의 인스턴스와 바깥 인스턴스 사이의 관계는 멤버 클래스가 인스턴스화 될 때 확립되며, 더 이상 변경할 수 없다. 이 관계는 비정적 멤버 클래스의 인스턴스 안에 만들어져 메모리 공간을 차지하며, 생성시간도 더 걸린다.
언제 사용할까
비정적 멤버 클래스는 어댑터를 정의할 때 자주 쓰인다. 즉, 어떤 클래스의 인스턴스를 감싸 마치 다른 클래스의 인스턴스처럼 보이게 하는 뷰로 사용하는 것이다.
example: Map 인터페이스의 구현체
keySet()
, entrySet()
, values()
가 반환하는 자신의 컬렉션 뷰 를 구현할 때 활용 public class HashMap<K, V> extends AbstractMap<K, V>
implements Map<K, V>, Cloneable, Serializable {
final class EntrySet extends AbstractSet<Map.Entry<K, V>> {
// size(), clear(), contains(), remove(), ...
}
final class KeySet extends AbstractSet<K> {
// size(), clear(), contains(), remove(), ...
}
final class Values extends AbstractCollection<V> {
// size(), clear(), contains(), remove(), ...
}
}
익명 클래스는 바깥 클래스의 멤버도 아니다. 쓰이는 시점과 동시에 인스턴스가 만들어진다. 정적 문맥에서라도 상수 변수 이외의 정적 멤버는 가질 수 없다.
익명 클래스는 여러 인터페이스를 구현할 수 없고, 인터페이스를 구현하는 동시에 다른 클래스를 상속할 수도 없다.
public class Calculator {
private int x;
private int y;
public Calculator(int x, int y) {
this.x = x;
this.y = y;
}
public int plus() {
Operator operator = new Operator() {
private static final String COMMENT = "더하기"; // 상수
// private static int num = 10; // 상수 외의 정적 멤버는 불가능
@Override
public int plus() {
// Calculator.plus()가 static이면 x, y 참조 불가
return x + y;
}
};
return operator.plus();
}
}
interface Operator {
int plus();
}
List<Integer> list = Arrays.asList(10, 5, 6, 7, 1, 3, 4);
// 익명 클래스 사용
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
});
// 람다 도입 후
Collections.sort(list, Comparator.comparingInt(o -> o));
가장 드물게 사용된다. 유효범위가 지역 변수와 같고, 비정적 문맥에서 사용될 때만 바깥 인스턴스를 참조할 수 있고, 정적 멤버는 가질 수없고, 짧게 작성해야 한다.
public class TestClass {
private int number = 10;
public TestClass() {
}
public void foo() {
// 지역변수처럼 선언
class LocalClass {
private String name;
// private static int staticNumber; // 정적 멤버 가질 수 없음
public LocalClass(String name) {
this.name = name;
}
public void print() {
// 비정적 문맥에선 바깥 인스턴스를 참조 가능
// foo()가 static이면 number에서 컴파일 에러
System.out.println(number + name);
}
}
LocalClass localClass1 = new LocalClass("local1"); // 이름이 있고
LocalClass localClass2 = new LocalClass("local2"); // 반복해서 사용 가능
}
}