📜 제목으로 보기

01 Test코드 등 add/remove 허용 가변List는 new ArrayList<>()로 생성

  • 테스트코드에서 .add()해야하는데 Arrays.asList(전체단위만 가변ex>set, replace)List.of(아예 조작 불가)로 초기화 하면 UOE 예외가 발생한다.

    • add 등 조작이 당장 필요한 List는 new ArraysList<>( );로 초기화할 것
      @Test
      void 일급컬렉션_생성자가_파라미터로_list를_받는경우_방어적복사_테스트() {
        
          //1. 외부에서 일급컬렉션의 포장변수(List<객체>)로 넘겨줄 [add가능한 가변List]를 생성
          // - List.of()는 add/remove 등을 제공하지 않으니 Arrays.List()로 생성했다가 UOE 뜸. 역시 불변
          //List<LottoNumber> lottoNumbers = List.of(new LottoNumber(1),
          //List<LottoNumber> lottoNumbers = Arrays.asList(new LottoNumber(1),
          // -> 테스트에 add 등 조작하는 가변List는 new ArraysList()<>;로 초기화해서 사용할 것
          List<LottoNumber> lottoNumbers = new ArrayList<>();
          lottoNumbers.addAll(
              List.of(new LottoNumber(1),
                      new LottoNumber(2),
                      new LottoNumber(3),
                      new LottoNumber(4),
                      new LottoNumber(5),
                      new LottoNumber(6)
                     ));
      }
    

02 input 가정인자(검증후 완료된 인자) 등 add/remove 불허 불변List(List.of())

  • 참고 블로그
  • TDD할 때, input으로 넘어온다고 가정한 인자로서 List를 받을 때
    • Arrays.asList(1,2,3)이 아니라 List.of(1,2,3)을 쓰더라

결론

  • List.of() , List.copyof( list )는 add/remove등 **조작시 **
    • UnsupportedOperationException발생시키는 불변List
    • 모든 가변 조작(add/remove, set/replace) 불허
    • null도 불허
  • 반면 Arrays.asListadd/remove만 안되는 전체는 가변List를 제공
    • 통채로 add하는 set, replace 가능)
    • null허용 +
  • 둘다 add안되니.. 테스트용 가변 객체리스트 생성시에도.. add하면서 테스트하면 안될 듯.

returnType

  • List.of()10개까진 가변인자 없이 하나하나 받아서 -> ImmutableCollections.ListN()을 반환한다.

    • add(), remove(), set(), replace()처럼 삽입, 삭제, 변경작업을 하게 될시 UnsupportedOperationException예외가 발생불변 자료구조 = 이름 그대로 Immutable한 Collection을 반환해준다.
      static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6) {
          return new ImmutableCollections.ListN<>(e1, e2, e3, e4, e5,
                                                  e6);
      }
    
    • java9부터 제공한다.

    • 인자의 갯수( 0개 / 1~2개 / 3개이상N/ List )에 따라서 다른방식 메서드를 반환한다.

      image-20220304210552364

      image-20220304210629956 image-20220304210601988

  • 반면에 Arrays.asList()는 가변인자를 받으며 -> new ArraysList<>()를 반환한다.

      public static <T> List<T> asList(T... a) {
          return new ArrayList<>(a);
      }
    
    • 가변인자는 내부 배열로 관리됨
    • 추상메서드 AbstractList<E>를 extends하지만,
      • add+remove만 메서드 오버라이딩 하지 않는다.
        • AbstractList의 add()와 remove() 메서드를 확인해본 결과 서브클래스에서 해당 메서드를 오버라이딩 하지 않았다면, UnsupportedOperationException예외가 발생된다. Arrays.asList()의 리턴되는 ArrayList는 삽입, 삭제로 이뤄지는 구조적인 변경을 허용하지 않고 내부 원소들의 값을 변경하는건 허용하는걸 알 수 있다.
      • set() setter메서드만 오버라이딩함.

03 일급컬렉션의 List<객체> 파라미터를 받는 생성자 or 컬렉션을 return하는 getter의 return문에서의 방어적 복사 -> 불변List.copyOf( list ) or add 등 가변 new ArraysList<>( list )

만약, 생성자가 파라미터 List<객체>를 받으면, 외부 원본 list의 메모리주소가 들어오니 끊고 재생성해라.

  • list를 내부 포장할 때, 만약 생성자 파라미터로 내부 포장하는 변수와 동일한 형태의 List<객체>가 들어오면, 외부에서 온 List의 메모리주소로서, 끊고 재생성해줘야 오염이 안된다.
    • list는 callByReference 로서 파라미터에 지역변수로 선언되어도 외부의 메모리주소를 가지고 있다.
    • this.value = 파라미터로 온 list는 메모리 주소만 대입하여 포장되어있지만, 외부 변화에 오염된다.
      @Test
      void 일급컬렉션_생성자가_파라미터로_list를_받는경우_방어적복사_테스트() {
          //1. add될 가변 && 방어적복사로인한 메모리주소 끊음(재발급)안하면 일급컬렉션 내부 변수도 오염시키는
          // -> <외부 원본 List>
          List<LottoNumber> lottoNumbers = new ArrayList<>();
          lottoNumbers.addAll(
              List.of(new LottoNumber(1),
                      new LottoNumber(2),
                      new LottoNumber(3),
                      new LottoNumber(4),
                      new LottoNumber(5),
                      new LottoNumber(6)
                     ));
        
          //2. 일급은 외부list를 생성자로 넘겨받아 --방어적복사없이 --> this.value에 넣은 상태다.
          final Lotto lotto = new Lotto(lottoNumbers);
        
          //3. 일급컬렉션말고, 원본에 1개를 더한다.
          lottoNumbers.add(new LottoNumber(7));
        
          //4. getter로 내부 상태값의 길이를 가져와보자. 6개여야하는데... 7개가 되어있을 것이다.
          final int size = lotto.getSize();
        
          //5. 7하니까 통과 ... -> 외부list를 add했는데 -> 일급컬렉 포장변수가 가변으로 변해버렸다.
          assertThat(size).isEqualTo(7);
      }
    
  • 파라미터로 오는 외부List의 메모리주소를 끊는 방법 2가지
    1. 불변 일급 : List.copyOf( otherList )
    2. 가변 일급: new ArraysList<>( ohterList )로 반환받아서 this.value에 넣어주자.
      public Lotto(final List<LottoNumber> value) {
          if (value.size() != 6) {
              throw new IllegalArgumentException("로또 번호는 6개여야 합니다.");
          }
          if (value.stream().distinct().count() != 6) {
              throw new IllegalArgumentException("로또 번호는 중복해서 입력할 수 없습니다.");
          }
          //this.value = value;
          // 불변하는 일급컬렉션으로 -> 1번째 방어적 복사 적용
          this.value = List.copyOf(value);
      }
    
      // 외부 add에도 6개라고 에러났다.
      org.opentest4j.AssertionFailedError: 
      expected: 7
       but was: 6
      Expected :7
      Actual   :6
      <Click to see difference>
    

참고) 포장(VO or 일급컬렉션) 변수명: value, 상수 MIN/MAX

  • 값의 포장은 앵간하면 value를 인스턴스 변수(상태값)으로 쓴다.
    • 그래야 getter시 LottoNumber.getValue() 형태로 잘 빠진다.
    • 만약, LottoNumber.getLottoNumber()가 될 수 도..
  • 상수도 마찬가지로 도메인Class.이 앞에 붙는다고 생각하고 도메인내에서 편하게 짓자
    • LottoNumber.LOTTO_NUMBER_MIN이 될 수도..
    • LottoNumber.MIN으로 MIN, MAX 편하게 쓰자.
public class LottoNumber {

    private static final int MIN = 1;
    private static final int MAX = 45;

    private final int value;

    //LottoNumber.getValue()가 맞다.(o) ->  LottoNumber.getLottoNumber() (X)
    public LottoNumber(final int value) {
        validate(value);
        this.value = value;
    }

    //LottoNumber.MIN 이 맞다.(o) -> LottoNumber.LOTTO_NUMBER_MIN (X)
    //Integer.MAX_VALUE (o)
    private void validate(final int value) {
        if (value < MIN || value > MAX) {
            throw new IllegalArgumentException("로또 번호의 범위가 아닙니다.");
        }

일급이 중복 허용 안한다면 단일List가 대신 단일Set으로 정의해도 된다.

  1. List<단일>의 value를 Set<단일>로 바꾼다. image-20220305010218112

  2. 테스트에서 생성도 Set.of( 단일1, 단일2 )형식으로 사용한다.

    image-20220305013247242

  3. Set에 들어가므로 해쉬코드로 비교하는 자료구조단일객체는 equals/hashCode 오버라이딩이 되어있어야한다.

    • VO뿐만 아니라 단일값 포장객체는 그냥 다 오버라이딩 해주자.