Làm thế nào để kiểm tra xem Java 8 Stream có trống không?


95

Làm cách nào tôi có thể kiểm tra xem a Streamcó trống hay không và đưa ra một ngoại lệ nếu không phải là một hoạt động không phải terminal?

Về cơ bản, tôi đang tìm kiếm thứ gì đó tương đương với mã bên dưới, nhưng không thực hiện luồng ở giữa. Đặc biệt, việc kiểm tra không nên xảy ra trước khi luồng thực sự được sử dụng bởi hoạt động đầu cuối.

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
Bạn không thể cầm chiếc bánh của mình và ăn nó - và theo nghĩa đen, trong bối cảnh này. Bạn phải sử dụng luồng để tìm xem nó có trống không. Đó là điểm ngữ nghĩa của Stream (sự lười biếng).
Marko Topolnik

Nó sẽ được tiêu thụ cuối cùng, vào thời điểm này việc kiểm tra nên xảy ra
vật thân mềm

11
Để kiểm tra xem luồng không trống, bạn phải cố gắng sử dụng ít nhất một phần tử. Khi đó suối đã mất “trinh” và không thể tiêu thụ lại từ đầu.
Marko Topolnik

Câu trả lời:


24

Nếu bạn có thể sống với khả năng song song hạn chế, giải pháp sau sẽ hoạt động:

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);
}

Đây là một số mã ví dụ sử dụng nó:

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);

Vấn đề với việc thực thi song song (hiệu quả) là việc hỗ trợ phân tách Spliteratoryêu cầu một cách an toàn cho luồng để thông báo xem một trong hai đoạn có thấy bất kỳ giá trị nào theo cách an toàn cho luồng hay không. Sau đó, đoạn cuối cùng được thực thi tryAdvancephải nhận ra rằng nó là đoạn cuối cùng (và nó cũng không thể tiến lên) để ném ngoại lệ thích hợp. Vì vậy, tôi đã không thêm hỗ trợ cho việc tách ở đây.


33

Các câu trả lời và nhận xét khác đúng ở chỗ để kiểm tra nội dung của một luồng, người ta phải thêm một thao tác đầu cuối, do đó "tiêu thụ" luồng. Tuy nhiên, người ta có thể làm điều này và chuyển kết quả trở lại thành một luồng mà không cần lưu vào bộ đệm toàn bộ nội dung của luồng. Dưới đây là một số ví dụ:

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());
    }
}

Về cơ bản, hãy biến luồng thành một Iteratorđể gọi hasNext()nó và nếu đúng, hãy chuyển Iteratorngược thành một Stream. Điều này không hiệu quả ở chỗ tất cả các hoạt động tiếp theo trên luồng sẽ đi qua các phương thức hasNext()và của Iterator next(), điều này cũng ngụ ý rằng luồng được xử lý tuần tự một cách hiệu quả (ngay cả khi sau đó nó được chuyển thành song song). Tuy nhiên, điều này cho phép bạn kiểm tra luồng mà không cần lưu vào bộ đệm tất cả các phần tử của nó.

Có thể có một cách để làm điều này bằng cách sử dụng Spliteratorthay vì một Iterator. Điều này có khả năng cho phép luồng trả về có cùng đặc điểm với luồng đầu vào, bao gồm cả việc chạy song song.


1
Tôi không nghĩ rằng có một giải pháp có thể bảo trì sẽ hỗ trợ xử lý song song hiệu quả vì rất khó để hỗ trợ chia tách, tuy nhiên có estimatedSizecharacteristicsthậm chí có thể cải thiện hiệu suất đơn luồng. Nó chỉ xảy ra mà tôi đã viết Spliteratorgiải pháp trong khi bạn đang đăng Iteratorgiải pháp ...
Holger

3
Bạn có thể yêu cầu luồng cho một Spliterator, gọi tryAdvance (lambda) nơi lambda của bạn nắm bắt bất kỳ thứ gì được chuyển đến nó, sau đó trả lại Spliterator ủy quyền hầu hết mọi thứ cho Spliterator bên dưới, ngoại trừ việc nó dán phần tử đầu tiên trở lại đoạn đầu tiên ( và sửa chữa kết quả của ước tính Kích thước).
Brian Goetz

1
@BrianGoetz Vâng, đó là suy nghĩ của tôi, tôi vẫn chưa bận tâm đến việc xử lý tất cả các chi tiết đó.
Stuart Marks

3
@Brian Goetz: Đó là ý của tôi với “quá phức tạp”. Gọi tryAdvancetrước Streamhiện tại nó biến bản chất lười biếng của dòng Streamthành dòng "lười biếng một phần". Nó cũng ngụ ý rằng việc tìm kiếm phần tử đầu tiên không phải là một hoạt động song song nữa vì bạn phải tách trước và thực hiện tryAdvanceđồng thời trên các phần được chia để thực hiện một hoạt động song song thực sự, theo như tôi hiểu. Nếu thao tác đầu cuối duy nhất findAnygiống hoặc tương tự sẽ hủy toàn bộ parallel()yêu cầu.
Holger

2
Vì vậy, để được hỗ trợ song song đầy đủ, bạn không được gọi tryAdvancetrước khi luồng thực hiện và phải gói mọi phần tách thành một proxy và tự thu thập thông tin “hasAny” của tất cả các hoạt động đồng thời và đảm bảo rằng hoạt động đồng thời cuối cùng ném ra ngoại lệ mong muốn nếu dòng đã trống. Rất nhiều thứ…
Holger

18

Điều này có thể đủ trong nhiều trường hợp

stream.findAny().isPresent()

15

Bạn phải thực hiện thao tác đầu cuối trên Luồng để bất kỳ bộ lọc nào được áp dụng. Vì vậy, bạn không thể biết liệu nó có trống không cho đến khi bạn tiêu thụ nó.

Tốt nhất bạn có thể làm là kết thúc Luồng bằng findAny()thao tác đầu cuối, thao tác này sẽ dừng khi tìm thấy bất kỳ phần tử nào, nhưng nếu không có phần tử nào, nó sẽ phải lặp lại tất cả danh sách đầu vào để tìm ra phần tử đó.

Điều này sẽ chỉ giúp bạn nếu danh sách đầu vào có nhiều phần tử và một trong số ít phần tử đầu tiên vượt qua bộ lọc, vì chỉ một tập hợp con nhỏ của danh sách sẽ phải được sử dụng trước khi bạn biết Luồng không trống.

Tất nhiên, bạn sẽ vẫn phải tạo một Luồng mới để tạo danh sách đầu ra.


7
anyMatch(alwaysTrue()), tôi nghĩ đó là gần nhất với hasAny.
Marko Topolnik

1
@MarkoTopolnik Chỉ cần kiểm tra tham chiếu - điều tôi nghĩ là findAny (), mặc dù anyMatch () cũng sẽ hoạt động.
Eran

3
anyMatch(alwaysTrue())hoàn toàn phù hợp với ngữ nghĩa dự định của bạn hasAny, tạo cho bạn một booleanthay vì Optional<T>--- nhưng chúng tôi đang tách sợi lông ở đây :)
Marko Topolnik

1
Lưu ý alwaysTruelà một vị ngữ Guava.
Jean-François Savard,

10
anyMatch(e -> true)sau đó.
FBB

5

Tôi nghĩ là đủ để lập bản đồ boolean

Trong mã này là:

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
Nếu đây là tất cả những gì bạn cần thì câu trả lời của kenglxn sẽ tốt hơn.
Dominykas Mostauskis

nó vô dụng, nó sao chép Collection.isEmpty ()
Krzysiek

@Krzysiek sẽ không vô ích nếu bạn cần lọc bộ sưu tập. Tuy nhiên, tôi đồng ý với Dominykas rằng câu trả lời của kenglxn tốt hơn
Hertzu

Đó là bởi vì nó cũng trùng lặpStream.anyMatch()
Krzysiek

4

Theo ý tưởng của Stuart, điều này có thể được thực hiện bằng cách Spliteratornhư sau:

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;
    }
}

Tôi nghĩ điều này hoạt động với Luồng song song vì stream.spliterator()hoạt động sẽ kết thúc luồng và sau đó xây dựng lại theo yêu cầu

Trong trường hợp sử dụng của tôi, tôi cần một Streamgiá trị mặc định hơn là một giá trị mặc định. điều đó khá dễ thay đổi nếu đây không phải là thứ bạn cần


Tôi không thể tìm ra liệu điều này có ảnh hưởng đáng kể đến hiệu suất với các luồng song song hay không. Có lẽ nên kiểm tra nó nếu điều này là một yêu cầu
phoenix7360

Xin lỗi đã không nhận ra rằng @Holger cũng có một giải pháp với Spliteratortôi tự hỏi làm thế nào cả hai so sánh.
phoenix7360 17/07/17
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.