컬렉션 복사 방법

new, copyOf(), Collections.unmodifiable() 이렇게 크게 3가지가 있습니다.

방어적 복사란

방어적 복사란, 객체를 복사하여 새로운 객체를 만드는 것으로, 기존 객체의 변경이 새로운 객체에 영향을 주지 않도록 하는 방법입니다. 컬렉션의 경우, 원본 컬렉션을 그대로 사용하면 다른 스레드나 객체에서 컬렉션을 공유할 때 동기화 문제가 발생할 수 있습니다. 따라서, 방어적 복사를 사용하여 컬렉션의 요소를 변경하지 않고 안전하게 공유할 수 있습니다.

new ArrayList()

new ArrayList()는 원본 컬렉션의 모든 요소들을 가지고 있는 새로운 컬렉션을 만듭니다. 다른 두 메소드와 가장 큰 차이점은 복사를 한 컬렉션에 add(), set() 등과 같은 메소드를 통해 컬렉션의 요소들을 변경 가능할 수 있다는 점 입니다. 하지만 자유롭게 변경할 수 있는 가변 컬렉션이기에 다른 스레드나 객체에서 컬렉션을 공유할 때, 동기화 문제를 고려해야 합니다. (방어적 복사)

public class CopyTest {

    List<String> list = new ArrayList();

    @Test
    void arrayList() {
        List<String> arrayList = new ArrayList<>(list);

        assertDoesNotThrow(() -> arrayList.add("boxster"));
        assertThat(arrayList).contains("boxster");
    }
 }

copyOf()

copyOf() 는 읽기 전용인 새로운 컬렉션을 만듭니다. copyOf()로 반환한 컬렉션을 추가, 삭제 등 수정을 할 때 예외가 발생합니다. (방어적 복사)

    @Test
    void copyOf() {
        List<String> copyOf = List.copyOf(list);

        assertThatThrownBy(() -> copyOf.add("boxster"))
                .isInstanceOf(UnsupportedOperationException.class);
    }

Collections.unmodifiableList()

Collections.unmodifiableList() 도 읽기 전용인 새로운 컬렉션을 만듭니다. copyOf()와 마찬가지로 불변 객체를 반환합니다.

    @Test
    void unmodifiable() {
        List<String> unmodifiableList = Collections.unmodifiableList(list);

        assertThatThrownBy(() -> unmodifiableList.add("boxster"))
                .isInstanceOf(UnsupportedOperationException.class);
    }

Collections.unmodifiableList()과 copyOf()의 차이

두 메소드의 공통점은 불변 컬렉션를 반환한다는 점입니다. 하지만 두 메소드는 큰 차이점이 있습니다. copyOf()는 컬렉션을 복사한 뒤 원본 컬렉션과의 참조 값을 끊습니다. 하지만 unmodifiable()은 원본 컬렉션의 참조 값을 가지고 있습니다. unmodifiable()은 참조 값을 가지고 있기에 원본 컬렉션에 값이 추가, 삭제 등 변경이 되면 복사된 불변 컬렉션도 같이 변경이 됩니다. copyOf()는 참조 값이 끊기기 때문에 원본 컬렉션을 변경해도 복사된 컬렉션은 변경되지 않습니다.

public class CopyTest {

    List<String> list = new ArrayList();
    
    @Test
    void copyOf() {
        List<String> copyOf = List.copyOf(list);

        list.add("porsche"); // 원본 컬렉션에 값 추가
        
        assertThat(list).contains("porsche");
        assertThat(copyOf).doesNotContain("porsche"); // does not contains
    }
 }
public class CopyTest {

    List<String> list = new ArrayList();
    
    @Test
    void unmodifiable() {
        List<String> unmodifiableList = Collections.unmodifiableList(list);

        list.add("porsche"); // 원본 컬렉션에 값 추가
        
        assertThat(list).contains("porsche");
        assertThat(unmodifiableList).contains("porsche"); // contains
    }
 }

따라서 unmodifiable() 메소드를 사용할 때에는 다른 스레드나 객체에서 컬렉션을 공유할 때 동기화 문제를 고려해야 합니다. new, copyOf(), unmodifiable() 메소드 중 상황을 고려하여 적절한 메소드를 사용해야합니다.

그리고 이런 복사방법은 얕은 복사에 해당하기 때문에 내부에 있는 요소는 수정될 수 있습니다.

아래와 같이 copyOf(), new ArrayList()라던지 unmodifiable()도 로 원본 컬렉션과의 참조값을 끊더라도 요소로 가지고 있는 객체의 주소값은 가지고 있기 때문에 원본 컬렉션안의 객체가 변하게 된다면 복사한 컬렉션의 내부 요소들도 변하게 됩니다.


class CarTest {

    List<Car> cars;

    @BeforeEach
    void before() {
        cars = new ArrayList<>();
        cars.add(new Car("boxster"));
    }

    @Test
    void copyOf() {
        List<Car> copyOf = List.copyOf(cars); // boxster라는 이름의 Car 하나를 가진 컬렉션 복사

        Car boxsterInCopyOf = copyOf.get(0);
        assertThat(boxsterInCopyOf.getName()).isEqualTo("boxster");

        Car boxsterInOrigin = cars.get(0);  // 원본 컬렉션에서 객체를 꺼냄
        boxsterInOrigin.setName("jude");    // 꺼낸 객체의 값 변경

        assertThat(boxsterInCopyOf.getName()).isEqualTo("jude");  // 복사한 컬렉션의 내부 객체 변경됨
        assertThat(boxsterInOrigin.getName()).isEqualTo("jude");  // 원본 컬렉션의 내부 객체도 변경됨
    }
}

Java에서는 따로 깊은 복사를 하는 api를 제공해주지 않기 때문에 직접 하나하나 요소를 순회하며 복사해주는 방법을 사용해야합니다. 혹은 컬렉션 안의 객체를 불변으로 만드는 방법도 있습니다.

태그: ,

카테고리:

업데이트:

댓글남기기