일급컬렉션관련 가,불변 List
테스트용 가변, input대신 불변, 일급의 List받는 생성자
📜 제목으로 보기
- 01 Test코드 등 add/remove 허용 가변List는 new ArrayList<>()로 생성
- 02 input 가정인자(검증후 완료된 인자) 등 add/remove 불허 불변List(List.of())
- 03 일급컬렉션의 List<객체> 파라미터를 받는 생성자 or 컬렉션을 return하는 getter의 return문에서의 방어적 복사 -> 불변List.copyOf( list ) or add 등 가변 new ArraysList<>( list )
- 참고) 포장(VO or 일급컬렉션) 변수명: value, 상수 MIN/MAX
- 일급이 중복 허용 안한다면 단일List가 대신 단일Set으로 정의해도 된다.
new ArrayList<>()
로 생성
01 Test코드 등 add/remove 허용 가변List는 -
테스트코드에서
.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) )); }
- add 등 조작이 당장 필요한 List는
List.of()
)
02 input 가정인자(검증후 완료된 인자) 등 add/remove 불허 불변List(- 참고 블로그
- 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.asList
는 add/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 )에 따라서 다른방식 메서드를 반환한다.
- add(), remove(), set(), replace()처럼
-
반면에
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메서드만 오버라이딩함.
-
List<객체>
파라미터를 받는 생성자
or 컬렉션을 return하는 getter의 return문에서의 방어적 복사
-> 불변List.copyOf( list )
or add 등 가변 new ArraysList<>( list )
03 일급컬렉션의
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가지
-
불변 일급
:List.copyOf( otherList )
-
가변 일급
: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으로 정의해도 된다.
-
List<단일>
의 value를Set<단일>
로 바꾼다. -
테스트에서 생성도
Set.of( 단일1, 단일2 )
형식으로 사용한다. -
Set에 들어가므로
해쉬코드로 비교하는 자료구조
용단일객체
는 equals/hashCode 오버라이딩이 되어있어야한다.- VO뿐만 아니라
단일값 포장객체
는 그냥 다 오버라이딩 해주자.
- VO뿐만 아니라