[Java] 이펙티브 - 1

Fortice·2022년 6월 27일
0

Java

목록 보기
7/7

1. 생성자 대신 정적 팩터리 메서드를 고려하라

정적 팩터리 메서드

GoF 디자인 패턴 중 팩터리 메서드 패턴에서 유래한 것으로 객체를 생성하는 역할을 분리하겠다는 취지가 담겨있다. 객체를 private 생성자 + public static 메소드를 통해 캡슐화한다. new Object() --> Object.getInstance()
(이 책에서는 디자인 패턴에서의 팩터리 메서드 패턴과 다르다고 명시하고 있다.)

메서드로 설명하면, 객체 생성의 역할을 하는 클래스 메서드이다.

1-1. Boolean을 통한 예시

  • boolean 값을 받아 Boolean 객체로 변환
  • 클래스는 생성자 대신 정적 팩터리 메서드를 제공 가능
public static Boolean valueOf(boolean b) {
    return b ? Boolean.TRUE : Boolean.FALSE;
}

1-2. 장점

  1. 이름
    • 생성자로는 반환될 객체의 특성을 제대로 설명하지 못함
    • 메소드로 이름을 지어주어 특성 설명 가능
  2. 호출될 때마다 인스턴스를 새로 생성하지는 않아도 됨
    • 불필요한 객체 생성 X
      • 인스턴스 미리 생성
      • 새로 생성한 인스턴스 캐싱
    • 유사한 패턴 : 플라이웨이트 패턴(Flyweight pattern)
    • 인스턴스 생명주기 통제 : 통제 클래스
    • 인스턴스 통제 클래스로 가능한 작업 목록
      • 싱글턴
      • 인스턴스화 불가
      • 불변 값 클래스에서 동치인 인스턴스가 하나임을 보장
      • 플라이웨이트 패턴의 근간
      • 열거 타입은 인스턴스가 하나만 만들어짐을 보장
  3. 반환 타입의 하위 타입 객체 반환 가능
    • 반환할 객체의 클래스 자유롭게 선택
      -> 구현 클래스를 공개하지 않고 객체 반환 가능
      -> API 작게 유지 가능
      -> 인터페이스 기반 프레임워크를 만드는 핵심 기술
    • 인터페이스만으로 복잡한 객체 컨트롤
  4. 입력 매개변수에 따라 매번 다른 클래스의 객체 반환 가능
    • 반환 타입의 하위 타입이기만 하면 됨
    • 클라이언트에는 영향이 없게 내부적으로 수정이 가능함
  5. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 됨
    • 리플렉션을 이용해 객체 지정 생성
    • 서비스 제공자 프레임워크를 만드는 근간
      • 대표 프레임워크 JDBC
      • 제공자 == 서비스의 구현체

1-3. 단점

  1. 정적 팩터리 메서드만 제공하면 하위 클래스를 만들지 못함
    • 상속을 하려면 public이나 protected 생성자가 필요
  2. 정적 팩터리 메서드는 프로그래머가 찾기 어려움

2. 생성자에 매개변수가 많다면 빌더 고려

2-1. 점층적 생성자 패턴

  • 생성자의 매개변수를 하나씩 늘리는 패턴
    • 매개변수 순서 유지, 우선 순위 표현 가능
  • 단점
    • 가독성 하락
    • 같은 타입의 매개변수 순서 구분하기 힘듦
      • 컴파일 시점 발견 불가
      • 런타임 시점 발견 가능

2-2. 자바 빈즈 패턴

  • 기본 생성자 + setter를 이용한 값 설정
  • 점층적 생성자 패턴 단점 보완
  • 단점
    • 객체 하나 생성 시 메소드 여러번 호출
    • 완성되기 전까지 객체의 일관성이 무너진 상태
    • 클래스를 불변으로 만들지 못함

2-3. 빌더 패턴

  • 앞의 두 패턴 단점 보완
  • 고려할 점
    • 비교적 큰 생성 비용(그리 크진 않음)
    • 매개변수 4개 이상에 적절
      • 정말 고정적인게 아니라면 매개변수가 늘어날 수 있으니, 처음부터 빌더 패턴 사용

3. private & Enum 으로 싱글턴 보장

3-1. 싱글턴 생성 방식

  1. private 생성자 + public static final 필드
    • 장점
      • 간결함
      • 싱글턴임이 API에 명백히 들어남
    • 단점
      • public이므로 리플렉션으로 private 생성자 접근 가능
      • 생성자에서 중복 생성 방지 필요
  2. private 생성자 + private static 정적 팩토리 메소드
    • 장점
      • API를 바꾸지 않고도 싱글턴이 아니게 변경 가능
      • 정적 팩토리를 제네릭 싱글턴 팩토리롤 만들 수 있음
      • 정적 팩토리의 메소드 참조를 Supplier로 사용할 수 있음
  3. Enum 이용
    • 원소가 하나뿐인 열거 타입 생성
    • 직렬화 상황, 리플렉션 공격 모두 해결
    • 단, Enum외의 클래스를 상속해야 한다면 불가능

3-2. 직렬화&역직렬화 시 단일 인스턴스 보장

  • 역직렬화 시 새로운 인스턴스가 생성됨
  • 해결 방법
    1. 필드를 transient로 선언
    2. readResolve 메소드 제공

4. 인스턴스화 방지

  • private 생성자 + 명시적 예외
    • 자동으로 기본 생성자를 생성하는 것을 방지
    • 외부에서 생성자 호출 방지
    • 생성자 호출 시에도 예외로 방지
    • 상속 시에도 super()로 인한 예외 방지
  • 추상 클래스는 상속 해서 인스턴스화 가능 --> XX

5. 의존 객체 주입

  • 스프링 특성 생각

6. 불필요 객체 생성 회피

  • 재사용 가능한 경우 최대한 객체 생성 최소화
    • ex) String, Pattern
      • new String()
      • String.matches(String) -> Pattern.matcher(String).matches()
        • 패턴 인스턴스를 매번 재생성하여, 미리 생성해놓고 재사용
    • HashMap.keySet()
      • HashMap < AbstractMap < Map
      • 새로운 인스턴스가 아닌 AbstractMap의 transient Set을 갖고 있음
  • 오토 박싱 주의
    • 기본 타입과 박싱된 기본 타입을 섞어 쓸 경우
    • 자동으로 박싱을 해주면서 성능 저하
        • ex ) Long += long -> Long += Long(long)
  • 주의점
    • 객체 생성은 비싸니 피하자는 아님, 재사용이 필요하면 확실히 재사용
    • 방어적 복사에서 재사용이 훨씬 크리티컬 함

7. 다 쓴 객체 참조 해제

  • 가비지 컬렉션에서 heap 메모리에 연결된 객체 참조가 있으면 메모리 해제가 불가능
  • 명시적으로 참조를 끊어주어 GC 수행
    • null 처리 수행
      • 직접 메모리를 다루는 클래스의 경우만 사용
  • 캐시
    • 스케줄러로 캐시 청소
    • 외부에서 키를 참조하는 동안만 엔트리가 살아있는 캐시일 경우
      • WeakHashMap 이용
  • 리스너, 콜백
    • 콜백을 등록만 하고 해지하지 않을 경우
    • WeakHashMap으로 약한 참조로 저장

8. finalizer와 cleaner 사용 회피

(어렵다, 잠깐 생략)

8-1. 자바의 소멸자

finalizer

가비지 컬렉션이 수행될 때, 자원에 대한 정리 작업을 진행하기 위해 호출되는 소멸자 메서드

public class Object {
    @Deprecated(since="9")
    protected void finalize() throws Throwable { }
}

"종료자는 사용하면 안된다. 예측이 불가능하고 대체로 위험하고 일반적으로 필요하지 않다"

자바의 finalize 메서드는 실행을 보장하지 않음

  • 해결책 : 아이템 7 참고

cleaner

자바 9에서 도입된 소멸자
생성된 Cleaner가 더 이상 사용되지 않을 때 등록된 스레드에서 소멸 작업 수행

9. try-finally vs try-with-resource

자바 라이브러리에는 close 메서드로 직접 닫아야 하는 자원이 많음

  • InputStream
  • OutputStream
  • java.sql.Connection

try-finally

자원이 닫힘을 try-finally로 보장 가능

static String example(String param) throws IOException {
    ClosableObject obj = new ClosableObject();
    try {
    	obj.do();
    } finally {
        obj.close();
    }
}

자원이 여러 개라면 try-finally가 중첩된다

  • 예외 중첩으로 추적하기 어려움
  • 코드 가독성 감소

try-with-resources

자바 7에서 나온 기능으로 위 문제 해결 (AutoCloseable 인터페이스 구현 필요)

static String example(String param) throws IOException {
    try(ClosableObject obj = new ClosableObject();
    ClosableObject2 obj2 = new ClosableObject2()) {
    	obj.do();
        obj2.do();
    }
}
  • 소멸시킬 자원을 명확히 구분 가능
  • catch 문으로 다른 예외에 대한 처리 가능
  • finally로 명시 안해줘도 됨

10. equals, 일반 규약을 지켜 재정의 필요

재정의 필요 없는 경우

  • 인스턴스가 본질적으로 고유
    • 동작하는 개체, Thread
  • 논리적 동시성을 검사할 일이 없음
    • 굳이 검사할 필요 없는 객체
  • 상위 클래스의 equals가 하위 클래스에도 맞음
    • Set - AbstractSet
  • private 클래스, package-private && equals를 호출할 일이 없음

재정의 필요

  • 논리적으로 객체가 같음을 확인해야할 경우
  • 인스턴스 통제 클래스
    • 같은 값이 2개일 경우가 없음

equals 메소드 동치 관계

  • 공통 조건 : null이 아닌 모든 참조 값
  • 반사성 : x에 대해 x equals x는 항상 true
  • 대칭성 : x, y에 대해 x equals y가 true이면 y equals x도 true
  • 추이성 : x, y, z에 대해 x equals y && y equals z 이면 x equals z
  • 일관성 : x, y에 대해 x equals y를 호출하면 항상 true만 반환하거나 항상 false만 반환
  • null 아님 : 모든 참조값 x에 대해 x equals null은 false
profile
서버 공부합니다.

0개의 댓글