숑숑이의 개발일기

불변 객체(Immutable Object)란?

객체 지향 프로그래밍에 있어서 불변객체는 생성 후 그 상태를 바꿀 수 없는 객체를 말한다. 그렇기에 생성이후 신뢰할 수 있는 객체가 된다.

대표적인 불변객체로는 Boolean, Integer, String, BigDecimal.. 이 존재한다.

 

 불변이라는 키워드를 놓고 보면 final이라는 예악어가 생각날 수 있는데, 단순히 final을 사용한다고 해서 불변객체를 만드는 것이 아니다. final 예약어를 사용시 각 효과는 아래와 같다.

  • 변수 : 값을 수정할 수 없는 상수로 만들어줌
  • 메소드 : Overriding을 할 수 없음
  • 클래스 : 상속이 불가능 함

즉, 객체 선언시 final을 사용한다고 하더라도 객체 내부상태는 변경할 수 있다.

 

불변 객체 장점

  • Thread-Safe하여 병렬 프로그래밍에 유용하고 동기화를 고려하지 않아도 된다.
    • 불변객체는 항상 동일한 값을 보장하므로 동기화를 신경쓸 필요가 없다.
  • 내부 상태의 변경이 없기 때문에 Cache, Map, Set등의 요소로 활용하기에 적합하다.
  • 외부에 객체에 대해 변경할 수 없기 때문에 안정성이 있다.
  • 가비지 컬렉션의 성능을 높일 수 있다.
    • 상태의 변경이 필요한 경우 새로운 객체를 생성해서 사용해야 한다. 이때 새로운 객체가 많이 생성될 경우 성능상 좋지 않을거라 생각할 수 있다. 그러나 오라클에서는 객체 생성에 대한 비용은 과대평가되고있으며, 이는 불변객체를 이용한 효율로 충분히 상쇄할 수 있다고 말했다. 불변객체 이용시 내부의 객체에 대해서는 GC 스캔에서 제외된다. 즉, CG의 스캔빈도와 범위가 줄게되어 GC의 성능에 도움이 된다.

 

불변 객체 생성 규칙

불변객체를 생성시에는 아래와 같은 규칙이 존재한다.

  • setter 메서드를 제공하지 않는다.(read-only method만 제공. 다만, 타입에 따라 방어적 복사를 통해 참조를 끊어 제공하기도 한다.
  • 모든 필드를 private, final로 선언한다.
  • 하위 클래스에서 overriding을 방지하기 위해 클래스를 final로 선언한다.
  • 객체를 생성하기 위한 생성자 또는 정적 팩토리 메서드를 추가한다.
  • 인스턴스 필드에 가변객체가 포함된다면 방어적 복사를 이용하여 전달한다.

 

직접 만들어보자

원시 타입만 있는 경우

public class BaseObject {

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

위의 코드의 경우 외부에서 Setter를 통해 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
}

즉, 불변 객체의 참조 변수 또한 불변이어야 한다. 

 

필드에 참조 타입이 있는 경우 객체, Array, List 등을 참조할 수 있다. 이런 상황에 대해 불변객체를 만들어보자.

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

참조 변수인 Age도 final 키워드를 총해 불변 객체로 만들어 해결할 수 있다.

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[]과 같은 형태라면 해당 객체는 불변객체여야 한다.

 

3) List인 경우

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

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);
    }
}
https://yeoonjae.tistory.com/entry
https://dev-cool.tistory.com/23
https://velog.io/@conatuseus/Java-Immutable-Object불변객체
https://velog.io/@roro/Java-Object-클래스-clone

 

profile

숑숑이의 개발일기

@숑숑-

풀스택 개발자 준비중입니다