-
Notifications
You must be signed in to change notification settings - Fork 2
Open
Labels
🐸 04 Classes & Interfaces4장 클래스와 인터페이스4장 클래스와 인터페이스
Description
Chapter : 4. 클래스와 인터페이스
Item : 17. 변경 가능성을 최소화하라
Assignee : byunghyunkim0
🍑 서론
불변 클래스
- 불변 클래스란 그 인스턴스 내부 값을 수정할 수 없는 클래스이다.
- 객체가 파괴되는 순간까지 달라지지 않는다.
- String, BigInteger, BigDecimal
🍑 본론
불변 클래스로 만들기 위한 5가지 규칙
- 객체의 상태를 변경하는 메서드를 제공하지 않는다.
- 클래스를 확장할 수 없도록 한다.
- 클래스를 final로 설정해서 상속을 막음
- 모든 필드를 final로 선언한다.
- 새로 생성된 인스턴스를 동기화 없이 다른 스레드로 건네도 문제 없다.
- 모든 필드를 private로 선언한다.
- public final로 해도 불변이지만, 내부 구현을 바꾸지 못하므로 권하지않음
- 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.
- 생성자, 접근자, readObject 메서드 모두에서 방어적 복사를 수행
// 불변 복소수 클래스
public 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 (o == this)
return true;
if (!(o instanceof Complex))
return false;
Complex c = (Complex) o;
// == 대신 compare를 사용하는 이유는 63쪽을 확인하라.
return Double.compare(c.re, re) == 0
&& Double.compare(c.im, im) == 0;
}
@Override public int hashCode() {
return 31 * Double.hashCode(re) + Double.hashCode(im);
}
@Override public String toString() {
return "(" + re + " + " + im + "i)";
}
}- 이러한 방식으로 프로그래밍하면 코드에서 불변이 되는 영역의 비율이 높아지는 장점을 누릴 수 있다.
불변 객체
- 불변 객체는 단순하다.
- 생성 시점의 상태를 파괴될 때까지 간직한다.
- 불변 객체는 근본적으로 스레드 안전하여 따로 동기화할 필요 없다.
- 불변 객체에 대해서는 어떤 스레드도 다른 스레드에 영향을 줄 수 없기때문에
- 따라서 불변 클래스라면 한번 만든 인스턴스를 최대한 재활용하기를 권함
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);- 인스턴스를 중복 생성하지 않게 해주는 정적 팩터리를 제공할 수 있다.
- 불변 객체는 자유롭게 공유할 수 있음은 물론, 불변 객체끼리는 내부 데이터를 공유할 수 있다.
- 새로 만든 인스턴스도 원본 인스턴스 내부 배열을 그대로 가르킴
public class BigInteger extends Number implements Comparable<BigInteger> { /** * The signum of this BigInteger: -1 for negative, 0 for zero, or * 1 for positive. Note that the BigInteger zero <em>must</em> have * a signum of 0. This is necessary to ensures that there is exactly one * representation for each BigInteger value. */ final int signum; /** * The magnitude of this BigInteger, in <i>big-endian</i> order: the * zeroth element of this array is the most-significant int of the * magnitude. The magnitude must be "minimal" in that the most-significant * int ({@code mag[0]}) must be non-zero. This is necessary to * ensure that there is exactly one representation for each BigInteger * value. Note that this implies that the BigInteger zero has a * zero-length mag array. */ final int[] mag; public BigInteger negate() { return new BigInteger(this.mag, -this.signum); }
- 객체를 만들 때 다른 불변 객체들을 구성요소로 사용하면 이점이 많다.
- 값이 바뀌지 않는 구성요소들로 이뤄져있다면 복잡하더라도 불변식을 유지하기 쉽다.
- 예 : 맵(map)의 키와 집합(set)의 원소로 쓰기에 좋음
- 불변 객체는 그 자체로 실패 원자성을 제공한다.
- 잠깐이라도 불일치 상태에 빠질 가능성이 없다.
불변 클래스의 단점
- 값이 다르면 반드시 독립된 객체로 만들어야한다.
- 값의 가짓수가 많다면 이들을 만드는 데 큰 비용이 발생
- 백만 비트짜리 BigInteger에서 비트 하나만 바꾸기 (flipBit 메서드)
- 비슷한 기능인 BitSet은 가변 클래스 (원하는 비트 하나만 상수 시간 안에 바꿔주는 메서드를 제공 flip 메서드)
- 값의 가짓수가 많다면 이들을 만드는 데 큰 비용이 발생
성능 문제 대처하는 방법
- 다단계 연산들을 예측하여 기본 기능으로 제공
- 다단계 연산을 기본으로 제공한다면 단계마다 객체를 생성하지 않아도됨
- BigInteger는 모듈러 지수 같은 다단계 연산 속도를 높여주는 가변 동반 클래스(companion class)를 package-private로 두고있음
public BigInteger gcd(BigInteger val) { if (val.signum == 0) return this.abs(); else if (this.signum == 0) return val.abs(); MutableBigInteger a = new MutableBigInteger(this); MutableBigInteger b = new MutableBigInteger(val); MutableBigInteger result = a.hybridGCD(b); return result.toBigInteger(1); } /** * Convert this MutableBigInteger to a BigInteger object. */ BigInteger toBigInteger(int sign) { // <-- 접근제한자 package-private 에 주목 if (intLen == 0 || sign == 0) return BigInteger.ZERO; return new BigInteger(getMagnitudeArray(), sign); // 이마저도 불변을 지키기 위해서 방어적 복사를 진행함 }
- 예측이 불가능해서 가변 동반 클래스로만으로 해결할 수 없다면 public으로 제공하는게 최선
- 대표적인 예가 String
- String의 가변 동반 클래스 : StringBuilder, StringBUffer
불변 클래스를 만드는 또 다른 방법
- 모든 생성자를 private 혹은 package-private로 만들고 public 정적 팩토리를 제공하는 방법
// Complex 클래스
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);
}
...// 나머지는 생략
}불변 클래스 주의 점
- BigInteger, BigDecimal을 설계할 당시에는 불변 객체가 final이어야 한다는 생각이 퍼지지않았다.
- BigInteger, BigDecimal 메서드들은 모두 재정의 할 수 있게 설계되었다.
- 신뢰할 수 없는 클라이언트로부터 BigInteger, BigDecimal의 인스턴스를 인수로 받는다면 주의해야한다. (진짜 BigInteger인지 확인)
- 신뢰할 수 없는 하위 클래스의 인스턴스라고 확인되면 가변이라고 정의하고 방어적으로 복사해서 사용(아이템 50)
public static BigInteger safeInstance(BigInteger val){ return val.getClass() == BigInteger.class ? val : new BigInteger(val.toByteArray()); }
- getter가 있다고 해서 setter를 만들지마라
- 클래스는 꼭 필요한 경우가 아니라면 불변이어야 한다.
- 특정 상황에서의 잠재적 성능 저하를 제외하고는 장점이 많다
- String, BigInteger처럼 무거운 값 객체도 불변으로 만드는 것이 좋다.
- 불변으로 만들 수 없는 클래스라도 변경할 수 있는 부분을 최소한으로 줄이자.
- 다른 합당한 이유가 없다면 모든 필드는 private final이어야 한다.
🍑 결론
- 가능한 불변 클래스를 사용하도록 고려하자
Referenced by
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
🐸 04 Classes & Interfaces4장 클래스와 인터페이스4장 클래스와 인터페이스