[Java 자료구조] [Java] Immutable Object(불변객체)

면접에서 "자바에서 불변객체에 대해 설명해주세요.."라는 질문을 받았다.
속으로 'final만 붙이면 불변객체 아닌가?'라는 생각을 했지만 불변객체에 대해 공부하지 않아 모른다고 했다...
그래서 찾아봤더니 생각했던 것과 다른 내용이 있어서 글을 작성한다.
이번 포스팅의 코드는 이 곳에 있습니다.




 

Immutable Object란?


객체 지향 프로그래밍에 있어서 불변객체(immutable object)는 생성 후 그 상태를 바꿀 수 없는 객체를 말한다. 반대 개념으로는 가변(mutable) 객체로 생성 후에도 상태를 변경할 수 있다. 객체 전체가 불변인 것도 있고, C++에서 const 데이터 멤버를 사용하는 경우와 같이 일부 속성만 불변인 것도 있다. 또, 경우에 따라서는 내부에서 사용하는 속성이 변화해도 외부에서 그 객체의 상태가 변하지 않은 것 처럼 보인다면 불변 객체로 보기도 한다. 예를 들어, 비용이 큰 계산의 결과를 캐시하기 위해 메모이제이션(Memoization)을 이용하더라도 그 객체는 여전히 불변하다고 볼 수있다. 불변 객체의 초기 상태는 대개 생성 시에 결정되지만 객체가 실제로 사용되는 순간까지 늦추기도 한다.
불변 객체를 사용하면 복제나 비교를 위한 조작을 단순화 할 수 있고, 성능 개선에도 도움을 준다. 하지만 객체가 변경 가능한 데이터를 많이 가지고 있는 경우엔 불변이 오히려 부적절한 경우가 있다. 이 때문에 많은 프로그래밍 언어에서는 불변이나 가변 중 하나를 선택할 수 있도록 하고 있다.
-위키백과

제가 공부한 바로 불변 객체를 한마디로 표현하자면 다음과 같습니다.

불변객체는 재할당은 가능하지만, 한번 할당하면 내부 데이터를 변경할 수 없는 객체

즉, 객체에 값을 할당하면 내부 데이터를 변경시킬 수 없다는 것입니다. 대표적인 예로 String, Integer, Boolean 등이 있습니다.
String은 String str="a", str="ab" 이런 식으로 사용하기 때문에 값이 변경한다고 생각하여 불변객체가 아닌 것으로 착각하기 쉽습니다.
하지만 이것은 str가 처음에 참조하고 있는 "a"값이 "b"로 변경되는 것이 아니라 "b"라는 새로운 객체를 만들고 그 객체를 str이 참조하게 하는 것입니다.

아래 코드는 불변이 아닌 클래스입니다.

class MutablePerson {
   public int age;
   public int name;
    
   public MutablePerson(int age, int name) {
    	this.age = age;
        this.name = name;
    }
}

그 이유는 외부에서 age나 name을 변경할 수 있기 때문입니다.
그럼 이 클래스를 불변 클래스로 만들어보겠습니다.

class ImmutablePerson {
    private final int age;
    private final int name;
    
    public ImmutablePerson(int age, int name) {
    	this.age = age;
        this.name = name;
    }
}

위와 같이 만들면 외부에서 값을 수정할 수 없습니다. 따라서 불변객체가 됩니다.
(final 변수이므로 당연히 Setter 메서드를 작성할 수 없습니다)
일반 객체들을 불변객체로 만드는 방법들은 뒤에 추가적으로 보여드리겠습니다.






 

Immutable Object의 장단점

장점

  • 객체에 대한 신뢰도가 높아집니다. 객체가 한번 생성되어서 그게 변하지 않는다면 transaction 내에서 그 객체가 변하지 않기에 우리가 믿고 쓸 수 있기 때문입니다.
  • 생성자, 접근메소드에 대한 방어 복사가 필요없습니다.
  • 멀티스레드 환경에서 동기화 처리없이 객체를 공유할 수 있습니다.

단점

  • 객체가 가지는 값마다 새로운 객체가 필요합니다. 따라서 메모리 누수와 새로운 객체를 계속 생성해야하기 때문에 성능저하를 발생시킬 수 있습니다.








     

Immutable Object 만들어보기


Immutable Object를 만드는 기본적인 아이디어는 필드에 final을 사용하고, Setter를 구현하지 않는 것입니다.
이 아이디어는 불변객체의 필드가 모두 원시 타입일 경우에만 가능하고, 참조 타입일 경우엔 추가적인 작업이 필요합니다.

이제 필드가 원시 타입만 있는 객체와 참조 타입이 있는 객체를 불변객체로 만들어봅시다.


 

원시 타입만 있는 경우

 ➡️ 변경 전

public class BaseObject {

    private int value;
    
    public BaseObject(final int value) {
        this.value = value;
    }
    
    public void setValue(int newValue) {
    	this.value = newValue;
    }
    
    // getter는 생략 했음
}
 

위 객체는 불변객체가 아닙니다. 앞서 설명드렸지만, 외부에서 Setter를 통해 int형 필드 value의 값을 변경할 수 있기 때문입니다.

필드가 원시 타입만 있으므로 이는 final 키워드를 사용해서 불변객체로 만들 수 있습니다.
 

 ➡️ 변경 후

public class BaseObject {

    private final int value;
    
    public BaseObject(final int value) {
        this.value = value;
    }
    
    // getter는 생략 했음
}

필드에 final 키워드를 사용했으므로 Setter를 구현하는 것은 당연히 불가능합니다.
따라서 이 객체의 value를 변경하려면 재할당하는 방법밖에 없습니다.


 

참조 타입이 있는 경우

참조 타입이 있는 경우의 대부분은 final을 사용하고, Setter를 작성하지 않는 것으로는 불변객체를 만들 수 없습니다.
아래의 예를 보겠습니다.

public class Animal {
    
    private final Age age;

    public Animal(final Age age) {
        this.age = age;
    }
    
    public Age getAge() {
    	return age;
    }
}

class Age {
    
    private int value;

    public Age(final int value) {
        this.value = value;
    }

    public void setValue(final int value) {
        this.value = value;
    }
    
    public int getValue() {
    	return value;
    }
}

Animal 클래스는 final을 사용하고, Setter를 구현하지 않았지만 불변객체가 될 수 없습니다. 왜냐하면 Animal 클래스의 필드인 Age의 값을 아래처럼 변경할 수 있기 때문입니다.

public static void main(String[] args) {
    Age age = new Age(1);
    Animal animal = new Animal(age);

    System.out.println(animal.getAge().getValue());
    // Output: 1

    animal.getAge().setValue(10);
    System.out.println(animal.getAge().getValue());
    // Output: 10
}

즉, 불변 객체의 참조 변수 또한 불변이어야 합니다.
필드에 참조 타입이 있는 경우는 여러가지 상황이 있을 수 있습니다. 대표적으로 (1)객체를 참조할 수도 있고, (2)Array나 (3)List 등을 참조할 수 있습니다.
이 세가지 상황에 대해 불변객체를 만들어보겠습니다.

(1) 참조 변수가 일반 객체인 경우

이 상황은 위의 예제를 고쳐보면 될 것입니다.
따라서 이는 참조 변수인 Age도 불변객체로 만들어 해결할 수 있습니다.

public class Animal {
    
    private final Age age;

    public Animal(final Age age) {
        this.age = age;
    }
    
    // getter
}

class Age {
    
    private final int value;

    public Age(final int value) {
        this.value = value;
    }
    
    // getter
}

이 상황을 보면 결국 "참조 변수도 불변 객체이어야 한다"라는 결론이 나옵니다.

(2) Array일 경우

public class ArrayObject {

    private final int[] array;

    public ArrayObject(final int[] array) {
        this.array = Arrays.copyOf(array,array.length);
    }


    public int[] getArray() {
        return (array == null) ? null : array.clone();
    }
}

배열일 경우에는 생성자에서 배열을 받아 copy해서 저장하도록 했고,
getter를 clone을 반환하도록 하면 됩니다.
배열을 그대로 참조하거나, 그대로 반환할 경우 외부에서 배열 내부값을 변경시킬 수 있기 때문에, clone을 반환하게 되면 외부에서 값을 변경시킬 수 없습니다.
만약 원시 타입 배열이 아니고, Animal[]과 같은 형태라면 해당 객체는 불변객체이어야 합니다.

public static void main(String[] args) {
	int[] array = {1, 2, 3};
      	ArrayObject arrayObject = new ArrayObject(array);

        for (int num : arrayObject.getArray()) {
            System.out.print(num + " ");
        }
        // 결과: 1 2 3

        System.out.println();
        array[0] = 999; // arrayObject의 배열 값은 변하지 않는다.

        for (int num : arrayObject.getArray()) {
            System.out.print(num + " ");
        }
        // 결과: 1 2 3
}

(3) List인 경우

List인 경우에도 Array와 마찬가지로 생성시 생성자 인자를 그대로 참조하지 않고, 새로운 List를 만들어 값을 복사하도록 해야합니다. 그리고 getter를 통해 값 추가/삭제가 불가능하도록 Collection의 unmodifiableList 메서드를 사용했습니다.

여기서 Animal은 앞서 만든 불변객체입니다.

import java.util.Collections;
import java.util.List;

public class ListObject {

    private final List<Animal> animals;

    public ListObject(final List<Animal> animals) {
        this.animals = new ArrayList<>(animals);
    }

    public List<Animal> getAnimals() {
        return Collections.unmodifiableList(animals);
    }
}

결과

public static void main(String[] args) {
    List<Animal> animals = new ArrayList<>();
    animals.add(new Animal(new Age(1)));

    ListObject listObject = new ListObject(animals);

    for (Animal animal : listObject.getAnimals()) {
        System.out.print(animal.getAge().getValue());
    }
    System.out.println();
    // Output: 1

    animals.add(new Animal(new Age(2))); // List인 animals에는 추가되지만 listObject의 List에는 추가되지 않는다

    for (Animal animal : listObject.getAnimals()) {
        System.out.print(animal.getAge().getValue());
    }
    System.out.println();
    // Output: 1
}
        

결론

  • 불변객체는 한번 할당하면 필드 값을 변경할 수 없는 객체입니다.
  • 하지만 재할당은 가능합니다.
  • 필드가 원시 타입일 경우엔 final 사용으로 불변객체를 만들 수 있고, 참조 타입일 경우엔 추가적인 작업이 필요합니다.


 

참고 자료

 

[출처] https://velog.io/@conatuseus/Java-Immutable-Object%EB%B6%88%EB%B3%80%EA%B0%9D%EC%B2%B4

 

 

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
번호 제목 글쓴이 날짜 조회 수
430 [Java] Java Console Input and Output Examples 졸리운_곰 2021.11.07 4
429 [java 인공지능] [java] 라이프 게임 (life game) file 졸리운_곰 2021.10.19 3
428 [Java][MyBatisc] myBatis에서 null과 nullString을 체크할 때 졸리운_곰 2021.09.13 2
427 [MongoDB/Java] MongoDB에 JSON 형식 데이터 삽입하기 file 졸리운_곰 2021.07.13 6
426 [Java, MongoDB] mongodb java driver 3.0: how to store JSON document 졸리운_곰 2021.07.13 3
425 [Java] \ 문자 빠구기 : replaceAll 사용시 특수문자 졸리운_곰 2021.07.13 4
424 [java, spring] Spring에서 request와 response를 JSON format 으로 한번에 로깅하기 file 졸리운_곰 2021.06.18 12
423 [Spring Boot] 2) Springboot OncePerRequestFilter 와 GenericFilterBean의 차이 file 졸리운_곰 2021.06.18 3
422 [Spring boot] [Spring boot] Spring Boot servlet filter 사용하기 졸리운_곰 2021.06.18 5
421 [SpringBoot] Filter(필터) OncePerRequestFilter간단히 사용하기 file 졸리운_곰 2021.06.18 5
420 [Spring boot] Spring boot 에서 Filter 사용하기 졸리운_곰 2021.06.18 3
419 [Spring Boot] 스프링 부트에 필터를 '조심해서' 사용하는 두 가지 방법 졸리운_곰 2021.06.18 5
418 [Java 자료구조] [Java] 문자열의 첫 글자 제거 졸리운_곰 2021.05.24 29
417 [Java 자료구조] [java] 특정 문자열 사이의 문자열 추출하기, 정규식 졸리운_곰 2021.05.24 887
416 [Java 자료구조] [JAVA] Java언어로 JSON 생성, 파싱 예제 file 졸리운_곰 2021.05.17 11
415 이클립스에서 java 버전 변경 file 졸리운_곰 2021.04.29 8
414 [Java 자료구조] [Java] Immutable Class (불변 클래스) file 졸리운_곰 2021.03.07 15
413 [Java 자료구조] 불변 객체란? Java Immutable Object file 졸리운_곰 2021.03.07 13
» [Java 자료구조] [Java] Immutable Object(불변객체) 졸리운_곰 2021.03.07 16
411 [java 자료구조] Oracle + Mybatis 환경에서의 Date 다루기 졸리운_곰 2021.02.25 24
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED