[Java] 불변 객체(Immutable Object)

Yujeong·2024년 4월 28일
0

Java

목록 보기
3/22
post-thumbnail

불변 객체(Immutable Object)는 한 번 생성되면 내부 상태가 변경되지 않는 객체이다.
불변 객체를 사용하는 이유와 사용 방법을 자세히 알아보자.

기본형과 참조형

  • 기본형(Primitive Type): 하나의 값을 여러 변수에 절대로 공유하지 않음

    • ba를 대입할 때, 자바는 값을 복사해서 대입한다.
    • 그래서 b 값만 5로 변경하면, a의 값은 변경되지 않는다.
    public class PrimitiveMain {
    
      public static void main(String[] args) {
          int a = 3;
          int b = a;
          System.out.println("a = " + a);
          System.out.println("b = " + b);
    
          b = 5;
          System.out.println("a = " + a);
          System.out.println("b = " + b);
      }
    }
      a = 3
      b = 3
      a = 3
      b = 5
  • 참조형(Reference Type): 하나의 객체를 참조값을 통해 여러 변수에서 공유할 수 있음

    • 회원 정보를 보관하는 객체가 있다.
    • Hong이라는 addressA을 만들어주었다. 그리고 addressBaddressA을 대입하는 코드를 작성해주었다. 그러면 addressAaddressB는 Hong이라는 이름을 가진다.
    • 그리고나서 addressB만 이름을 Kim으로 변경해주었다. 실행 결과를 보면, 기본형과 다르게 addressA의 이름도 Kim으로 변경된 것을 볼 수 있다.
    • addressAaddressB가 같은 참조값을 가지고 있기 때문에 같은 결과가 나오는 것이다.
    public class Address {
    
        private String value;
    
        public Address(String value) {
            this.value = value;
        }
    
        public String getValue() {
            return value;
        }
    
        public void setValue(String value) {
            this.value = value;
        }
    
        @Override
        public String toString() {
            return "Address{" +
                    "value='" + value + '\'' +
                    '}';
        }
     }
    public class AddressMain {
    
      public static void main(String[] args) {
          Address addressA = new Address("Seoul");
          Address addressB = new Address("Seoul");
          System.out.println("addressA = " + addressA);
          System.out.println("addressB = " + addressB);
    
          addressB.setValue("Changwon");
          System.out.println("addressA = " + addressA);
          System.out.println("addressB = " + addressB);
      }
    }
    addressA = Address{value='Seoul'}
    addressB = Address{value='Seoul'}
    addressA = Address{value='Changwon'}
    addressB = Address{value='Changwon'}

공유 참조의 사이드 이펙트

  • 사이드 이펙트(Side Effect): 주된 작업 외에 추가적인 부수 효과를 일으키는 것
  • 문제
    AddressMain 코드에서 addressB만 변경하였지만, addressA도 함께 변경된 문제가 있었다.
    • 디버깅이 어려워짐
    • 코드의 안정성이 저하됨
  • 해결
    • 서로 다른 인스턴스를 참조하게 함
      Address addressA = new Address("Seoul");
      Address addressB = new Address("Changwon");

이렇게 서로 다른 객체를 참조하게 하면 문제가 해결된다. 하지만, 객체를 공유하는 것을 강제로 막을 방법이 없다.
그래서 객체의 상태(객체 내부 값, 필드, 멤버 변수)가 변하지않는 불변 객체를 사용한다.

불변 객체

"final로 선언하면 되지 않나?"라고 생각할 수 있다.
맞다. 그러면 setValue() 메서드를 제거해야하니까, 값 변경이 아예 불가능해진
다.

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 + '\'' +
                '}';
    }
}
public class AddressMain2 {

    public static void main(String[] args) {
        ImmutableAddress addressA = new ImmutableAddress("Seoul");
        ImmutableAddress addressB = addressA;
        System.out.println("addressA = " + addressA);
        System.out.println("addressB = " + addressB);

        addressB = new ImmutableAddress("Changwon");
        System.out.println("addressA = " + addressA);
        System.out.println("addressB = " + addressB);
    }
}
member1 = Member{value='Kim'}
member2 = Member{value='Kim'}
member1 = Member{value='Kim'}
member2 = Member{value='Hong'}

가변 객체와 불변 객체의 사용 예시

가변 객체(Mutable Object) vs. 불변 객체(Immutable Object)
가변은 처음 만든 이후, 상태가 변할 수 있다는 의미
불변은 처음 만든 이후, 상태가 변하지 않는 다는 의미

예시 상황

  • 회사 내 직원의 회사 주소를 변경하는 경우
    → 가변 객체, 같은 회사니까 모든 직원 주소 함께 변경
  • 가족이 같이 살다가 한 명이 독립하는 경우
    → 불변 객체, 독립한 식구의 주소만 변경

예시 코드

  • 가변 클래스 사용: Address
    Member1에서 Address를 사용하여 주소를 저장하도록 하였다.
    그리고 MemberMain1에서 memberB의 주소를 "Changwon"으로 변경하니까 memberA의 주소도 함께 변경되었다.
public class Member1 {

    private String name;
    private Address address;

    public MemberV(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 "Member1{" +
                "name='" + name + '\'' +
                ", address=" + address +
                '}';
    }
}
public class MemberMain1 {

    public static void main(String[] args) {
        Address address = new Address("Seoul");

        Member1 memberA = new Member1("Kim", address);
        Member1 memberB = new Member1("Hong", address);

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

        memberB.getAddress().setValue("부산");
        System.out.println("memberA = " + memberA);
        System.out.println("memberB = " + memberB);
    }
}
memberA = Member{name='Kim', address=Address{value='Seoul'}}
memberB = Member{name='Hong', address=Address{value='Seoul'}}
memberA = Member{name='Kim', address=Address{value='Changwon'}}
memberB = Member{name='Hong', address=Address{value='Changwon'}}
  • 불변 클래스 사용: Immutable Address
    Member2에서 Immutable Address를 사용하여 주소를 저장하도록 하였다.
    그리고 MemberMain2에서 memberB의 주소를 새로운 Immutable Address 객체를 생성하여 변경하였다. 이렇게 하니까 memberA의 주소는 변경되지 않았다.
public class Member2 {

    private String name;
    private ImmutableAddress address;

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

    public ImmutableAddress getAddress() {
        return address;
    }

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

    @Override
    public String toString() {
        return "Member2{" +
                "name='" + name + '\'' +
                ", address=" + address +
                '}';
    }
}
public class MemberMain2 {

    public static void main(String[] args) {
        ImmutableAddress address = new ImmutableAddress("Seoul");

        Member2 memberA = new Member2("Kim", address);
        Member2 memberB = new Member2("Hong", address);

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

        memberB.setAddress(new ImmutableAddress("Changwon"));
        System.out.println("memberA = " + memberA);
        System.out.println("memberB = " + memberB);
    }
}
memberA = Member{name='Kim', address=Address{value='Seoul'}}
memberB = Member{name='Hong', address=Address{value='Seoul'}}
memberA = Member{name='Kim', address=Address{value='Seoul'}}
memberB = Member{name='Hong', address=Address{value='Changwon'}} 

불변 객체 값 변경하기

불변 객체는 값을 변경하면 더이상 불변 객체가 아니다. 그렇다면, 기존 값에 새로운 값을 더하는 add()와 같은 메서드는 어떻게 구현할 수 있을까?

  • 기존 값(value)을 변경하지 않고, 계산 결과(result)를 바탕으로 새로운 객체를 생성하여 반환(return new ImmutableObj(result))
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;
    }
}
public class ImmutableMain {

    public static void main(String[] args) {
        ImmutableObj objectA = new ImmutableObj(30);
        ImmutableObj objectB = objectA.add(50);

        System.out.println("objectA = " + objectA.getValue());
        System.out.println("objectB = " + objectB.getValue());
    }
}
objectA = 30
objectB = 80

정리

  • 불변 객체는 재할당 가능한, 한 번 생성되면 값을 변경할 수 없는 객체
  • 기본형 타입인 경우, final을 사용하여 불변 객체 만들 수 있음
  • 참조형 타입인 경우, 추가 작업 필요

참고
김영한의 실전 자바 - 중급 1편

profile
공부 기록

0개의 댓글

관련 채용 정보