Search
Duplicate

아이템 17 - 변경 가능성을 최소화하라

작성자
챕터
4장 - 클래스와 인터페이스
최종 편집
2023/07/16 06:08
생성 시각
2023/07/13 09:19

핵심어

Mutable(변경 가능성)
Immutable Class(불변 클래스) 기본적인 박싱 클래스
함수형과 절차/명령형의 차이 - 피연산자의 상태 변경
동반 클래스
동반 클래스는 클래스와 동일한 이름을 가진 클래스다. 컴패니언 클래스는 클래스와 같은 파일/패키지에 선언되며, 클래스의 인스턴스 없이도 사용할 수 있다.
클래스의 인스턴스 없이 사용할 수 있는 정적 메서드 또는 필드를 선언하려는 경우
클래스의 인스턴스와 관련된 값을 저장하려는 경우
클래스의 인스턴스와 관련된 로직을 구현하려는 경우

요약

클래스는 꼭 필요한 경우가 아니라면 불변이어야 한다. → 합당한 이유가 없다면 모든 필드는 private final이어야 한다.
불변으로 만들 수 없다면, 최대한 가변 요소를 줄여라.
불변 클래스를 구성하는 다섯 가지 규칙
1.
모든 필드를 final로 선언한다.
2.
모든 필드를 private으로 선언한다.
3.
Setter를 제공하지 않는다.
4.
상속 불가능하게 만든다(확장 할 수 없도록, 하위 클래스가 없도록).
5.
자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다. → 클래스에 가변 객체를 참조하는 필드가 하나라도 있다면 클라이언트에서 그 객체의 참조를 얻을 수 없도록 해야 한다. 필요하다면 방어적 복사.
불변 객체는 Thread-Safe다.

내 생각

불변 객체의 새 인스턴스 반환 vs 방어적 복사 방어적 복사는 가변 필드를 가지고 있는 클래스의 상태를 보장하기 위함 + 깊은 복사를 위한 더 큰 비용을 들이는 방법일 수 있다. 불변 객체의 새 인스턴스 반환은 불필요한 부분이라고 생각한다. 만약
성능 이슈는 없는가? 아마도 잦은 생성이 이뤄져야한다면, BigInteger의 경우처럼 Mutable한 동반 클래스를 내부적으로 이용해서 성능을 높이는 방법도 있을 것 같다(복잡할 것 같지만).

예제 코드

동반 클래스 구현
@RequiredArgsConstructor(access = AccessLevel.PACKAGE) @EqualsAndHashCode public class Car { private final String maker; private final String model; private final int year; } class CarFactory { public static Car createCar(String maker, String model, int year) { return new Car(maker, model, year); } }
Java
복사
불변 객체 구현
@AllArgsConstructor @Getter public class InstanceOnHeap { private int count; public static InstanceOnHeap of(int count) { return new InstanceOnHeap(count); } public void add() { count++; } } /*-----------------------------------------------------------------------------*/ @AllArgsConstructor public final class Immutable { private final String name; private final String address; private final int count; private InstanceOnHeap mutableCount; // 참조는 외부에서 받을 수 없음 public String getName() { return name; } public String getAddress() { return address; } public int getCount() { return count; } public InstanceOnHeap getMutableCountDefensively() { return InstanceOnHeap.of(mutableCount.getCount()); } public void increaseMutableCount() { mutableCount.add(); } public Immutable functionalLikeImmutableAdd() { return new Immutable(name, address, count + 1, mutableCount); } }
Java
복사
테스트
public class ImmutableTest { @Test void 불변_클래스() { Immutable immutable = new Immutable("name", "address", 0, InstanceOnHeap.of(0)); assertEquals(0, immutable.getCount()); assertEquals(0, immutable.getMutableCountDefensively().getCount()); InstanceOnHeap defensive = immutable.getMutableCountDefensively(); InstanceOnHeap defensive2 = immutable.getMutableCountDefensively(); // 방어적 복사(스냅샷)이기 때문에 다른 인스턴스다. assertNotEquals(defensive.hashCode(), defensive2.hashCode()); /*------------------------------------함수형 따라해보기-----------------------------------------*/ long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { immutable = immutable.functionalLikeImmutableAdd(); } long endTime = System.currentTimeMillis(); System.out.println("함수형 따라하기 : " + (endTime - startTime) + "ms"); // 8ms startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { immutable.increaseMutableCount(); } endTime = System.currentTimeMillis(); System.out.println("그냥 더하기 : " + (endTime - startTime) + "ms"); // 4ms } @Test void 동반_클래스() { Car car = CarFactory.createCar("maker", "model", 2021); Car car2 = new Car("maker", "model", 2021); assertEquals(car, car2); } }
Java
복사
utterances: repo: TightJava/effective_java
YAML
복사