-
아이템17. 변경 가능성을 최소화 하라.Java/이펙티브 자바 2023. 8. 15. 10:07
핵심 정리 - 불변 클래스
- 불변 클래스는 가변 클래스보다 설계하고 구현하고 사용하기 쉬우며, 오류가 생길 여지도 적고 훨씬 안전하다.
- 불변 클래스를 만드는 다섯 가지 규칙
ㆍ객체의 상태를 변경하는 메서드를 제공하지 않는다.
▷ setter를 제공하지 않는다.
ㆍ클래스를 확장할 수 없도록 한다
▷ 상속을 할 수 없게 만든다. ( 1. final class 2. private contructor )
ㆍ모든 필드에 final로 선언한다.
ㆍ모든 필드를 private으로 선언한다.
▷ public이면 필드 참조를 할 수도 있기에 우리는 필드 참조를 원치않는다.
ㆍ자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.
▷ getter를 만들어 주지 않거나 방어적인 복사를 해서 사용
public final class Person { private final Address address; // address가 가진 정보들 까지 final이 아니다. public Person(Address address) { this.address = address; } public Address getAddress() { // return address; Address copyOfAddress = new Address(); copyOfAddress.setCity(address.getCity()); copyOfAddress.setZipCode(address.getZipCode()); copyOfAddress.setStreet(address.getStreet()); return copyOfAddress; // Person의 정보가 바뀌지 않게 된다. } }
핵심 정리 - 불변 클래스의 장점과 단점
- 함수형 프로그래밍에 적합하다. ( 피연산자에 함수를 적용한 결과를 반환하지만 피연산자가 바뀌지는 않는다. )
- 불변 객체는 단순하다.
- 불변 객체는 근본적으로 스레드 안전하여 따로 동기화할 필요 없다.
- 불변 객체는 안심하고 공유할 수 있다. ( 상수, public static final )
- 불변 객체 끼리는 내부 데이터를 공유할 수 있다.
▷ ex) BigInteger의 negate
- 객체를 만들 때 불변 객체로 구성하면 이점이 많다.
▷ ex) Set - 컬렉션들은 구성요소들 까지 같아야 같다.
// 불변이라면 Set<Integer> numbers = Set.of(1,2,3); // 불변이 아니라면 final Set<Point> points = new HashSet<>(); Point firstPoint = new Point(1, 2); points.add(firstPoint); firstPoint.x = 10;
- 실패 원자성을 제공한다. ( 아이템 76, p407 )
▷ 예외가 발생하더라도 파라미터나 필드가 바뀌지 않는다.
- 단점) 값이 다르다면 반드시 별도의 객체로 만들어야 한다.
ㆍ"다단계 연산"을 제공하거나, "가변 동반 클래스"를 제공하여 대처할 수 있다.
▷ 가변동반클래스 ex) String
public static void main(String[] args) { String name = "whiteship"; StringBuilder nameBuilder = new StringBuilder(name); nameBuilder.append("phm"); }
핵심 정리 - 불변 클래스 만들 때 고려할 것
- 상속을 막을 수 있는 또 다른 방법
ㆍprivate 또는 package-private 생성자 + 정적 팩토리
ㆍ확장이 가능하다. 다수의 package-private 구현 클래스를 만들 수 있다.
ㆍ정적 팩터리를 통해 여러 구현 클래스중 하나를 활용할 수 있는 유연성을 제공하고 객체 캐싱 기능으로 성능을 향상 시킬 수도 있다.
- 재정의가 가능한 클래스는 방어적인 복사를 사용해야 한다.
// BigInteger은 상속을 막아두지 않았기에 방어적인 복사를 사용 public static BigInteger safeInstance(BigInteger val) { return val.getClass() == BigInteger.class ? val : new BigInteger(val.toByteArray()); }
- 모든 "외부에 공개하는" 필드가 final이어야 한다.
ㆍ계산 비용이 큰 값은 해당 값이 필요로 할 때 (나중에) 계산하여 final이 아닌 필드에 캐시해서 쓸 수도 있다.
완벽 공략 요약
- p105, 새로 생성된 불변 인스턴스를 동기화 없이 다른 스레드로 건네도 문제없이 동작 (JLS 17.5)
- p106, readObject 메서드 (아이템 88)에서 방어적 복사를 수행하라.
- p112, 불변 클래스의 내부에 가변 객체를 참조하는 필드가 있다면... (아이템 88)
- p113, java.util.concurrent 패키지의 CountDownLatch 클래스
완벽 공략 - final 과 자바 메모리 모델(JMM)
final을 사용하면 안전하게 초기화할 수 있다.
- JMM과 final을 완벽히 이해하려면 JLS 17.4와 JLS 17.5를 참고하라
- JMM
ㆍ자바 메모리 모델은 JVM의 메모리 구조가 아니다.
ㆍ적법한(legal) 프로그램을 실행 규칙
ㆍ메모리 모델이 허용하는 범위내에서 프로그램을 어떻게 실행하든 구현체(JMM)의 자유이다.
( 이 과정에서 실행 순서가 바뀔 수도 있다. )
- 어떤 인스턴스의 final 변수를 초기화 하기 전까지 해당 인스턴스를 참조하는 모든 스레드는 기다려야 한다.(freeze)
class FinalFieldExample { final int x; int y; static FinalFieldExample f; public FinalFieldExample() { x = 3; y = 4; } static void writer() { f = new FinalFieldExample(); } static void reader() { if (f != null) { int i = f.x; // guaranteed to see 3 int j = f.y; // could see 0 } } }
▷ 반드시 필드의 값이 설정되고 사용되길 바란다면 final 키워드를 추가한다.
완벽 공략 - CountDownLatch
java.util.concurrent 패키기
병행(concurrency) 프로그래밍에 유용하게 사용할 수 있는 유틸리티 묶음
- 병행(Concurrency)과 병렬(Parallelism)의 차이
- 병행은 여러 작업을 번갈아 가며 실행해 마치 동시에 여러 작업을 동시에 처리하듯 보이지만, 실제로는 한번에 오직 한 작업만 실행한다. CPU가 한개여도 가능하다.
- 병렬은 여러 작업을 동시에 처리한다. CPU가 여러개 있어야 가능하다.
- 자바의 concurrent 패키지는 병행 애플리케이션에 유용한 다양한 툴을 제공한다.
ㆍBlockingQueue, Callable, ConcurrentMap, Executor, ExecutorService, Future ...
CountDownLatch
다른 여러 스레드로 실행하는 여러 오퍼레이션이 마칠 때까지 기다릴 때 사용할 수 있는 유틸리티
- 초기회 할 때 숫자를 입력하고, await() 메서드를 사용해서 숫자가 0이 될 때까지 기다린다.
- 숫자를 셀 때는 countDown() 메서드를 사용한다.
- 재사용할 수 있는 인스턴스가 아니다. 숫자를 리셋해서 재사용하려면 CyclicBarrier를 사용해야 한다.
- 시작 또는 종료 신호로 사용할 수 있다.
'Java > 이펙티브 자바' 카테고리의 다른 글
아이템19. 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라. (0) 2023.08.30 아이템18. 상속보다는 컴포지션을 사용하라 (0) 2023.08.23 아이템16. public 클래스에서는 public 필드가 아닌 접근 메서드를 사용하라. (0) 2023.08.13 아이템15. 클래스와 멤버의 접근 권한을 최소화하라 (0) 2023.06.27 아이템 14. Comparable을 구현할지 고민하라 (0) 2023.06.23