Java

[Java] 불변 객체

oneH 2025. 5. 7. 21:12

 

자바에서 데이터 타입은 크게 기본형과 참조형으로 나뉜다.

 

기본형: 하나의 값을 여러 변수에서 공유하지 않는다.

참조형: 하나의 객체를 참조값을 통해 여러 변수가 공유한다.

 

 


 

기본형 값 복사

 

package immutableBlog;

public class CallByValue {
    public static void main(String[] args) {
        int a = 7;
        int b = a;
        System.out.println("a = " + a);
        System.out.println("b = " + b);
        b = 300;
        System.out.println("a = " + a);
        System.out.println("b = " + b);
    }
}

 

 

 

 

 

기본형은  b=a 연산을 해도 하나의 변수를 쓰는 것이 아니다. 다시 말해 값을 공유하는 것이 아닌 a의 값을 복사하여 대입한다.

 

결과적으로 a, b는 복사 대입했을 때는 둘 다 7이라는 정수값을 가지고 있지만 메모리상 a에 속하는 7과 b에 속하는 7이 각 각 별도로 존재한다.

 


 

참조형 값 복사

 

package blogimmutability;

public class Reference {
    public static void main(String[] args) {
        Address myAddress = new Address("서울");
        Address yourAddress = myAddress;    //같은 곳 산다고 가정
        System.out.println("myAddress: " + myAddress.getAddress());
        System.out.println("yourAddress: " + yourAddress.getAddress());
        System.out.println("yourAddress 이사: 부산으로");
        yourAddress.setAddress("부산");
        System.out.println("myAddress: " + myAddress.getAddress());
        System.out.println("yourAddress: " + yourAddress.getAddress());

    }
}

class Address {
    private String address;

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

    public String getAddress() {
        return address;
    }

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

}

 

 

 

myAddress와 yourAddress는 같은 주소값(참조값)을 지니고 있다. 따라서 어느 한 곳에 수정이 일어나면 두 곳 전부 같은 곳을 참조하고 있으므로 수정된 값을 공유하는 것이다.

 

즉, 참조형 복사는 원본 데이터에 영향이 갈 수 있다.

 

 


 

 

사이드 이펙트(Side Effect)

 

참조 값을 공유하게 되면 사이드 이펙트(Side Effect)가 발생할 수 있다. 사이드 이펙트는 프로그래밍에서 어떤 계산이 주된 작업 외에 추가적인 부수 효과를 일으키는 것을 말한다.

 

위 코드에서 yourAddress 만 "부산"으로 값을 변경하였지만 myAddress도 문자열값이 변경되었다.

개발자가 의도해서 변경한거면 상관없지만 의도치 않게 다른 부분에 영향을 미치며 인지하지 못할 시 디버깅이 어려워지고 안정성이 저하된다.

 

 

사이드 이펙트 해결법 1

 

단순하게 참조값을 복사, 공유를 안 하면 된다.

 

Address myAddress = new Address("서울");
Address yourAddress = new Address("서울");    //같은 곳 산다고 가정

 

두 객체는 서로 다른 참조값을 가지고 있다. 

객체 생성을 서로 다르게 하고 독립적으로 값을 수정하면 된다.

 

 

사이드 이펙트 해결법 2

 

같은 객체를 공유하지 않으면 사이드 이펙트는 발생하지 않는다. 하지만 JVM에서는 사이드 이펙트가 큰 에러가 아니다. 오히려 참조값 공유를 JVM이 막지 않는다. 이러한 이유로 프로그래밍 중 실수로 참조값을 공유하게 된다면 어떻게 될까?

 

사이드 이펙트가 발생할 것이다. 이러한 문제를 해결하기 위해 불변 객체를 이용해야 한다.

 

 

 


 

 

불변객체

 

class ImmutableAddress {
    //상수 이용
    private final String address;

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

    public String getAddress() {
        return address;
    }

    /*  address 값이 상수이므로 객체 생성 이 후 값 변경 못함.
    public void setAddress(String address) {
        this.address = address;
    }
    */

}

 

 

주소값을 수정 못하게 막으면 된다. 인스턴스 변수 address를 상수로 바꾸어주면 set method로 수정도 못한다. 처음 생성자로 인해 객체 생성된 값을 가지고 프로그램이 끝날 때 까지 해당 객체 인스턴스 변수값은 수정 못한다.

 

 

객체 공유 참조는 JVM에서 막지 않으니 객체 공유는 어디서든 일어날 수 있다. 객체 공유를 인지하지 못하면 사이드 이펙트가 발생하는데 사이드 이펙트를 막기 위해서 불변 객체를 만들어 사용한다.

불변 객체는 final 키워드로 인스턴스 변수 값 변경을 막을 수 있다. 값을 변경하고 싶다면 새로운 객체를 생성하는 것 말곤 방법이 없다.

 

 

 

 

package blogimmutability;

public class Reference {
    public static void main(String[] args) {
        Address address = new Address("서울");
        MemberClass memberClass1 = new MemberClass("customer1", address);
        MemberClass memberClass2 = new MemberClass("customer2", address);

        System.out.println(memberClass1.toString());
        System.out.println(memberClass2.toString());

        System.out.println("customer2 주소 변경!! 부산으로");
        memberClass2.setAddress(new Address("부산"));

        System.out.println(memberClass1.toString());
        System.out.println(memberClass2.toString());

    }
}

final class MemberClass {
    //상수 이용
    private String name;
    private Address address;

    public MemberClass(Address address) {
        this.address = address;
    }

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

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

    @Override
    public String toString() {
        return "MemberClass{" +
                "이름='" + name + '\'' +
                ", address=" + address.getAddress() +
                '}';
    }
}

class Address {
    private final String address;

    Address(String address) {
        this.address = address;
    }

    public String getAddress() {
        return address;
    }
}

 

 

 

 

 

 

 

 

 

 

 

 

package blogimmutability;

public class Reference {
    public static void main(String[] args) {
        Obj1 obj1 = new Obj1(30);
        Obj1 obj2 = obj1.add(500);
        System.out.println(obj1.getValue());
        System.out.println(obj2.getValue());
    }
}

class Obj1 {
    private final int value;

    Obj1(int value) {
        this.value = value;
    }

    public Obj1 add(int value) {
        int returnValue = this.value + value;
        return new Obj1(returnValue);
    }

    public int getValue() {
        return value;
    }
}

 

해당 코드는 기존의 add 전 정수값과 add 연산 이 후 정수값 둘 다 보존할 수 있는 코드이다.