개발하는 두더지

[Effective Java 규칙15] 변경 가능성을 최소하하라 본문

Java,Android

[Effective Java 규칙15] 변경 가능성을 최소하하라

덜지 2018. 9. 14. 17:49

[Effective Java 규칙15] 변경 가능성을 최소하하라

Effective Java 2/E 책과 구글링을 통해 내용을 정리하고 개인적인 견해가 포함된 글입니다.



자바 플랫폼에는 String, 기본 자료형 클래스, BigInteger, BigDecimal과 같은 변경 불가능(immutable) 클래스가 많다. 변경 불가능 클래스는 변경 가능 클래스보다 설계하기 쉽고, 구현하기 쉽고, 사용하기 쉽다. 오류 가능성도 적으며 더 안전하다.

아래 다섯가지 규칙을 따르면 변경 불가능한 클래스를 만들 수 있다.

1. 객체 상태를 변경하는 메서드를 제공하지 않는다.

2. 상속할 수 없도록 한다.

3. 모든 필드를 final로 선언한다. 이렇게하면 자바 메모리 모델에 명시된 바와 같이 새로 생성된 객체에 대한 참조가 동기화 없이 다른 스레드로 전달되어도 안전하다.

4. 모든 필드를 private으로 선언한다. 그러면 클라이언트가 필드가 참조하는 객체를 직접 수정하는 일을 막을 수 있다. 

5. 변경 가능 컴포넌트에 대한 독점적 접근권을 보장한다. 



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 add(Complex c) {
return new Complex(re + c.re, im + c.im);
}

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

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

public Complex divide(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(re, c.re) == 0
&& Double.compare(im, c.im) == 0;
}

@Override
public int hashCode() {
int result = 17 + hashDouble(re);
result = 31 * result + hashDouble(im);
return result;
}

private static int hashDouble(double val) {
long longBits = Double.doubleToLongBits(val);
return (int)( longBits ^ (longBits >>> 32));
}

@Override
public String toString() {
return "(" + re + " + " + im + "i)";
}
}

이 클래스는 복소수(실수부, 허수부)를 표현하는 클래스이다. 사칙연산은 this를 반환하는 대신 새로운 객체를 반환하도록 구현되어 있는데 이는 대부분의 변경 불가능 클래스가 따르는 패턴이다. 함수형 접근법으로도 알려져 있는 이러한 패턴은 객체를 변경하는 대신 연산을 적용한 결과를 새로웁게 만들어 반환하는 패턴이다.


rx를 배우면 익숙한 개념이고, 변경 불가능성을 보장하므로 장점이 많다. 일단 단순하며 한가지 상태만 갖는다. 또한 스레드에 안전하므로 어떤 동기화도 필요가 없고, 여러 스레드가 동시에 사용해도 상태가 훼손될 일이 없다. 


변경 불가능한 객체는 자유롭게 공유할 수 있는데 한가지 방법은 자주 사용되는 값을 public static final 상수로 만들어서 캐시하여 거듭 생성되지 않도록 하는 정적 팩터리 패턴을 이용하는 것이다. 기본 자료형에 대한 객체 클래스들 (boxed primitive class)와 BigInteger 클래스는 이미 이렇게 구현되어 있다. 정적 팩터리를 사용하면 규칙 1에서와 같이 새로운 객체를 만드는 대신 기존 객체를 공유하므로 메모리 요구량과 GC 수집 비용이 줄어들게 된다.


유일한 단점은 값마다 별도의 객체를 만들어서 사용한다는 점인데, 객체 생성 비용이 높을 가능성이 있다. 이럴때는 연산 중 자주 요청되는 값을 기본 연산으로 제공하는 방법으로 대응할 수 있다. 



정리를해보면,

변경 불가능 클래스 구현 규칙은 어떤 메서드도 객체를 수정해서는 안되며, 모든 필드는 final로 선언되어야 한다. 아직 익숙하지 않다면 과한 작업이 될 수 있다. 실제로는 어떤 메서드도 외부에서 관측 가능한 상태 변화를 야기하지 않아야 한다는 것으로 이해해야 한다. 




String을 로그찍을 때 String들을 + 하면 Warning이 뜬다. 그리고 StringBuilder에 비해 속도 차이가 많이 난다.







Comments