불변 객체란 무엇인가

오늘 포스팅 할 것은 불변 객체이다.

불변객체.. 이름부터 "변하지 않는 객체" 인 것 같지 않은가.. 얼마나 직관적인지..!

사랑은 변해도 객체는 변하지 않아..

그전에 불변객체가 왜 필요한지를 알기 위해 빌드업부터 시작해보겠다.

아무튼 우리가 자바에서 사용하는 데이터 타입에는 크게 두가지가 있다 바로 기본형과 참조형 이다.

간단하게 예를 들면, 아래와 같다.

int number = 10; // 기본형
Object obj = new Object(); // 참조형

기본형은 그냥 직관적으로 봐도 잘 알테고, 참조형은.. 확실하게 알지 못한다면 더 공부를 하고 와야한다..

그냥 일단 참조형 -> 객체의 주소 값 저장 정도로만 알고 일단 킵고잉하자.


먼저 기본형에 대해 예를 들어보면, 간단하게 a 에 10을 대입하고, b 에는 a의 값을 대입하는 코드이다.

int a = 10;
int b = a;

기본형은 하나의 값을 절대로 공유하지 않는다  -> 이게 먼소리냐면, 다들 일단 '자바는 항상 값을 복사해서 대입한다' 라는 것을 알고 있을 것이다. 그래서 즉, 위 코드에서 a 와 b는 10이라는 값으로 동등성(equals) 을 가지지만, 동일성 (==) 은 가지지 않는다! -> 즉 a 와 b 는 서로 다른 10을 가지고 있는 것이다. 

(당연한 얘기지만서도, 자바에서 중요한 내용이니.. 리마인드..)


다음은 참조형이다. 참조형 변수는 언제나 객체의 주소 값을 저장한다. 편의상 주소값을 x00n 으로 표시 하겠다.

먼저 Korea 클래스를 하나 만들겠다.

public class Korea {

    private String value;

    public Korea(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "Korea{" +
                "value='" + value + '\'' +
                '}';
    }
}

 

그리고 이제 메인 메서드를 만들어 아래와 같은 코드를 입력했다고 가정하자.

Korea kor1 = new Korea("서울"); // 주소값 x001로 가정
Korea kor2 = kor1;

위 코드 결과 값으로는 kor1 의 참조값을 복사하여 kor2 의 대입하였으니, 즉 둘다 x001이라는 객체를 가르키게 된다.

 

근데 만약 kor2의 value를 부산으로 바꾸고 싶을때, 어떻게 하면 될까? kor2.setValue("부산") 메서드로 변경을 시도하면 될까?

정답은 안된다. 이유는 다음과 같다.

분명 우리는 kor1 의 참조값을 kor2에 대입했다. 그러므로 두 변수는 같은 인스턴스를 가르키고 있다. 즉, 만약 setValue()를 통해 value 값을 부산으로 바꾼다면, 인스턴스 자체의 value 값이 부산으로 변경되어, kor1 의 값도 부산으로 바뀌게 될것이다.  -> 자바에선 이걸 사이드 이펙트가 발생한다고 칭한다.


근데 그러면  kor2 = kor1이 틀린 코드일까?

그것도 아니다. 문법적으로 틀리지 않았다. 다만 자바에서는 여러 변수가 하나의 객체를 참조하는 것을 막을 방법이 없다.


해결방법이 뭔가요 그래서ㅋㅋ

쉽다. 그냥 각 변수가 서로 다른 인스턴스를 참조하도록 인스턴스를 추가로 만들면 된다.

Korea kor1 = new Korea("서울"); // x001
Korea kor2 = new Korea("부산"); // x002

이렇게 되면 서로 다른 인스턴스를 참조하므로, 각 변수에 담긴 참조값이 다르다. 이렇게 되면 부담 없이 kor2의 지역을 변경하던 말던 씹고 뜯고 맛보고 즐기고 알아서 바꿔주면 된다.


근데 사실 위 방법으로 해결하면, 인스턴스를 두개 생성하기 때문에, 상당히 메모리 관점에서 비효율적이다.

더구나 만약 처음에는 둘다 서울을 가르키게 하고 싶다면, 그냥 두개가 같은 객체를 참조하도록 했으면 더욱 효율적이었을 거다.

문제는 우리가 kor2 값을 변경하고 싶은 나머지, 객체의 value 값을 직접 수정하여 문제가 발생하고 있다.

-> 즉 애초에 외부에서 value 값에 접근하지 못하도록 하면 안되는건가? 그러면 개발하다가 컴파일 오류로 빠르게 버그를 잡아 낼 수 있지 않을까?

자, 빌드업이 길었다.

위 문제의 근본적인 문제인 객체 필드에 직접 접근할 수 있었던걸 아예 원천 차단 해버리자. 아래 코드를 참고하자

public class ImmutableKorea {

    private final String value; // 상수로 설정

    public ImmutableKorea(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }

	/* setValue() 메서드 삭제
    public void setValue(String value) {
        this.value = value;
    }
    */ 

    @Override
    public String toString() {
        return "ImmutableKorea{" +
                "value='" + value + '\'' +
                '}';
    }
}

아까 만든 Korea 클래스에서 value값에 접근하지 못하도록, value를 상수로 설정해버리고, 값을 조작하는 메서드인 setValue 메서드를 그냥 삭제해버렸다. 이제 우리는 생성자를 통해 객체를 생성할 때 말고는 value에 접근 조차 할 수 없다.

이렇게 클래스를 만들어 놓고 아래 코드를 메인 메서드에서 실행한다고 가정하자.

Korea kor1 = new Korea("서울"); 

kor2.setValue("부산") // 이 메서드는 삭제 했으므로, 아예 사용 불가

이후 어떤 방법을 동원해서 value 값을 변경한다고 해도, final 키워드로 설정되어 생성할 때 이후에 절대 값을 변경 할 수 없게 원천 차단 할 수 있게 되었다.

즉, 최종적으로 값을 바꾸고 싶다면 결국 kor2 변수에는 새로운 객체를 생성하여 그 참조값을 담아주어 서로 다른 객체를 관리할 수 있게끔 해결 하는 수 밖에 없게 되었다.


좋은 프로그램은 제약이 많은 프로그램이라고 했다.

어떤 문제가 발생하기 전에, 지피지기면 백전백승, 적절한 제약을 둔다면 유지보수면에서 활용도가 상당히 높으니, 확실하게 알아두고 넘어가자!

그럼 오늘도 20000!