Java 8 Akışının boş olup olmadığı nasıl kontrol edilir?


100

A'nın Streamboş olup olmadığını nasıl kontrol edebilirim ve değilse bir istisna atabilirim, terminal dışı bir işlem olarak?

Temel olarak, aşağıdaki koda eşdeğer bir şey arıyorum, ancak aradaki akışı gerçekleştirmeden. Özellikle, akış fiilen bir terminal işlemi tarafından tüketilmeden önce kontrol gerçekleştirilmemelidir.

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
Pastanızı alıp da yiyemezsiniz - ve tam anlamıyla, bu bağlamda. Yapmalisin tüketmek içi boş olduğunda öğrenmek için akışı. Stream'in anlambiliminin (tembellik) amacı budur.
Marko Topolnik

Sonunda tüketilecek, bu noktada kontrol yapılmalıdır
Cephalopod

12
Akışın boş olmadığını kontrol etmek için en az bir element tüketmeyi denemelisiniz. Bu noktada, dere "bekaretini" kaybetti ve baştan tekrar tüketilemez.
Marko Topolnik

Yanıtlar:


24

Sınırlı paralel yeteneklerle yaşayabiliyorsanız, aşağıdaki çözüm işe yarayacaktır:

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

İşte onu kullanan bazı örnek kod:

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

Paralel yürütmede (verimli) sorun, parçaların bölünmesinin desteklenmesinin Spliterator, parçalardan herhangi birinin iş parçacığı açısından güvenli bir şekilde herhangi bir değer görüp görmediğini anlamak için iş parçacığı güvenli bir yol gerektirmesidir. Daha sonra, yürütülen parçaların tryAdvancesonuncusu, uygun istisnayı atmanın sonuncusu olduğunu (ve ilerleyemediğini) anlamalıdır. Bu yüzden buraya bölme desteği eklemedim.


33

Diğer cevaplar ve yorumlar, bir akışın içeriğini incelemek için bir uçbirim işlemi eklemek ve dolayısıyla akışı "tüketmek" gerektiğinden doğrudur. Ancak, akışın tüm içeriğini ara belleğe almadan bunu yapabilir ve sonucu bir akışa geri döndürebilirsiniz. İşte birkaç örnek:

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

Temel olarak , onu Iteratoraramak için akışı bir'e çevirin hasNext()ve doğruysa, Iteratorarka tarafı bir Stream. Bu, akıştaki sonraki tüm işlemlerin Yineleyici hasNext()venext() yöntemlerden , aynı zamanda akışın etkili bir şekilde sıralı olarak işlendiği anlamına gelir (daha sonra paralel dönse bile). Ancak bu, akışı tüm öğelerini ara belleğe almadan test etmenize olanak tanır.

Bunu muhtemelen bir Spliteratoryerine a kullanarak yapmanın bir yolu vardır Iterator. Bu, potansiyel olarak geri dönen akışın, paralel çalışma da dahil olmak üzere giriş akışı ile aynı özelliklere sahip olmasına izin verir.


1
Bölmeyi desteklemek zor olduğu için verimli paralel işlemeyi destekleyen, ancak tek iş parçacıklı performansa sahip olan estimatedSizeve characteristicshatta bunu artırabilecek bir çözüm olduğunu düşünmüyorum . SpliteratorÇözümü siz gönderirken ben yazdım Iterator
Holger

3
Akıştan bir Spliterator isteyebilir, lambda'nızın kendisine geçirilen her şeyi yakaladığı tryAdvance (lambda) 'yı çağırabilir ve ardından ilk öğeyi ilk parçaya ( ve estimateSize sonucunu düzeltir).
Brian Goetz

1
@BrianGoetz Evet, benim düşüncem buydu, henüz tüm bu ayrıntıları ele almak için ayak işi yapmaya zahmet etmedim.
Stuart Marks

3
@Brian Goetz: "Çok karmaşık" derken bunu kastettim. Bunu tryAdvanceyapmadan önce aramak Stream, tembel doğasını Stream"kısmen tembel" bir akıma dönüştürür. Aynı zamanda, tryAdvanceanladığım kadarıyla gerçek bir paralel işlem yapmak için ilk önce ayrılmanız ve aynı anda bölünmüş parçalar üzerinde yapmanız gerektiğinden, ilk elemanı aramanın artık paralel bir işlem olmadığını ima ediyor . Tek terminal işlemi findAnyveya benzeri ise, tüm parallel()talebi yok eder .
Holger

2
Bu nedenle, tam paralel destek için tryAdvance, akış yapmadan önce çağrı yapmamalı ve bölünmüş her parçayı bir proxy'ye sarmanız ve tüm eşzamanlı işlemlerin "hasAny" bilgilerini kendi başınıza toplamanız ve son eşzamanlı işlemin istenen istisnayı atmasını sağlamanız gerekir. akış boştu. Bir sürü şey…
Holger

25

Bu çoğu durumda yeterli olabilir

stream.findAny().isPresent()

15

Filtrelerden herhangi birinin uygulanabilmesi için Akış üzerinde bir terminal işlemi gerçekleştirmelisiniz. Bu nedenle tüketene kadar boş olup olmayacağını bilemezsiniz.

Yapabileceğiniz en iyi şey, Akışı bir findAny() herhangi bir öğe bulduğunda duracak olan terminal işlemiyle , ancak eğer yoksa, bunu bulmak için tüm giriş listesini yinelemesi gerekecektir.

Bu, yalnızca giriş listesinde çok sayıda öğe varsa ve ilk birkaçından biri filtreleri geçerse yardımcı olur, çünkü Akışın boş olmadığını bilmeden önce listenin yalnızca küçük bir alt kümesinin tüketilmesi gerekir.

Elbette, çıktı listesini oluşturmak için yeni bir Akış oluşturmanız gerekecek.


7
Orada anyMatch(alwaysTrue())Bunun en yakın olduğunu düşünüyorum, hasAny.
Marko Topolnik

1
@MarkoTopolnik Sadece referansı kontrol ettim - aklımda olan findAny () idi, ancak anyMatch () de işe yarayacaktı.
Eran

3
anyMatch(alwaysTrue())sizin amaçladığınız semantik ile mükemmel bir şekilde eşleşir, hasAnysize bir --- booleanyerine Optional<T>--- ama burada saçları
ayırıyoruz

1
Note alwaysTruebir Guava yüklemidir.
Jean-François Savard

11
anyMatch(e -> true)sonra.
FBB

6

Bence bir boole eşlemek için yeterli olmalı

Kodda bu:

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
Tüm ihtiyacınız olan buysa, kenglxn'in cevabı daha iyidir.
Dominykas Mostauskis

işe yaramaz, Collection.isEmpty ()
Krzysiek

@Krzysiek, koleksiyonu filtrelemeniz gerekiyorsa işe yaramaz. Ancak, kenglxn'in cevabının daha iyi olduğu konusunda Dominykas'a katılıyorum
Hertzu

Bunun nedeni aynı zamanda yinelenmesidirStream.anyMatch()
Krzysiek

4

Stuart'ın fikrini takiben, bu şu şekilde yapılabilir 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()İşlem akışı sonlandıracağı ve ardından gerektiği gibi yeniden oluşturacağı için bunun paralel Akışlarla çalıştığını düşünüyorum

Kullanım durumumda Stream, varsayılan bir değerden ziyade bir varsayılana ihtiyacım vardı . İhtiyacınız olan bu değilse değiştirmek oldukça kolaydır


Bunun paralel akışlarda performansı önemli ölçüde etkileyip etkilemeyeceğini anlayamıyorum. Bu bir gereklilikse muhtemelen test
etmelisiniz

Üzgünüm, @Holger'ın da bir çözümü olduğunu fark etmedim, Spliteratormerak ediyorum, ikisinin nasıl karşılaştırıldığını merak ediyorum.
phoenix7360

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.