마코센세의 나이스샷 명강의를 듣고 감동을 받아 정리해보려한다!
땡큐 마코센세!
new vs copyOf
Java의 두 가지 복사 방법을 비교해보자.
- new를 통해 새로운 객체 생성 후 복사하기
- copyOf 키워드 사용하기
new를 사용하여 복사하면 기존의 컬렉션과의 참조를 끊어버린다! (새로운 주소값)
하지만 가변이다. (나의 도플갱어가 생겼을 뿐이다. 얘가 지금부터 자기관리를 어떻게 하든 내 알 바 아니다!)
copyOf를 사용하여 복사해도 기존 컬렉션과의 참조를 끊어버린다.
하지만 new와 다르게, 불변이다. (ImmutableCollection)
(단, 원본이 불변이면 참조를 끊지 않는다.
복사할 때 참조를 끊어주는 이유에 대해 생각해보면 된다.
원본이 변할 때 복사본이 변할것을 대비해 참조를 끊어주는건데, 원본이 불변이라면? 복사본이 변할 일이 없다!)
new 복사본은 가변이고, copyOf 복사본은 불변이다.
new와 copyOf 둘 다 기존 컬렉션과 참조가 끊어졌기 때문에, 기존 컬렉션이 변화하더라도 복사본은 바뀌지 않는다.
new 가변 테스트
List<String> crews = List.of("ash", "ako", "maco");
List<String> newCrews = new ArrayList<>(crews);
newCrews.add("beaver"); // 정상 동작
new를 사용해 복사한 리스트에 새로운 문자열을 추가할 경우 정상적으로 동작한다. (가변)
copyOf 불변 테스트
List<String> crews = List.of("ash", "ako", "maco");
List<String> copiedCrews = List.copyOf(crews);
copiedCrews.add("beaver"); // 에러 발생!!
copyOf를 사용해 복사한 리스트에 새로운 문자열을 추가할 경우 에러가 발생한다. (불변)
copyOf가 불변인 이유는 내부 구현을 보면 쉽게 알 수 있다.
static <E> List<E> copyOf(Collection<? extends E> coll) {
return ImmutableCollections.listCopy(coll);
}
copyOf 메서드 내부에선 ImmutableCollections를 return해주기 때문에, 불변이다!
추가) List.of와 불변
static <E> List<E> of() {
return ImmutableCollections.emptyList();
}
List.of 구현을 보면, ImmutableCollection을 리턴해준다. (불변)
그래서 List.of()로 선언해준 리스트에는 새로운 요소 add가 안되는것이다!
가변으로 만들고 싶으면 new 키워드를 사용해주면 된다. (new ArrayList<>(list))
copyOf vs unmodifiable
리스트를 "read-only"하도록 리턴해주는 방법으로 주로 Collections.unmodifiableList을 사용한다.
그러면 copyOf 로 복사한 객체를 리턴해주는 방법과, Collections.unmodifiableList를 사용해 리턴해주는 방법의 차이는 뭘까?
copyOf의 경우, 기존 컬렉션과 참조를 끊어버리지만,
unmodifiable의 경우 기존 컬렉션과 참조가 아직 이어져있다. (그저 set/add등의 연산만 막아준 만들어준 읽기 전용 객체이다)
즉, 원래 객체의 정보가 바뀌면
copyOf에선 변경 사항이 반영되지 않지만,
unmodifiable에선 변경 사항이 그대로 반영된다!!
원본이 변하면 copyOf에선 변하지 않고, unmodifiable에선 변한다.
객체 배열 및 불변 속 가변
copyOf(불변/원본과 참조가 끊김)으로 복사된 객체 리스트가 있다고 가정하자.
원본 리스트 속 객체 값이 바뀌면, 복사본의 객체 값은 바뀔까?
그리고 복사본 속 객체에 접근해 객체의 속성을 바꿀 수 있을까?
원본 리스트 내부 객체 값 수정 테스트
copyOf 복사본은 원본 컬렉션과 참조가 끊어졌다 했는데..
Q. 원본 리스트 내부 객체 값이 바뀌면, 복사본 내부 객체 값은 바뀔까?
A. 바뀐다!
Crew crew1 = new Crew("ash");
Crew crew2 = new Crew("ako");
Crew crew3 = new Crew("maco");
List<Crew> crews = List.of(crew1, crew2, crew3);
List<Crew> copiedCrews = List.copyOf(crews);
crews.get(0).setName("beaver");
System.out.println(copiedCrews.get(0).getName()); // beaver
원본 리스트 속 첫번째 크루의 이름을 ash에서 beaver로 바꿨다.
복사본 속 첫번재 크루의 이름을 출력해보면, ash가 아닌 beaver가 출력된다.
이는 copyOf의 방어적 복사 특징 때문이다.
방어적 복사는 틀만 갈아 끼울 뿐, 내부의 내용물은 동일하다.
(리스트 자체의 참조만 끊겼을 뿐, 리스트 내부 객체의 참조는 여전히 유지된다!)
이러한 문제는 내부 객체의 참조도 끊어버리는 깊은 복사로 해결할 수 있다.
여기서 방어적 복사와 깊은 복사에 대해 자세히 알고싶으면, 아래 블로그의 글을 추천한다.
https://ttl-blog.tistory.com/1206
복사본 리스트 내부 객체 속성 수정 테스트
copyOf 복사본은 불변이라 했는데..
Q. 복사본 속 객체에 접근해 객체의 속성을 바꿀 수 있을까?
A. 바꿀 수 있다!
Crew crew1 = new Crew("ash");
Crew crew2 = new Crew("ako");
Crew crew3 = new Crew("maco");
List<Crew> crews = List.of(crew1, crew2, crew3);
List<Crew> copiedCrews = List.copyOf(crews);
copiedCrews.get(0).setName("beaver"); // 정상 동작 (에러 X)
System.out.println(copiedCrews.get(0).getName()); // beaver
복사된 리스트 속 첫번째 크루의 이름을 ash에서 beaver로 바꿨는데, 에러 없이 정상적으로 동작했다.
이는 위의 원본리스트 수정에서와 비슷한 이유이다.
껍데기 컬렉션만 불변일 뿐, 내부의 요소들은 가변일 수 있다!
불변 객체가 담겨있다면 불변일 것이고, 가변 객체가 담겨있다면 가변일 것이다.
세 줄 요약
new는 가변이고, 원본 컬렉션과 참조를 끊는다.
copyOf는 불변이고, 원본 컬렉션과 참조를 끊는다.
unmodifiable은 읽기 전용이고, 원본 컬렉션과 참조가 유지된다.
(단, 컬렉션 내부 요소까지 불변을 보장하거나 참조를 끊어주진 않는다.)
'프로그래밍' 카테고리의 다른 글
[Java] LinkedHashMap (0) | 2023.03.13 |
---|---|
[Java] 함수형 인터페이스와 람다 (5) | 2023.03.12 |
[Java] 동일성(==)과 동등성(equals) (8) | 2023.02.23 |
JUnit5란? (0) | 2023.02.20 |
AssertJ를 사용하는 이유 (0) | 2023.02.17 |