불변객체

황상익·2024년 5월 7일

Inflearn JAVA

목록 보기
25/61

기본형과 참조형 공유

기본형 : 하나의 값을 여러 변수에서 절대로 공유 X
참조형 : 하나의 객체를 참조값을 통해 여러 변수에서 공유

public class PrimitiveMain {
    public static void main(String[] args) {
        //기본형은 절대로 값을 공유하지 X
        int a = 10;
        int b = a; // a -> b 복사 후 대입

        System.out.println("a = " + a);
        System.out.println("b = " + b);

        b = 20;
        System.out.println("a = " + a);
        System.out.println("b = " + b);
    }
}

기본형 변수 a 와 b는 절대로 하나의 값을 공유 X
b = a 라고 하면 자바는 항상 값을 복사해서 대입
결과적으로 a와 b는 둘다 똑같은 숫자의 값을 갖는다. 하지만 a가 갖는 값과 b가 갖는 값은 완전히 다른 값. 메모리상에서도 각각 별도로 존재


기본형 변수는 하나의 값을 절대로 공유 X, 따라서 값을 변경해도 변경한 변수만 대입

package chap23.immutable;

public class Address {
    private String value;

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

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

    public String getValue() {
        return value;
    }

    @Override
    public String toString() {
        return "Address{" +
                "value='" + value + '\'' +
                '}';
    }
}
public class RefMain1 {
    public static void main(String[] args) {
        //참조형 변수는 하나의 인스턴스를 공유 가능
        Address a = new Address("seoul");
        Address b = a;
        System.out.println("a = " + a);
        System.out.println("b = " + b);

        b.setValue("부산");
        System.out.println("a = " + a);
        System.out.println("b = " + b);
    }
}
b.setValue("부산");
        System.out.println("a = " + a);
        System.out.println("b = " + b);


참조형 변수들은 같은 참조값을 통해 같은 인스턴스를 참조 가능
참조값을 복사해서 전달하므로 결과적으로 같은 인스턴스를 참조

공유 참조와 사이드 이펙트

어떤 계산이 주된 작업 이외에 추가적 부수 효과를 일으키는 것

b를 변경했지만, a도 같이 변경되는 상황 = 사이드 이펙트

의도치 않게 다른 부분이 변경되는 것을 사이드이펙트라고 한다.

사이드 이팩트 해결방안

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


a와 b는 서로 다른 Address 인스턴스를 참조한다.


a와 b는 서로 다른 인스턴스를 참조, 따라서 b가 참조하는 인스턴스의 값을 변경해도 a에는 영향을 주지 않는다.

여러 변수가 하나의 객체를 공유하는 것을 막을 방법 X

객체를 공유하지 않으면 문제 해결. 여기서 변수 a,b가 서로 각각 다른 주소지로 변경할 수 있으면 됨 => 서로 다른 객체를 참조

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

이 경우 a, b 둘다 같은 Address 인스턴스를 바라보기 때문에 한쪽의 부산으로 변경하는 것 불가능

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

한쪽의 주소만 부산으로 변경하는 것 가능

참조값의 공유를 막을 수 잇는 방법 X

Address a = new Address("서울");
Address b = a; //참조값 대입을 막을 수 있는 방법이 없다

여러 변수가 하나의 참조값을 공유하지 않으면 문제 해결
하지만 Address를 사용하는 개발자 입장에서는 실수로 b = a라고 해도 오류 발생 X

Address b = new Address("서울") //새로운 객체 참조
Address b = a //기존 객체 공유 참조

참조값을 다른 변수에 대입 하는 순간 여러 변수가 하나의 객체를 공유. 객체의 공유를 막을 수 있는 방법 X

기본형은 항상 값을 복사하기 때문에 값이 절대로 공유되지 않는다. 하지만 참조형의 경우 참조값을 복사해서 대입하기 때문에 여러 변수에서 얼마든지 같은 객체 공유

package chap23.immutable;

public class RefMain3 {
    public static void main(String[] args) {
        //참조형 변수는 하나의 인스턴스를 공유 가능
        Address a = new Address("seoul");
        Address b = a;
        System.out.println("a = " + a);
        System.out.println("b = " + b);

        change(b, "부산");
        System.out.println("a = " + a);
        System.out.println("b = " + b);
    }

    public static void change(Address address, String changeAddress){
        System.out.println("changeAddress = " + changeAddress);
        address.setValue(changeAddress);
    }
}

여러 변수가 하나의 객체를 참조하는 공유 참조를 막을 수 있는 방법 X. 공유참조로 인해 발생하는 문제를 어찌 해결??

불변 객체 도입

객체를 공유한다고 바로 사이드이펙트 발생하지 않는다. 문제의 직접원인은 공유된 객체의 값을 변경한 것

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

b = a와 같이 "서울" 이라는 Address 인스턴스를 a, b가 함께 사용하는 것이, 다음 코드와 같이 서로 다른 인스턴스를 사용하는 것 보다 메모리와 성능상 더 효율적

b.setValue("부산"); //b의 값을 부산으로 변경해야함
System.out.println("부산 -> b");
System.out.println("a = " + a); //사이드 이펙트 발생
System.out.println("b = " + b);

자바에서 여러 참조형 변수가 하나의 객체를 참조하는 공유 참조 문제는 피할 수 없다.
기본형과 다르게 참조형 객체는 처음부터 여러 참조형 변수에서 공유 될 수 있도록 설계

문제의 직접적 원인은 공유 될 수 있는 Address 값을 어디선가 변경

불변 객체 도입

객체 상태가 변하지 않는 객체를 불변 객체

public class ImmutableAddress {
    private final String value;

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

    public String getValue() {
        return value;
    }

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

내부 값 변경되면 안되기 때문에 value 필드를 final로 선언
값을 변경할 수 있는 setValue를 제거

public class RefMain1 {
    public static void main(String[] args) {
        //참조형 변수는 하나의 인스턴스를 공유 가능
        ImmutableAddress a = new ImmutableAddress("seoul");
        ImmutableAddress b = a;
        System.out.println("a = " + a);
        System.out.println("b = " + b);

//        b.setValue("부산"); 값 변경 불가능
        b = new ImmutableAddress("부산");
        System.out.println("a = " + a);
        System.out.println("b = " + b);
    }
}

b.setValue 메서드 자체 제거.
ImmutableAddress 인스턴스의 값을 변경할 수 있는 방법 X
ImmutableAddress를 사용하는 개발자는 값을 변경하려고 시도, 값을 변경하는 것이 불가능
변경을 하고 싶다면 인스턴스를 따로 생성해서 대입


불변이라는 단순한 제약을 사용해서 사이드이펙트라는 큰 문제를 해결
객체의 공유참조는 막을 수 없다. 객체의 값을 변경하면 다른 곳에서 참조하는 변수의 값도 함께 변경되는 사이드 이펙트 발생

불변 객체는 값을 변경할 수 없기 때문에 사이드 이펙트 원천 차단

불변 객체 - 예제

public class MemberV1 {
    private String name;
    private Address address;

    public MemberV1(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "MemberV1{" +
                "name='" + name + '\'' +
                ", address=" + address +
                '}';
    }
}
public class MemberMainV1 {
    public static void main(String[] args) {

        Address address = new Address("서울");

        MemberV1 memberA = new MemberV1("황A", address);
        MemberV1 memberB = new MemberV1("황B", address);

        System.out.println("memberA = " + memberA);
        System.out.println("memberB = " + memberB);

        //memberB의 주소를 부산으로 변경
        memberB.getAddress().setValue("부산");
        System.out.println("memberA = " + memberA);
        System.out.println("memberB = " + memberB);
    }
}

불변 객체 값 변경

불변 객체를 사용하지만, 그래도 값을 변경해야 하는 메서드가 필요하다면??

public class MutableObj {
    private int value;

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

    public void add(int addValue){
        value = value + addValue;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}
public class MutableMain {
    public static void main(String[] args) {
        MutableObj mutableObj = new MutableObj(10);
        mutableObj.add(20);

        //계산 이후의 값은 사라진다.
        System.out.println("mutableObj.getValue() = " + mutableObj.getValue());
    }
}
public class ImmutableObj {
    private final int value;

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

    public ImmutableObj add(int addValue) {
        int rst = value + addValue;
        //ImmutableObj immutableObj = new ImmutableObj(30);
        return new ImmutableObj(rst);
    }

    public int getValue() {
        return value;
    }
}

핵심은 add 메서드
불변 객체는 값을 변경하면 안된다.
하지만 기존 값에 새로운 값을 더해야 한다.
불변 객체는 기본 값을 변경하지 않고, 대신 계산 결과를 바탕으로 새로운 객체를 만들어서 반환

public class ImmutableMain1 {
    public static void main(String[] args) {
        ImmutableObj mutableObj = new ImmutableObj(10);
        ImmutableObj ob = mutableObj.add(20);

        //계산 이후의 값은 사라진다.
        System.out.println("mutableObj = " + mutableObj.getValue());
        System.out.println("ob.getValue() = " + ob.getValue());
    }
}

불변 객체를 설계할 때 기본 값을 변경해야 하는 메서드가 필요할 수 있다.
기존 객체의 값을 그대로 두고 대신에 변경된 결과를 새로운 객체에 담아서 반환
기존 값 그대로 유지 되는 것 볼 수 있음

package lang.immutable.change;
public class ImmutableMain2 {
 public static void main(String[] args) {
 ImmutableObj obj1 = new ImmutableObj(10);
 obj1.add(20);
 System.out.println("obj1 = " + obj1.getValue());
 }
}
profile
개발자를 향해 가는 중입니다~! 항상 겸손

0개의 댓글