Java 8 스트림이 비어 있는지 확인하는 방법은 무엇입니까?


99

Stream비 터미널 작업으로 a가 비어 있는지 확인 하고 그렇지 않은 경우 예외를 throw하려면 어떻게해야합니까?

기본적으로 아래 코드와 동일한 것을 찾고 있지만 중간에 스트림을 구체화하지 않습니다. 특히, 터미널 작업에서 스트림이 실제로 사용되기 전에는 검사가 발생하지 않아야합니다.

public Stream<Thing> getFilteredThings() {
    Stream<Thing> stream = getThings().stream()
                .filter(Thing::isFoo)
                .filter(Thing::isBar);
    return nonEmptyStream(stream, () -> {
        throw new RuntimeException("No foo bar things available")   
    });
}

private static <T> Stream<T> nonEmptyStream(Stream<T> stream, Supplier<T> defaultValue) {
    List<T> list = stream.collect(Collectors.toList());
    if (list.isEmpty()) list.add(defaultValue.get());
    return list.stream();
}

23
당신은 당신의 케이크를 가질 수없고 그것을 너무 먹을 수 없습니다.이 맥락에서 말 그대로 그렇습니다. 비어 있는지 확인하려면 스트림 을 소비 해야합니다. 이것이 Stream의 의미론 (게으름)의 요점입니다.
Marko Topolnik 2014 년

그것은 결국 소비 될 것입니다.이 시점에서 확인이 이루어져야합니다
Cephalopod

12
스트림이 비어 있지 않은지 확인하려면 최소한 하나의 요소를 소비해야합니다. 이 시점에서 스트림은 "처음"을 잃었고 처음부터 다시 사용할 수 없습니다.
Marko Topolnik 2014 년

답변:


24

제한된 병렬 용량으로 생활 할 수있는 경우 다음 솔루션이 작동합니다.

private static <T> Stream<T> nonEmptyStream(
    Stream<T> stream, Supplier<RuntimeException> e) {

    Spliterator<T> it=stream.spliterator();
    return StreamSupport.stream(new Spliterator<T>() {
        boolean seen;
        public boolean tryAdvance(Consumer<? super T> action) {
            boolean r=it.tryAdvance(action);
            if(!seen && !r) throw e.get();
            seen=true;
            return r;
        }
        public Spliterator<T> trySplit() { return null; }
        public long estimateSize() { return it.estimateSize(); }
        public int characteristics() { return it.characteristics(); }
    }, false);
}

다음은이를 사용하는 몇 가지 예제 코드입니다.

List<String> l=Arrays.asList("hello", "world");
nonEmptyStream(l.stream(), ()->new RuntimeException("No strings available"))
  .forEach(System.out::println);
nonEmptyStream(l.stream().filter(s->s.startsWith("x")),
               ()->new RuntimeException("No strings available"))
  .forEach(System.out::println);

(효율적인) 병렬 실행의 문제는 분할을 지원 Spliterator하려면 조각 중 하나가 스레드로부터 안전한 방식으로 값을 보았는지 여부를 알 수있는 스레드로부터 안전한 방법 이 필요하다는 것입니다. 그런 다음 실행중인 마지막 프래그먼트 tryAdvance는 적절한 예외를 발생시키기위한 마지막 프래그먼트 (또한 진행할 수 없음)임을 인식해야합니다. 그래서 여기에 분할 지원을 추가하지 않았습니다.


33

다른 답변과 코멘트는 스트림의 내용을 조사하기 위해 정확합니다. 하나는 터미널 작업을 추가해야하므로 스트림을 "소비"해야합니다. 그러나이를 수행하고 스트림의 전체 내용을 버퍼링하지 않고도 결과를 스트림으로 되돌릴 수 있습니다. 다음은 몇 가지 예입니다.

static <T> Stream<T> throwIfEmpty(Stream<T> stream) {
    Iterator<T> iterator = stream.iterator();
    if (iterator.hasNext()) {
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
    } else {
        throw new NoSuchElementException("empty stream");
    }
}

static <T> Stream<T> defaultIfEmpty(Stream<T> stream, Supplier<T> supplier) {
    Iterator<T> iterator = stream.iterator();
    if (iterator.hasNext()) {
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
    } else {
        return Stream.of(supplier.get());
    }
}

기본적으로 스트림 Iterator을 호출하려면 스트림을로 바꾸고, hasNext()참이면 Iterator다시 Stream. 이는 스트림에 대한 모든 후속 작업이 Iterator hasNext()next()메서드를 통과한다는 점에서 비효율적이며 , 이는 스트림이 순차적으로 효과적으로 처리된다는 것을 의미합니다 (나중에 병렬로 전환 되더라도). 그러나 이렇게하면 모든 요소를 ​​버퍼링하지 않고도 스트림을 테스트 할 수 있습니다.

사용하여이 작업을 수행 할 수있는 방법 아마이 Spliterator대신의가 Iterator. 이렇게하면 반환 된 스트림이 병렬 실행을 포함하여 입력 스트림과 동일한 특성을 가질 수 있습니다.


1
분할을 지원하기 어렵 기 때문에 효율적인 병렬 처리를 지원하는 유지 관리 가능한 솔루션이 없다고 생각하지만 단일 스레드 성능 이 estimatedSize있고 characteristics심지어 향상 될 수도 있습니다. 단지 내가 쓴 일이 Spliterator당신이 게시하는 동안 솔루션을 Iterator... 솔루션
홀거

3
스트림에 Spliterator를 요청하고, 람다가 전달 된 모든 것을 캡처하는 tryAdvance (lambda)를 호출 한 다음, 첫 번째 요소를 다시 첫 번째 청크 ( EstimatesSize의 결과를 수정합니다).
Brian Goetz 2014 년

1
@BrianGoetz 네, 그게 제 생각이었습니다. 저는 그 모든 세부 사항을 처리하는 다리 작업을 아직 거치지 않았습니다.
Stuart Marks

3
@Brian Goetz : 그것이 "너무 복잡하다"는 의미입니다. tryAdvance하기 전에 호출 Stream하면의 게으른 특성이 Stream"부분적으로 게으른"스트림으로 바뀝니다 . 또한 tryAdvance내가 이해하는 한 실제 병렬 작업을 수행하려면 먼저 분할하고 분할 된 부분을 동시에 수행해야하므로 첫 번째 요소를 검색하는 것이 더 이상 병렬 작업이 아님을 의미합니다. 유일한 터미널 작업이 findAny전체 parallel()요청을 파괴하는 것과 같거나 유사한 경우.
Holger

2
따라서 완전한 병렬 지원을 위해서는 tryAdvance스트림이 수행되기 전에 호출해서는 안되며 모든 분할 부분을 프록시로 래핑하고 모든 동시 작업의 "hasAny"정보를 직접 수집하고 마지막 동시 작업이 원하는 예외를 throw하는지 확인해야합니다. 스트림이 비어 있습니다. 많은 물건…
Holger

24

이것은 많은 경우에 충분할 수 있습니다.

stream.findAny().isPresent()

15

필터를 적용하려면 스트림에서 터미널 작업을 수행해야합니다. 따라서 소비 할 때까지 비어 있을지 알 수 없습니다.

할 수있는 최선의 방법은 findAny()터미널 작업으로 스트림을 종료 하는 것입니다.이 작업은 요소를 찾으면 중지되지만 요소가 없으면 모든 입력 목록을 반복하여 찾아야합니다.

이것은 입력 목록에 많은 요소가 있고 처음 몇 개 중 하나가 필터를 통과하는 경우에만 도움이됩니다. 스트림이 비어 있지 않다는 것을 알기 전에 목록의 작은 하위 집합 만 소비되어야하기 때문입니다.

물론 출력 목록을 생성하려면 새 스트림을 만들어야합니다.


7
거기에 anyMatch(alwaysTrue())내가 그와 가장 가까운 것 같아요, hasAny.
Marko Topolnik 2014 년

1
@MarkoTopolnik 방금 참조를 확인했습니다. 내가 염두에 둔 것은 findAny () 였지만 anyMatch ()도 작동합니다.
Eran 2014 년

3
anyMatch(alwaysTrue())완벽의 의도 된 의미를 일치 hasAny당신에게주는 boolean대신을 Optional<T>:) --- 그러나 여기 우린 분할 머리카락
마르코 Topolnik

1
참고 alwaysTrue는 Guava 술어입니다.
Jean-François Savard

11
anyMatch(e -> true)그때.
FBB

6

부울을 매핑하기에 충분해야한다고 생각합니다.

코드에서 이것은 다음과 같습니다.

boolean isEmpty = anyCollection.stream()
    .filter(p -> someFilter(p)) // Add my filter
    .map(p -> Boolean.TRUE) // For each element after filter, map to a TRUE
    .findAny() // Get any TRUE
    .orElse(Boolean.FALSE); // If there is no match return false

1
이것이 필요한 전부라면 kenglxn의 대답이 더 좋습니다.
Dominykas Mostauskis

쓸모
없고

@Krzysiek 컬렉션을 필터링해야하는 경우 쓸모가 없습니다. 그러나 나는 kenglxn의 대답이 더 낫다는 Dominykas의 의견에 동의합니다
Hertzu

중복되기 때문입니다Stream.anyMatch()
Krzysiek

4

Stuart의 아이디어에 따라 다음과 같이 할 수 있습니다 Spliterator.

static <T> Stream<T> defaultIfEmpty(Stream<T> stream, Stream<T> defaultStream) {
    final Spliterator<T> spliterator = stream.spliterator();
    final AtomicReference<T> reference = new AtomicReference<>();
    if (spliterator.tryAdvance(reference::set)) {
        return Stream.concat(Stream.of(reference.get()), StreamSupport.stream(spliterator, stream.isParallel()));
    } else {
        return defaultStream;
    }
}

stream.spliterator()작업이 스트림을 종료 한 다음 필요에 따라 다시 빌드하므로 병렬 스트림에서 작동한다고 생각 합니다.

내 사용 사례에서는 기본값이 Stream아닌 기본값 이 필요했습니다 . 이것이 필요한 것이 아니라면 변경하기가 매우 쉽습니다.


이것이 병렬 스트림의 성능에 큰 영향을 미치는지 알 수 없습니다. 이 요구 사항 인 경우 아마도 테스트해야
phoenix7360가

@Holger도 Spliterator두 가지가 어떻게 비교되는지 궁금합니다.
phoenix7360

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.