728x90
서론
- 현재 프로그래머스 백엔드 데브코스 5기 멘토를 진행하던중, 아래와 같이 멘티의 질문이 있었다.
- Java11까지만 해봤기 때문에 toList()에 대해서 잘 알지 못한다고 생각해 아래와 같이 정리를 해봤다.
stream의 최종연산을 List로 반환하기.
- stream의 최종연산을 List로 반환하는 방법은 Java11에선 아래와 두개가 있었다.
List<Integer> collectorToList = numbers.stream()
.filter(it -> it % 2 == 0)
.collect(Collectors.toList());
List<Integer> collectorToUnmodifiabledList = numbers.stream()
.filter(it -> it % 2 == 0)
.collect(Collectors.toUnmodifiableList());
- 즉, collect 함수에 함수형 인터페이스를 넣어서 List를 반환하는 법이였다.
- Collectors.toList()와 Collectors.toUnmodifiableList() 두개의 차이는 가변 List냐, 불변 List이냐 이다.
- 불변 List라면 무엇을 못하느냐?
- 랜덤 셔플, 정렬, 값 추가, 삭제 등을 할수가 없다. 즉, List를 아예 변경할수 있다는 것이다.
- 그렇다면 2개의 내부를 살펴보도록 하겠습니다.
/**
* Returns a {@code Collector} that accumulates the input elements into a
* new {@code List}. There are no guarantees on the type, mutability,
* serializability, or thread-safety of the {@code List} returned; if more
* control over the returned {@code List} is required, use {@link #toCollection(Supplier)}.
*
* @param <T> the type of the input elements
* @return a {@code Collector} which collects all the input elements into a
* {@code List}, in encounter order
*/
public static <T>
Collector<T, ?, List<T>> toList() {
return new CollectorImpl<>(ArrayList::new, List::add,
(left, right) -> { left.addAll(right); return left; },
CH_ID);
}
/**
* Returns a {@code Collector} that accumulates the input elements into an
* <a href="../List.html#unmodifiable">unmodifiable List</a> in encounter
* order. The returned Collector disallows null values and will throw
* {@code NullPointerException} if it is presented with a null value.
*
* @param <T> the type of the input elements
* @return a {@code Collector} that accumulates the input elements into an
* <a href="../List.html#unmodifiable">unmodifiable List</a> in encounter order
* @since 10
*/
public static <T>
Collector<T, ?, List<T>> toUnmodifiableList() {
return new CollectorImpl<>(ArrayList::new, List::add,
(left, right) -> { left.addAll(right); return left; },
list -> {
if (list.getClass() == ArrayList.class) { // ensure it's trusted
return SharedSecrets.getJavaUtilCollectionAccess()
.listFromTrustedArray(list.toArray());
} else {
throw new IllegalArgumentException();
}
},
CH_NOID);
}
CollectorImpl(Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Set<Characteristics> characteristics) {
this(supplier, accumulator, combiner, castingIdentity(), characteristics);
}
- 일단 toList()부터 살펴보면, Collector 인터페이스를 구현하고 있는데 각각 살펴보면
- supplier : 새로운 객체를 어떻게 생성하는지 정의하는데, 빈 ArrayList를 생성하고 있습니다.
- accumulator: Collector가 개별 요소를 어떻게 추가하는지 정의 합니다.
- combiner: 두 컨테이너를 어떻게 병합하는지 정의하는데, left List에 right 리스트를 addAll로 병합하고 잇습니다.
- characteristics: Collector의 특정을 정의하여 런타임 행동을 정의합니다.
- 중간 연산에서 나온 값들을 addAll로 한번에 추가하는 형식이라고 생각하면 좋습니다.
- 그렇다면 toUnmodifiabledList()는
Function<A,R> finisher
가 추가된걸 확인할수 있습니다.- 해당 로직을 간단히 설명해보면 아래와 같습니다.
- SharedSecrets 클래스는 서로 다른 패키지로 분리된 비공개 패키지 간의 공유 가능 상태를 제공하는 클래스로, JVM 내부 세부 정보를 추상화 하는데 사용합니다.
- 여기서 getJavaUtilCollectionAceesss()는 java.util 패키지 내의 컬렉션 클래스에 대한 추가 접근을 허용하며 마지막의 listFromTrustedArray()메서드는 내부 배열을 복사하면서 새로운 불변 리스트를 만드는것이 아니라 이 배열에 대한 참조로 부터 직접 불변 리스트를 생성합니다. 이렇게 하는 이유는 메모리와 성능 향상을 위해서 입니다.
- cf) 여기서 toUnmodifiableList()는 자바 10부터 추가되었습니다. 따라서 8~9인 경우에는 아래와 같이 작성하면 동일한 리스트를 반환받을수 있습니다.
List<Integer> collectorToList = Collections.unmodifiableList(numbers.stream().filter(it -> it % 2 == 0)
.collect(Collectors.toList()));`
- 그럼 Java16에 추가된 toList()를 살펴보겠습니다.
List<Integer> collectorToList = numbers.stream().filter(it -> it % 2 == 0)
.toList()
/**
* Accumulates the elements of this stream into a {@code List}. The elements in
* the list will be in this stream's encounter order, if one exists. The returned List
* is unmodifiable; calls to any mutator method will always cause
* {@code UnsupportedOperationException} to be thrown. There are no
* guarantees on the implementation type or serializability of the returned List.
*
* <p>The returned instance may be <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a>.
* Callers should make no assumptions about the identity of the returned instances.
* Identity-sensitive operations on these instances (reference equality ({@code ==}),
* identity hash code, and synchronization) are unreliable and should be avoided.
*
* <p>This is a <a href="package-summary.html#StreamOps">terminal operation</a>.
*
* @apiNote If more control over the returned object is required, use
* {@link Collectors#toCollection(Supplier)}.
*
* @implSpec The implementation in this interface returns a List produced as if by the following:
* <pre>{@code
* Collections.unmodifiableList(new ArrayList<>(Arrays.asList(this.toArray())))
* }</pre>
*
* @implNote Most instances of Stream will override this method and provide an implementation
* that is highly optimized compared to the implementation in this interface.
*
* @return a List containing the stream elements
*
* @since 16
*/
@SuppressWarnings("unchecked")
default List<T> toList() {
return (List<T>) Collections.unmodifiableList(new ArrayList<>(Arrays.asList(this.toArray())));
}
- 아주 심플해졌는데, 해당 List를
Collections.unmodifiableList()
로 감싸서 불변 리스트를 반환하게 됩니다.
각 반환값들 테스트 해보기
package org.example;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
// Collections
List<Integer> collectorToList = numbers.stream()
.filter(it -> it % 2 == 0)
.collect(Collectors.toList());
List<Integer> collectorToUnmodifiabledList = numbers.stream()
.filter(it -> it % 2 == 0)
.collect(Collectors.toUnmodifiableList());
List<Integer> toList = numbers.stream()
.toList();
// add
collectorToList.add(11);
collectorToUnmodifiabledList.add(11); // 예외발생
toList.add(11); // 예외 발생
// remove
collectorToList.remove(0);
collectorToUnmodifiabledList.remove(0); // 예외 발생
toList.remove(0); // 예외 발생
// sort
Collections.sort(collectorToList);
Collections.sort(collectorToUnmodifiabledList); // 예외 발생
Collections.sort(toList); // 예외 발생
// shuffle
Collections.shuffle(collectorToList);
Collections.shuffle(collectorToUnmodifiabledList); // 예외 발생
Collections.shuffle(toList); // 예외 발생
}
}
- 예외는 아래와 같이 지원하지 않은 메서드라는 의미로 예외가 발생합니다.
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:142)
at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.add(ImmutableCollections.java:147)
결론
- Java8에 stream이 추가되면서 최종연산을 리스트로 반환하는
.collect(Collectors.toList());
메서드가 추가되었지만, mutable(가변) list가 반환되었다. - 그러나 Java10에서 thread safe하고 안정적으로 관리하기 위해
.collect(Collectors.toUnmodifiableList());
메서드가 추가되었고 해당 메서드는 immutable(불변) list가 반환된다. - 해당 메서드를 좀더 쉽게 사용할수 있게 하는
toList()
를 java16에 추가되었고 동일하게 immutable(불변) list가 반환된다.
728x90
728x90
'Backend > Java' 카테고리의 다른 글
[번역] Unit Testing of System.out.println() with JUnit (0) | 2023.12.30 |
---|---|
[번역] Deprecated Features in Java 18 thru 21(Java 18 ~ 21 버전에서 더 이상 사용되지 않는 기능) - Sip of Java (0) | 2023.12.29 |
Java String의 isEmpty와 isBlank (0) | 2023.10.17 |
[번역] Java8에서 함수형 스타일로 리팩토링 : 레거시에서 람다로 (0) | 2022.07.23 |
9 tips to Increase your Java performance(자바 성능을 향상시키기 위한 9가지 팁) (0) | 2022.06.06 |