반응형
Jake Seo
제이크서 위키 블로그
Jake Seo
전체 방문자
오늘
어제
  • 분류 전체보기 (715)
    • 일상, 일기 (0)
    • 백준 문제풀이 (1)
    • 릿코드 문제풀이 (2)
    • 알고리즘 이론 (10)
      • 기본 이론 (2)
      • 배열과 문자열 (8)
    • 데이터베이스 (15)
      • Planet Scale (1)
      • MSSQL (9)
      • 디비 기본 개념 (1)
      • SQLite 직접 만들어보기 (4)
    • 보안 (7)
    • 설계 (1)
    • 네트워크 (17)
      • HTTP (9)
      • OSI Layers (5)
    • 회고 (31)
      • 연간 회고 (2)
      • 주간 회고 (29)
    • 인프라 (52)
      • 도커 (12)
      • AWS (9)
      • 용어 (21)
      • 웹 성능 (1)
      • 대규모 서비스를 지탱하는 기술 (9)
    • 깃 (7)
    • 빌드 도구 (7)
      • 메이븐 (6)
      • 그레이들 (0)
    • Java (135)
      • 이펙티브 자바 (73)
      • 자바 API (4)
      • 자바 잡지식 (30)
      • 자바 디자인 패턴 (21)
      • 톰캣 (Tomcat) (7)
    • 프레임워크 (64)
      • next.js (14)
      • 스프링 프레임워크 (28)
      • 토비의 스프링 (6)
      • 스프링 부트 (3)
      • JPA (Java Persistence API) (5)
      • Nest.js (8)
    • 프론트엔드 (48)
      • 다크모드 (1)
      • 노드 패키지 관리 매니저 (3)
      • CSS (19)
      • Web API (11)
      • tailwind-css (1)
      • React (5)
      • React 새 공식문서 요약 (1)
      • HTML (Markup Language) (5)
    • 자바스크립트 (108)
      • 모던 자바스크립트 (31)
      • 개념 (31)
      • 정규표현식 (5)
      • 코드 스니펫 (1)
      • 라이브러리 (6)
      • 인터뷰 (24)
      • 웹개발자를 위한 자바스크립트의 모든 것 (6)
      • 팁 (2)
    • Typescript (49)
    • 리눅스와 유닉스 (10)
    • Computer Science (1)
      • Compiler (1)
    • IDE (3)
      • VSCODE (1)
      • IntelliJ (2)
    • 세미나 & 컨퍼런스 (1)
    • 용어 (개발용어) (16)
      • 함수형 프로그래밍 용어들 (1)
    • ORM (2)
      • Prisma (2)
    • NODEJS (2)
    • cypress (1)
    • 리액트 네이티브 (React Native) (31)
    • 러스트 (Rust) (15)
    • 코틀린 (Kotlin) (4)
      • 자바에서 코틀린으로 (4)
    • 정규표현식 (3)
    • 구글 애널리틱스 (GA) (1)
    • SEO (2)
    • UML (2)
    • 맛탐험 (2)
    • 리팩토링 (1)
    • 서평 (2)
    • 소프트웨어 공학 (18)
      • 테스팅 (16)
      • 개발 프로세스 (1)
    • 교육학 (1)
    • 삶의 지혜, 통찰 (1)
    • Chat GPT (2)
    • 쉘스크립트 (1)
    • 컴파일 (2)
    • Dart (12)
    • 코드팩토리의 플러터 프로그래밍 (4)
    • 플러터 (17)
    • 안드로이드 스튜디오 (1)
    • 윈도우즈 (1)
    • 잡다한 백엔드 지식 (1)
    • 디자인 패턴 (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 작업기억공간
  • 자바스크립트
  • prerendering
  • 외래키 제약조건
  • Java
  • bean Validation
  • 이펙티브자바
  • rust
  • 자바 검증
  • item9
  • 알고리즘
  • Pre-rendering
  • 자바스크립트 면접
  • 프로그래머의 뇌
  • 메이븐 라이프사이클
  • pnpm
  • 서버리스 컴퓨팅
  • Javadoc 자바독 자바주석 주석 Comment
  • serverless computing
  • item7
  • 러스트
  • NEXT JS
  • 싱글톤 패턴
  • 디자인패턴
  • 참조 해제
  • 자바 디자인패턴
  • next js app
  • MSSQL
  • 추상 팩터리 패턴
  • try-with-resources
  • 도커공식문서
  • 이펙티브 자바 item9
  • 객체복사
  • 메이븐 골
  • 이펙티브 자바
  • 자바스크립트 인터뷰
  • 슬로우 쿼리
  • 토비의 스프링
  • 플라이웨이트패턴
  • 팩터리 메서드 패턴
  • 메이븐 페이즈
  • 싱글턴
  • Next.js
  • 자바
  • 빈 검증
  • 스프링 검증
  • 느린 쿼리
  • item8
  • 싱글톤
  • 자료구조

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Jake Seo

제이크서 위키 블로그

Java/이펙티브 자바

이펙티브 자바, 쉽게 정리하기 - item 17. 변경 가능성을 최소화하라

2022. 1. 3. 20:30

이펙티브 자바, 쉽게 정리하기 - item 17. 변경 가능성을 최소화하라

불변 클래스

불변클래스란, 인스턴스 내부 값을 수정할 수 없는 클래스이다. 불변 클래스 내부 정보는 객체가 파괴되는 순간까지 절대 달라지지 않는다. 자바에는 String, 기본 타입 박싱 클래스, BigInteger, BigDecimal이 있다.

  • 불변 클래스는 가변 클래스보다 설계하고 구현하고 사용하기 쉽다.
  • 오류가 생길 여지도 적고 훨씬 안전하다.

클래스를 불변으로 만드는 방법

  • 객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다.
  • 클래스를 확장할 수 없도록 한다. (상속이 불가능하게 만든다.)
    • 모든 생성자를 private 혹은 package-private으로 만들고 정적 팩터리를 제공한다.
  • 모든 필드를 final로 선언한다.
  • 모든 필드를 private으로 선언한다.
    • public final은 추후 API 리팩토링의 유연성을 저해할 수 있기 때문에 주의해야 한다.
  • 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.
static final class Complex {
    private final double re;
    private final double im;

    public Complex(double re, double im) {
        this.re = re;
        this.im = im;
    }

    public double realPart() {
        return re;
    }

    public double imaginaryPart() {
        return im;
    }

    public Complex plus(Complex c) {
        return new Complex(re + c.re, im + c.im);
    }

    public Complex minus(Complex c) {
        return new Complex(re - c.re, im - c.im);
    }

    public Complex times(Complex c) {
        return new Complex(re * c.re - im * c.im,
                           re * c.im + im * c.re);
    }

    public Complex dividedBy(Complex c) {
        double tmp = c.re * c.re + c.im * c.im;
        return new Complex((re * c.re + im * c.im) / tmp,
                           (im * c.re - re * c.im) / tmp);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Complex complex = (Complex) o;
        return Double.compare(complex.re, re) == 0 && Double.compare(complex.im, im) == 0;
    }

    @Override
    public int hashCode() {
        return Objects.hash(re, im);
    }

    @Override
    public String toString() {
        return "(" + re + " + " + im + "i)";
    }
}
  • 위 예는 복소수를 클래스로 구현한 것이다.
  • 접근자 메서드와 사칙연산 메서드가 있다.
  • 사칙연산 메서드는 피연산자인 클래스의 멤버를 건들지 않고, 매번 새로운 메서드를 반환한다.
    • 그래서 메서드 이름으로 add 같은 동사 메서드 대신 plus와 같은 전치사를 사용하였다.
    • 이를 함수형 프로그래밍이라 한다.
    • 이러한 방식을 이용하면, 코드에서 불변이 되는 영역이 늘어나는 장점을 누릴 수 있다.

불변이 되면 얻어지는 장점

  • 불변객체는 기본적으로 스레드 안전하여 따로 동기화가 필요 없다.
    • 불변객체는 안심하고 공유할 수 있다.
  • 불변의 장점을 활용하려면 클래스 내부에 많은 상수들을 제공하여 사용하는 것도 좋다.
public static final Complex ZERO = new Complex(0, 0);
public static final Complex ONE = new Complex(1, 0);
public static final Complex I = new Complex(0, 1);

불변 클래스 및 불변 객체의 특징

  • 불변 클래스는 자주 사용되는 인스턴스를 캐싱하여 같은 인스턴스를 중복 생성하지 않게 해주는 정적 팩터리를 제공할 수 있다.
    • 정적 팩터리를 통해 캐싱을 추후에 쉽게 덧붙일 수 있다.
  • 불변 클래스는 clone() 메서드나 복사 생성자를 제공하지 않는 것이 좋다.
    • String의 경우 이전의 잘못된 설계로 복사 생성자를 제공하고 있지만 사용하지 않는 것이 좋다.
  • 불변 객체는 자유롭게 공유도 가능할 뿐더러 내부 데이터 공유도 가능하다.
public BigInteger negate() {
    return new BigInteger(this.mag, -this.signum);
}
  • BigInteger 내부 negate()의 경우 this.mag 필드를 재활용한다.
    • 이 경우 배열 필드라서 사실 내부 값 수정이 가능하지만, private final로 된 불변 배열이고, 어디서도 접근 권한을 주지 않아 안전하다.
  • 객체를 만들 때 다른 불변 객체들을 구성요소로 사용하면 이점이 많다.
    • Map이나 Set 내부에 value로서 들어간 객체들에 변경이 생기면 불변식이 허물어지는데, 불변 객체를 사용하면 그런 걱정은 없다.
  • 불변 객체는 그 자체로 실패 원자성을 제공한다.
    • 메서드에서 예외가 발생한 뒤에도 그 객체는 여전히 메서드 호출 전과 같은 유효한 상태여야 한다는 것을 실패 원자성이라고 한다. 내부 상태를 바꾸지 않으니 메서드 호출 전과 정확히 동일할 것이다.

불변 클래스의 단점

  • 값이 다르면 반드시 독립된 객체로 만들어야 한다.
    • 보통 약간의 메모리 낭비가 있다.
      • 값이 다를 때마다 새로운 객체를 만들어야 한다.
    • 값의 가짓수가 많다면, 이들을 모두 만드는데 큰 비용을 치러야 한다.
      • ex) 100만 자리수를 가지고 있는 비트에서 앞의 1자리만 다른 경우, 낭비가 심하다.

불변 클래스의 단점 해소

  • 다단계 연산(multistep operation)들을 예측하여 기본 기능으로 제공할 수 있다.
    • 클라이언트가 원하는 복잡한 연산을 미리 예측하여 제공하는 것이다.
    • 불변인 String 클래스의 경우 가변 동반 클래스인 StringBuilder 클래스가 있다.
      • 구닥다리 전임자 StringBuffer도 있다.

불변 클래스의 설계 방법

  • 자신을 상속하지 못하게 해야 한다.
  • 클래스를 final로 선언하면 된다.
  • 모든 생성자를 private 혹은 package-private으로 만들고 public 정적 팩터리를 제공하면 된다.
public class Complex {
  private final double re;
  private final double im;

  private Complex(double re, double im) {
    this.re = re;
    this.im = im;
  }

  public static Complex valueOf(double re, double im) {
    return new Complex(re, im);
  }
}
  • 위의 코드 예에서 생성자는 private으로 제공하고, valueOf 정적 팩터리를 통해서 객체를 제공한다.
  • 이 방식이 최선인 경우가 많다.
  • 정적 팩터리 방식은 다수의 구현 클래스를 활용한 유연성을 제공한다.
  • 다음 릴리즈에서 객체 캐싱 기능을 추가해 성능을 끌어올릴 수도 있다.

BigInteger 클래스의 경우, 불변 객체가 사실상 final이어야 한다는 아이디어가 널리 퍼지기 전에 만들어진 것이라 public 생성자를 제공하는데, 이 때문에 하위 호환성에 의한 제약이 걸리고 지금까지 이 문제를 고치지 못했다.

public static BigInteger safeInstance(BigInteger val) {
  return val.getClass() == BigInteger.class ?
         val : new BigInteger(val.toByteArray());
}
  • 위는 BigInteger 클래스를 상속받은 하위 클래스가 BigInteger 타입의 자리에 들어오는 것을 방지하는 메서드이다.

불변 클래스를 설계할 때, '모든 필드가 final이어야 한다'는 규칙은 사실 좀 과해서 성능을 위해 '어떤 메서드도 객체의 상태 중 외부에 비치는 값을 변경할 수 없다.' 정도로 완화할 수 있다.
어떤 불변 클래스는 계산비용이 큰 값을 계산하여 final이 아닌 필드에 넣어놓고 재활용하기도 한다. 불변 객체는 몇번을 계산해도 같은 값임이 보장되기 때문에 가능하다.

핵심 정리

  • 클래스는 꼭 필요한 경우가 아니면 불변인 것이 좋다.
    • 단점이라곤 잠재적 성능저하 뿐이다.
  • String 혹은 BigInteger와 같은 무거운 값 객체도 불변으로 만들 수 있는지 고심해야 한다.
  • 불변으로 만들 수 없는 클래스는 변경할 수 있는 부분을 최대한 줄이는 것이 좋다.
    • 객체가 가질 수 있는 상태의 수를 줄이는 것이 객체를 예측하기 쉬워지고 오류가 생길 가능성이 줄어든다.
    • 꼭 변경할 필드 외에는 final로 선언하자.
  • 다른 합당한 이유가 없다면 모든 필드는 private final 접근 제어자를 가지는 것이 좋다.
  • 생성자는 불변식 설정이 모두 완료된, 초기화가 완벽히 끝난 상태의 객체를 생성해야 한다.

불변은 아니지만, 상태가 적어야 한다는 원칙을 잘 지킨 클래스의 예로 CountDownLatch가 존재한다. 이 클래스는 한번 카운트를 정하면 수정도 불가능하고 단, 1회만 이용 가능하다는 제약을 걸어 단순명료함을 극대화 시켰다.
CountDownLatch 관련 설명

반응형
저작자표시 (새창열림)

'Java > 이펙티브 자바' 카테고리의 다른 글

이펙티브 자바, 쉽게 정리하기 - item 19. 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라.  (0) 2022.01.04
이펙티브 자바, 쉽게 정리하기 - item 18. 상속보다는 컴포지션을 사용하라  (2) 2022.01.04
이펙티브 자바, 쉽게 정리하기 - item 16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라  (0) 2022.01.01
이펙티브 자바, 쉽게 정리하기 - item 15. 클래스와 멤버의 접근 권한을 최소화하라  (0) 2022.01.01
이펙티브 자바, 쉽게 정리하기 - item 14. Comparable을 구현할지 고려하라  (0) 2022.01.01
    'Java/이펙티브 자바' 카테고리의 다른 글
    • 이펙티브 자바, 쉽게 정리하기 - item 19. 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라.
    • 이펙티브 자바, 쉽게 정리하기 - item 18. 상속보다는 컴포지션을 사용하라
    • 이펙티브 자바, 쉽게 정리하기 - item 16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라
    • 이펙티브 자바, 쉽게 정리하기 - item 15. 클래스와 멤버의 접근 권한을 최소화하라
    Jake Seo
    Jake Seo
    ✔ 잘 보셨다면 광고 한번 클릭해주시면 큰 힘이 됩니다. ✔ 댓글로 틀린 부분을 지적해주시면 기분 나빠하지 않고 수정합니다. ✔ 많은 퇴고를 거친 글이 좋은 글이 된다고 생각합니다. ✔ 간결하고 명료하게 사람들을 이해 시키는 것을 목표로 합니다.

    티스토리툴바