즉, 불변 클래스에 간직된 정보는 고정되어 객체가 파괴되는 순간까지 절대 달라지지 않는다.
🧐 불변 클래스의 장점
가변 클래스보다 설계하고 구현하고 사용하기 쉽고 오류가 생길 여지가 적고 훨씬 안전하다. ->라고 책에서는 말하는데, 사용하기 쉽고 오류가 생길 여지가 적고 훨씬 안전 한건 객체 내부 값이 변경되지 않기 때문에 생기는 장점인 거 같은데 왜??? 가변 클래스보다 설계 및 구현이 쉬운 걸까? 이것도 불변이기 때문에 생기는 장점인데, 객체의 값이 변동되지 않도록 사용하는 방법 중 하나인 방어적 복사처럼 에러 상황에 대한 처리를 많이 고려하지 않아도 되기 때문 아닐까? 추측해본다.
🤔 클래스를 불변으로 만들기 위한 방법
객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다.
클래스를 확장할 수 없도록 한다. 대표적인 방법으로는 클래스를 final로 처리하는 것 -> final로 클래스를 선언하면 확장이 불가능한가? yes 왜냐 final로 하면 다른 클래스에서 상속받지 못하는 클래스가 되기 때문에 확장 자체가 불가능하다
모든 필드를 final로 선언한다. 시스템이 강제하는 수단을 이용해 설계자의 의도를 명확히 드러내는 방법 -> 필드를 final로 선언하면 값이 불가능하게 되는 즉 문법적으로 방지+ final 키워드를 사용한 것 자체가 해당 객체를 설계한 설계자의 입장에서 사용자에게 해당 필드는 변경 불가능한 것임을 알려주는 것
모든 필드를 private으로 선언한다. 기술적으로는 기본 타입 필드나 불변 객체를 참조하는 필드를 public final로만 선언해도 불변 객체가 되지만 이렇게 하면 다음 릴리스에서 내부 표현을 바꾸지 못하므로 권하지 않음 ex)
-> testValue가 private 일 경우 직접 접근해 수정할 수 x -> private으로 선언하면 클라이언트에서 임의로 접근해 필드를 변경할 수 있는 가능성을 지워준다. -> public final로 선언하면 다음 릴리스에서 내부 표현을 바꾸지 못하기 때문에 권하지 않는다??? 이것은 public final로 선언해도 불변을 보장하지만, 다음 릴리즈 때 public -> private으로 변경하고 싶어도 변경점이 많아 내부 표현을 바꾸지 못하기 때문에 권하지 않는다는 뜻 같다...
컴포넌트(component)란 여러 개의 프로그램 함수들을 모아 하나의 특정한 기능을 수행할 수 있도록 구성한 작은 기능적 단위
-> testValue를 접근할 수 있는 메서드 getTestValue를 통해 참조를 획득하면 testValue를 마음대로 변경할 수 있다. -> 즉 설계자의 의도대로 동작하는 것이 아니다. -> 이럴 때는 어떻게 해결할까? 위에서 언급한 방어적 복사를 통해 해결하자
-> getTestValue를 통해 참조값을 얻으면 Test 생성 시 만들어진 참조값 testValue와 다르다. -> test 클래스의 testValue 참조값과 getTestValue 메서드를 통해 반환된 객체 참조값이 다른 것을 확인할 수 있다. 위의 규칙을 만족하는 ex)
public final class
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 divideBy(Complex c){
double tmp = c.re*c.re + c.im * 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);
}
}
-> 해당 클래스는 복소수(실수부와 허수부로 구성된 수)를 표현한다.
-> 각 사칙연산 메서드(plus, minus, times, divideBy)는 자신은 수정하지 않고새로운 Complex인스턴스를 만들어 반환 해당 클래스 같은 패턴을 함수형 프로그래밍이라 한다.
->함수형 프로그래밍이란?피연산자에 함수를 적용해 그 결과를 반환하지만 피연산자 자체는 그대로인 프로그래밍 패턴을 함수형 프로그래밍이라 한다.
->절차적 or 명령형 프로그래밍이란?메서드에서 피연산자를 수정해 피연산자 상태가 변하게 된다.
-> 메서드 네이밍에 주목하자
-> (add 같은) 동사 대신 (plus 같은) 전치사를 사용한 점에 주목
->왜 전치사를 사용했을까?해당 메서드가 객체의 값을 변경하지 않는다는 사실을 강조하기 위해서
🤔 불변 객체 장점
1. 스레드 안전이기 때문에 따로 동기화할 필요가 없다.
-> 여러 스레드가 동시에 사용해도 절대 훼손되지 않음
-> 즉 스레드 safe 하게 만드는 가장 쉬운 방법
-> 따라서 불변 객체는 안심하고 공유할 수 있기 때문에 인스턴스를 최대한 재활용하는 것을 권장
-> 어떻게 재활용을 할까?
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)
-> 가장 쉬운 재활용 방법: 자주 쓰이는 값을 상수로 제공하는 것
2. 불변 객체는 자유롭게 공유할 수 있음은 물론, 불변 객체끼리는 내부 데이터를 공유할 수 있음
3. 객체를 만들 때 다른 불변 객체들을 구성요소로 사용하면 이정이 많다 -> 값이 바뀌지 않는 구성요소들로 이뤄진 객체라면 구조가 복잡하더라도 불변식을 유지하기 훨씬 수월하다.
4. 불변 객체는 그 자체로 실패 원자성을 제공한다.
-> 실패 원자성? 메서드에서 예외가 발생한 후에도 그 객체는 여전히 유효한 즉 메서드 호출 전과 동일한 상태여야 한다는 것
-> 예외가 무엇일까? 생각해 보았을 때, 배열이 있고 해당 배열을 출력하기 위해 방어적 복사를 사용해 해당 배열 참조값을 넘겨주는 getxxx라는 메서드가 있다 가정해보자. 이때 방어적 복사가 된 객체의 참조값을 얻어 add라는 메서드로 해당 객체의 값을 변경할 수 있다.
이렇게 불변 객체의 값을 변경하려는 시도가 ''예외'이며 add메서드를 호출해 객체의 값을 변경하려 시도해도, 원래 배열 값은 변경되지 않는다. 왜? 방어적 복사를 통해 객체의 참조값을 넘겨주었기 때문이다.