Java

불변 객체

공부처음하는사람 2024. 10. 10. 01:42

 

기본형과 참조형의 공유

 

자바의 데이터타입은 기본형(Primitive Type), 참조형 (Reference Type)으로 나눌 수 있다.

기본형: 하나의 값을 여러 변수에서 절대 공유하지 않음

int a = 10;
int b = a;
b = 20;

// a = 10 , b = 20

 

참조형: 하나의 객체를 참조값을 통해 여러 변수에서 공유할 수 있음

Address a = new Address("서울");
Address b = a;
b.setValue("부산");

// a,b 둘 다 부산으로 변경

 

공유 참조와 사이드 이펙트

사이드 이펙트(Side Effect)는 프로그래밍에서 어떤 계산이 주된 작업 외에 추가적인 부수 효과를 일으키는것을 말한다.

이로인해 디버깅이 어려워지고 코드의 안정성이 저하될 수 있다.

(엄한곳에서 버그가 발생해 찾기도 어려운 상태라고 보면 됨)

 

위 코드에선 사실 Address b만 부산으로 변경하고 싶었는데, 변경되지 말아야할 a도 부산으로 변경되어 버린것이다.

이 문제 해결방법은 단순히 Address b = new Address()처럼 다른 인스턴스를 참조하면 되는데, 애초에 이런 일이 발생하지

않게 자바에서 컴파일 오류를 내주면 되는거 아닌가 생각할 수 있다.

Address a = new Address("서울");
Address b = a;  //  << 이거

 

하지만 위와같은 참조값의 공유를 막을 방법이 없다. 자바 문법상에 전혀 문제가 되지 않기 때문이다.

따라서 객체의 공유를 막을 방법이 없다는 것이다..

 

이 예제는 매우 단순하기 때문에 쉽게 찾을 수 있지만, 복잡한 상황에선 쉽게 찾기 힘들 수 있다.

 

불변 객체 - 도입

위 사이드 이펙트의 근본적인 원인은 객체를 공유하는 것이 아닌, 공유된 객체의 값을 변경했기 때문이다.

값을 변경하지 못하게 하려면 final을 사용하면 된다.

 

package lang.immutable.address;

public class ImmutableAddress {

    private final String value; // final 설정

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

    public String getValue() {
        return value;
    }

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

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

(매우 간단해서 당황)

 

set메서드만 삭제해도 되지만,  명시적으로 final을 붙임으로써 main메서드에서 다른 개발자가 set메서드를 생성하지 못하게 원천적으로

막아줄 수 있다는 것이다.

 

package lang.immutable.address;

public class RefMain2 {

    public static void main(String[] args) {

        ImmutableAddress a = new ImmutableAddress("서울");
        ImmutableAddress b = a;

        System.out.println(a);
        System.out.println(b);

        // b.setValue("부산");
        b = new ImmutableAddress("부산");
        System.out.println("부산 -> b");

        System.out.println(a);
        System.out.println(b);
    }
}

 

이렇게 되면 다른 사람이 코드를 이어받거나 했을 때 인스턴스를 생성하는 방법으로 하게 될 것이다..

값이 다 같이 바뀌어야 하는 경우엔 위와 같이 객체를 공유해도 되지만, (가족이 다같이 이사를 간다거나?)

그게 아니라면 불변객체로 막아줄 수 있다는 것이다.

 

불변객체 - 값 변경

불변 객체에서 값을 변경해야하는 경우이다.

 

package lang.immutable.change;

public class ImmutableObj {

    private final int value;

    public ImmutableObj(int value) {
        this.value = value;
    }

    public ImmutableObj add(int addValue) {
        int result = value + addValue;
        return new ImmutableObj(result);
    }

    public int getValue() {
        return value;
    }
}

 

final 이기 때문에 setter를 생성할 수 없다.

따라서 add 메서드에 ImmutableObj 객체를 만들어서 반환해줘야 한다.

 

package lang.immutable.change;

public class ImmutableMain {

    public static void main(String[] args) {

        ImmutableObj obj = new ImmutableObj(10);
        ImmutableObj obj2 = obj.add(20);
        obj.add(20); // 이렇게 사용했을 땐 obj 인스턴스를 참조하기 때문에 불변값 10이 그대로 나오게 됨
        // add메서드의 리턴값을 다시 한번 확인해보자

        System.out.println(obj.getValue());
        System.out.println(obj2.getValue());
    }
}

 

obj2는 add메서드에서 생성한 인스턴스를 참조한다. 따라서 더해진 값 30이 나오게 되는것이다.

계산 결과를 새로운 객체를 만들어서 반환

 

 

불변 객체에서 변경과 관련된 메서드는 보통 새로운 객체를 만들어 반환하기 때문에 꼭 반환값을 받아야 한다.

 

*불변객체에서 값을 변경하는 경우 withAdd()처럼 with로 시작하는 경우가 많다.  set을 사용하는 경우 불변객체가 아니라고 생각할 수 있기때문

'Java' 카테고리의 다른 글

래퍼 클래스, Class 클래스  (3) 2024.10.17
String 클래스  (1) 2024.10.15
Object  (1) 2024.10.09
제네릭 - 제한된 타입 파라미터/ 와일드카드  (0) 2024.07.11
제네릭(Generic) 메소드  (0) 2024.07.11