JAVA/이펙티브자바
이펙티브자바 Item17. 변경 가능성을 최소화 하라
ghyeong
2024. 10. 7. 23:32
반응형
불변클래스란?
- 인스턴스의 내부 값을 수정할 수 없는 클래스
- 불변클래스 예시 → String, 박싱 클래스, BigInterger, BigDecimal
- 가변클래스보다 설계하고 구현하고 사용하기 쉽다
- 오류가 생길 여지가 적고 안전하다
클래스를 불변으로 만드는 5가지 규칙
- 객체의 상태를 변경하는 메서드를 제공하지 않는다
- setter가 존재하지 않아야한다
- 클래스를 확장할 수 없도록 한다
- class의 선언을 final로 선언하는 방법 존재
- 생성자를 private으로 막아 상속을 막는다**> 정적 팩토리 메서드 사용**
- 모든필드를 private로 선언한다
- 시스템이 강제하는 수단을 이용해서 설계자의 의도를 명확히 드러내도록 한다.
- 모든필드를 final로 선언한다
- 클라이언트에서 직접 접근해 수정하는 것을 막아준다.
- public final로 선언만으로도 불변 객체가 되지만, 다음 릴리스에서 내부 표현을 바꾸지 못한다
- 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다
- 방어적 복사를 수행해야 한다.
- 방어적 복사 VS 얕은 복사
- public class Lotto{ private final List<Integer> numbers; public Lotto(List<Integer> numbers){ this.numbers = List.copyOf(numbers); } // 방어적 복사 public List<Integer> getNumbers(){ return Collection.unmodifiableList(numbers); } // 방어적 복사 }
- 따라서 원본이나 복사한 객체나 변경이 되면 서로 영향을 미친다.
- 얕은 복사
- 내부의 객체를 반환할 때, 객체의 복사본을 만들어서 반환하는 것을 방어적 복사라고 한다.
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;
return Double.compare(c.re, re) == 0
&& Double.compare(c.im, im);
}
@Override public int hashCode(){
return 31 * Double.hashCode(re) + Double.hashCode(im);
}
@Override public String toString(){
return "(" + re + " + " + im + "i)";
}
}
- 사칙 연산 메서드들이 인스턴스 자신은 수정하지 않고 새로운 Complex 인스턴스를 만들어 반환한다. → 피연산자 자체는 그대로인 프로그래밍 패턴을 함수형 프로그래밍이라고 한다.
- 절차적 혹은 명령형 프로그래밍에서는 메서드에서 피연산자인 자신을 수정해 자신의 상태가 변한다.
- 함수형 프로그래밍은 코드에 불변이 되는 영역의 비율이 높아지는 장점을 가진다
반응형
불변객체를 사용의 장점
- 불변객체는 근본적으로 스레드 안전하여 따로 동기화할 필요가 없다
- 안심하고 공유가능
- 캐싱기능을 사용 수월 (재사용 가능 → 재사용할때 마다 같은 값이 나오기 때문에)
- 방어적 복사가 필요없다
- 복사자체가 의미가 없다(복사를 해도 원본이랑 같기 때문에 굳이 clone 메서드나 복사 생성자를 만들 필요가 없다)
- 자유롭게 공유할 수 있으며, 불변 객체끼리는 내부 데이터를 공유할 수 있다
- 상태가 변하지 않기 때문에 불일치 상태에 빠질 가능성이 없다.
불변객체 사용의 단점
- 값이 다르면 반드시 독립된 객체로 만들어야 한다.
- 값의 가짓수가 많다면 많은 비용이 들게된다.
- EX) BigInteger에서 비트 하나를 바꿔야한다면 계속해서 새로운 인스턴스를 만들게 된다.
- 그럴 경우엔 가변 동반 클래스를 제공하도록 한다.
- 값의 가짓수가 많다면 많은 비용이 들게된다.
가변동반 클래스란?
- 불변 클래스와 거의 동일한 기능을 가지고 있지만, 가변적인 클래스를 가변 동반 클래스라고 한다.
- 가변 동반 클래스는 주로 불변 클래스가 비즈니스 로직 연산 등 시간 복잡도가 높은 연산시 불필요한 클래스 생성을 막기위해 내부적으로 사용한다.
- 대표적으로 불변 클래스인 String은 가변 동반 클래스인 StringBuilder가 있다.
정리
- Getter가 있다고해서 무조건 Setter를 만들지는 말자(특히 값(Value) 객체(Object))
- 클래스는 꼭 필요한 경우가 아니라면 불변이어야 한다
- 장점이 무수히많고, 단점은 잠재적 성능저하 하나 뿐이라
- 불변으로 만들 수 없는 클래스라도 변경할 수 있는 부분을 최소한으로 줄이자
- 변경해야 할 필드를 뺀 나머지 모두를 final로 선언
- 다른 합당한 이유가 없다면 모든 필드는 private final이여야 한다
- 생성자는 불변식 설정이 모두 완료된(초기화)가 완벽히 끝난 상태의 객체를 생성해야 한다
- 생성자와 정적 팩터리 외에는 그 어떤 초기화 메서드도 public으로 제공해서는 안된다
반응형