■ 중첩클래스 : 다른 클래스 안에 정의된 클래스이며, 중첩 클래스의 종류는 4가지이다.
- 정적 멤버 클래스
- (비정적) 멤버 클래스
- 익명 클래스
- 지역 클래스
이중, 정적 멤버 클래스를 제외한 나머지는 내부 클래스(inner class)에 해당한다. (그 외의 쓰임새는 톱레벨 클래스로 만듦)
그러면 중첩 클래스는 왜 사용하는가?
- 내부 클래스에서 외부 클래스에 손쉽게 접근 가능
- 서로 관련있는 클래스들을 논리적으로 묶어, 코드의 캡슐화↑
- 외부 클래스에서 내부 클래스에 접근할 수 없으므로 코드의 복잡성↓
- 외부 클래스의 복잡한 코드를 내부 클래스로 옮겨 코드의 복잡성↓
▶ 정적 멤버 클래스
해당 클래스는 다른 클래스 안에 선언되고, 바깥 클래스의 private 멤버에도 접근할 수 있다는 점만 제외하고는 일반 클래스와 동일
(다른 정적 멤버와 똑같은 접근 규칙을 적용)
흔히, 바깥 클래스와 함께 쓰일 때만 유용한 public 도우미 클래스로 쓰임 (대표적인 예로 Builder 패턴이 존재)
Builder 패턴의 예)
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
public static class Builder {
// 필수 매개변수 - 외부 클래스의 pirivate 변수에 접근 가능
private final int servingSize;
private final int servings;
// 선택 매개변수 - 기본값으로 초기화 & 외부 클래스의 private 변수에 접근 가능
private int calories = 0;
private int fat = 0;
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int val) {
calories = val;
return this;
}
public Builder fat(int val) {
fat = val;
return this;
}
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
public NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
}
public static void main(String[] args) {
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
.calories(100).build();
//calories(100)을 하지 않아도 생성 가능
}
}
▶ (비정적) 멤버 클래스
구문상 차이는 단지 static의 유무 이지만, 의미상 차이는 매우 큼
멤버 클래스의 인스턴스와 바깥 인스턴스 사이의 관계는 멤버 클래스가 인스턴스화될 때 확립되며, 더 이상 변경할 수 X
즉, 바깥 클래스의 인스턴스와 멤버 클래스의 인스턴스는 서로 암묵적으로 연결되어 있다.
-> 멤버 클래스의 인스턴스 메소드에서 정규화된 this를 사용해 바깥 인스턴스의 메소드를 호출하거나 바깥 인스턴스의 참조를 가져올 수 있다.
(드물게 바깥 클래스의 인스턴스.new 비정적클래스를 통해 생성하기도 하나, 이는 비용과 생성시간 측면에서 좋지 못하다.)
public class ClassA {
public int a = 0;
public void print() {
System.out.println("pring ClassA");
ClassB b = new ClassB();
b.print();
}
public class ClassB {
public void print() {
//ClassA.this를 통해 ClassA에 접근이 가능.
//외부 인스턴스의 클래스.this 형태
System.out.println("print ClassB : " + ClassA.this.a);
}
}
}
public class ClassAMain {
public static void main(String[] args) {
ClassA a = new ClassA();
a.print();
System.out.println("================");
ClassA.ClassB b = a.new ClassB();
b.print();
}
}
//결과
//pring ClassA
//print ClassB : 0
//================
//print ClassB : 0
만약, 개념상 중첩 클래스(nested class)의 인스턴스가 바깥 인스턴스와 독립적으로 존재할 수 없다면 정적 멤버 클래스로 만들어야 한다.
왜냐하면,
◎ 바깥 인스턴스 없이는 멤버 클래스 생성 불가
◎ 바깥 인스턴스로의 숨은 외부참조를 저장하려면 시간과 공간이 소비
이로 인해, GC가 바깥 클래스의 인스턴스를 수거하지 못하는 메모리 누수가 생길 수 있다.
(바깥 클래스는 더 이상 사용되지 않지만 내부 클래스의 참조로 인해 GC가 수거하지 못해서 바깥 클래스의 메모리 해제를 하지 못하는 경우가 발생)
반면, 정적 내부 클래스의 경우 바깥 클래스에 대한 참조 값을 가지고 있지 않기 때문에 메모리 누수가 발생하지 않는다.
메모리 누수가 발생할 수 있는 문제점이 있기 때문에 내부 클래스가 독립적으로 사용된다면 정적 클래스로 선언하여 사용하는 것이 좋다.
그럼에도 멤버 클래스를 사용했을 때 장점이 존재
-> 어댑터 패턴을 이용하여 바깥 클래스를 다른 클래스로 제공할 때
(어댑터 패턴을 이용하는 경우 비정적 내부 클래스는 내부 클래스가 바깥 클래스 밖에서 사용되지 X)
ex) HashMap의 keySey() 메서드
▶ 익명 클래스
바깥 클래스의 멤버도 아니며 멤버와 달리 사용되는 시점에 선언과 동시에 인스턴스가 만들어지며 코드의 어디서나 만들 수 있다.
또한 익명 클래스가 상위 타입(자기 자신 혹은 부모)에 상속한 멤버 외에는 호출할 수가 없다.
오직 비정적인 문맥에서 사용될 때만 바깥 클래스의 인스턴스를 참조 가능하다. (정적 멤버는 객체생성 전에 메모리에 로드되기 때문)
+ 또 다른 주 쓰임은 정적 팩터리 메서드를 구현할 때 사용
but 자바에 람다가 등장한 이후, 익명 클래스보다는 람다 사용
▶ 지역 클래스
지역 클래스는 지역변수를 선언할 수 있는 곳이라면 어디서든 선언할 수 있다.
그에 따라 유효 범위도 지역변수와 같다.
또한 다른 중첩 클래스들의 공통점을 하나씩 가지고 있다.
- 멤버 클래스처럼 이름을 가질 수 있고 반복해서 사용할 수 있다.
- 비정적 문맥에서 사용될 때만 바깥 인스턴스를 참조할 수 있다.
- 정적 멤버는 가질 수 없으며, 가독성을 위해 짧게(10줄이하)로 작성되어야 한다.
'Effective Java' 카테고리의 다른 글
[Effective Java] 아이템 26 - 로 타입은 사용하지 말아라 (0) | 2022.02.14 |
---|---|
[Effective Java] 아이템 25 - 톱레벨 클래스는 한 파일에 하나만 담아라 (0) | 2022.02.14 |
[Effective Java] 아이템 23 - 태그 달린 클래스보다는 클래스 계층 구조를 활용하라 (0) | 2022.02.09 |
[Effective Java] 아이템 22 - 인터페이스는 타입을 정의하는 용도로만 사용하라 (0) | 2022.02.09 |
[Effective Java] 아이템 21 - 인터페이스는 구현하는 쪽을 생각해 설계하라 (0) | 2022.02.09 |