우아한 테크코스/테크코스

[Java] UnsupportedOperationException - 배열 asList 관련 에러

jamie. 2020. 2. 17. 21:22
반응형

asList한 Arrays에 add를 하면 해당 에러 발생

 UnsupportedOperationException, add를 하고 싶다면 addAll() 또는 생성자를 통해 하도록(아래 코드 참고)

깊은 복사와 얕은 복사깊은 복사 : deep copy : call by value

 복사한 원본 또는 복사본 모두 수정이 서로에게 영향을 미치지 않음 - 값을 복사하기 때문

Cloneable을 상속받은 객체를 clone() 메서드로 복사함

얕은 복사 : shallow copy : call by reference

 복사한 원본 또는 복사본 모두 수정이 서로에게 영향을 미침 - 주소를 복사하여 서로 참조하기 때문

= 기호로 복사함

List의 덜 깊은 복사?(공식 용어 아님)

 List를 대체할 때는 (set API이용) 대체를 해주는데(바꾸는 값)

 List를 대체하지 않을 때는(직접 수정, 메서드 등을 통해) 대체가 되지 않고 기존의 주소에서 해당 주소를 참조하는 듯(?)

 즉, clone을 해야 완전한 복사인 듯 함!

 (나중에 clone 하고 코드 추가해두기)

테스트 코드(학습 테스트)

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class TestException {

    @DisplayName("Arrays.asList의 경우, add시 UnsupportedOperationException 발생, 얕은 복사")
    @Test
    void unsupportedOperationException() {
        String string = "가,나,다";
        List<String> stringList = Arrays.asList(string.split(","));
        assertThatThrownBy(() -> stringList.add("라"))
            .isInstanceOf(UnsupportedOperationException.class);
    }

    @DisplayName("addAll(Arrays.asList())의 경우, (덜) 깊은 복사")
    @Test
    void success() {
        String string = "가,나,다";
        List<String> stringList = new ArrayList<>();
        stringList.addAll(Arrays.asList(string.split(",")));
        stringList.add("라");
        assertThat(stringList.get(3)).isEqualTo("라");
    }

    @DisplayName("new ArrayList<String>(Arrays.asList())의 경우도 (덜) 깊은 복사")
    @Test
    void successCopy() {
        String string = "가,나,다";
        List<String> stringList = new ArrayList<>(Arrays.asList(string.split(",")));
        stringList.add("라");
        assertThat(stringList.get(3)).isEqualTo("라");
    }

    @DisplayName("얕은/깊은 복사 테스트")
    @Test
    void copy() {
        List<String> stringListBefore = new ArrayList<>();
        stringListBefore.add(new String("가"));
        stringListBefore.add(new String("나"));
        stringListBefore.add(new String("다"));
        List<String> stringDeeps1 = new ArrayList<>();
        List<String> stringDeeps2 = new ArrayList<>(stringListBefore);
        List<String> stringShallow = stringListBefore;
        stringDeeps1.addAll(stringListBefore);

        stringListBefore.add("라");

        // 변경 전 - Reference copy이기 때문에 true
        assertThat(stringDeeps1.get(0) == stringListBefore.get(0)).isTrue();
        assertThat(stringDeeps2.get(0) == stringListBefore.get(0)).isTrue();
        assertThat(stringShallow.get(0) == stringListBefore.get(0)).isTrue();

        stringListBefore.set(0, new String("가"));
        // 변경 후
        assertThat(stringDeeps1.get(0) == stringListBefore.get(0)).isFalse();
        assertThat(stringDeeps2.get(0) == stringListBefore.get(0)).isFalse();
        assertThat(stringShallow.get(0) == stringListBefore.get(0)).isTrue();

        assertThatThrownBy(() -> stringDeeps1.get(3))
            .isInstanceOf(IndexOutOfBoundsException.class);
        assertThatThrownBy(() -> stringDeeps2.get(3))
            .isInstanceOf(IndexOutOfBoundsException.class);
        assertThat(stringShallow.get(3)).isEqualTo("라");

        stringListBefore.set(0, new String("가나다"));
        assertThat(stringDeeps1.get(0)).isEqualTo("가");
        assertThat(stringDeeps2.get(0)).isEqualTo("가");
        assertThat(stringShallow.get(0)).isEqualTo("가나다");
    }

    @DisplayName("얕은/깊은 복사 테스트 - 중요!")
    @Test
    void copy2() {
        List<int[]> integerBefore = new ArrayList<>();
        integerBefore.add(new int[]{1});
        integerBefore.add(new int[]{2});
        integerBefore.add(new int[]{3});
        List<int[]> integerDeeps1 = new ArrayList<>();
        List<int[]> integerDeeps2 = new ArrayList<>(integerBefore);
        List<int[]> integerShallow = integerBefore;
        integerDeeps1.addAll(integerBefore);

        integerBefore.add(new int[]{4});
        assertThat(integerDeeps1.get(0)[0] == integerBefore.get(0)[0]).isTrue();
        assertThat(integerDeeps2.get(0)[0] == integerBefore.get(0)[0]).isTrue();
        assertThat(integerShallow.get(0)[0] == integerBefore.get(0)[0]).isTrue();

        // 덜 깊은 복사 ? ? ? ! 진짜 깊은 복사는 clone으로 구현해야!
        integerBefore.get(0)[0] = 2;
        assertThat(integerDeeps1.get(0)[0] == integerBefore.get(0)[0]).isTrue();
        assertThat(integerDeeps2.get(0)[0] == integerBefore.get(0)[0]).isTrue();
        assertThat(integerShallow.get(0)[0] == integerBefore.get(0)[0]).isTrue();
    }
}
반응형