Her biri için Java 8 akışından ayrılma veya geri dönüş?


313

Kullanırken harici bir yineleme aşırı Iterablebiz kullanım breakya da returnhazır her bir döngü olarak geliştirilmiş den:

for (SomeObject obj : someObjects) {
   if (some_condition_met) {
      break; // or return obj
   }
}

Nasıl breakya returnda dahili yinelemeyi aşağıdaki gibi bir Java 8 lambda ifadesinde kullanarak yapabiliriz :

someObjects.forEach(obj -> {
   //what to do here?
})

6
Yapamazsın. Sadece gerçek bir forifade kullanın .
Nisan'ta Boann


Tabii ki olduğu için mümkün forEach(). Solüsyon hoş değil, ama bir mümkün. Cevabımı aşağıda görebilirsiniz.
Honza Zidek

Başka bir yaklaşımı düşünün, sadece kod yürütmek istemezsiniz , bu nedenle, hile ifiçindeki basit bir durum forEachhile yapar.
Thomas Decaux

Yanıtlar:


374

Buna ihtiyacınız varsa kullanmamalısınız forEach, ancak akışlarda bulunan diğer yöntemlerden birini kullanmalısınız ; hangisi, hedefinizin ne olduğuna bağlıdır.

Örneğin, bu döngünün amacı bazı yüklemlerle eşleşen ilk öğeyi bulmaksa:

Optional<SomeObject> result =
    someObjects.stream().filter(obj -> some_condition_met).findFirst();

(Not: Bu, tüm koleksiyonu yinelemez, çünkü akışlar tembel olarak değerlendirilir - koşulla eşleşen ilk nesnede durur).

Koleksiyonda koşulun geçerli olduğu bir öğe olup olmadığını öğrenmek istiyorsanız, şunları kullanabilirsiniz anyMatch:

boolean result = someObjects.stream().anyMatch(obj -> some_condition_met);

7
Bu, bir nesne bulmak hedef ise işe yarar , ancak bu hedefe genellikle returnbir findSomethingyöntemden gelen bir ifade tarafından sunulur . breakdaha çok bir işlem -çalışma türü ile ilişkilidir .
Marko Topolnik

1
@MarkoTopolnik Evet, orijinal poster bize tam olarak hedefin ne olduğunu bilmek için yeterli bilgi vermedi; bahsettiğim ikisinin yanı sıra üçüncü bir olasılık. (Akarsu ile "süre" yapmanın basit bir yolu var mı?).
Jesper

3
Hedef, iptal davranışını doğru bir şekilde uygulamak olduğunda ne olacak? Kullanıcının iptal istediğini fark ettiğimizde forEach lambda'nın içine bir çalışma zamanı istisnası atmak için yapabileceğimiz en iyi şey mi?
user2163960

1
@HonzaZidek Düzenlendi, ama asıl mesele bunun mümkün olup olmadığı değil, doğru şeyleri yapmaktır. Bunun için zorla kullanmaya çalışmamalısınız forEach; bunun yerine başka, daha uygun bir yöntem kullanmalısınız.
Jesper

1
@Jesper Sana katılıyorum, "İstisna çözümünü" sevmediğimi yazdım. Ancak "Bu mümkün değil forEach" ifadeniz teknik olarak yanlıştı. Ayrıca çözümünüzü de tercih ederim, ancak cevabımda verilen çözümün tercih edildiği kullanım durumlarını hayal edebiliyorum: gerçek bir istisna nedeniyle döngü ne zaman bitmelidir. Akışı kontrol etmek için genellikle istisnaları kullanmamanız gerektiğini kabul ediyorum .
Honza Zidek

54

Bu ise mümkün Iterable.forEach()(ancak güvenilir olan Stream.forEach()). Solüsyon hoş değil, ama bir mümkün.

UYARI : İş mantığını kontrol etmek için değil, yalnızca uygulamasının gerçekleşmesi sırasında ortaya çıkan istisnai bir durumu ele almak için kullanmalısınız forEach(). Bir kaynağın aniden erişilebilir olması gibi, işlenen nesnelerden biri bir sözleşmeyi ihlal ediyor (örneğin sözleşme, akıştaki tüm öğelerin nulldeğil, aniden ve beklenmedik bir şekilde bunlardan biri olması gerektiğini söylüyor null) vb.

İçin belgelere göre Iterable.forEach():

Tüm öğeler işleninceye veya eylem bir istisna Iterable atana kadar verilen her eylem için verilen eylemi gerçekleştirir ... Eylem tarafından atılan istisnalar arayana aktarılır.

Böylece, iç döngüyü derhal kıracak bir istisna atarsınız.

Kod böyle bir şey olacak - Sevdim diyemem ama işe yarıyor. Kendi sınıfınızı BreakExceptionyaratırsınız RuntimeException.

try {
    someObjects.forEach(obj -> {
        // some useful code here
        if(some_exceptional_condition_met) {
            throw new BreakException();
       }
    }
}
catch (BreakException e) {
    // here you know that your condition has been met at least once
}

O Bildirimi try...catcholduğu değil doğrusu etrafında bütün etrafında lambda ifadesi, ancak forEach()yöntem. Daha görünür hale getirmek için, kodu daha net gösteren aşağıdaki transkripsiyonuna bakın:

Consumer<? super SomeObject> action = obj -> {
    // some useful code here
    if(some_exceptional_condition_met) {
        throw new BreakException();
    }
});

try {
    someObjects.forEach(action);
}
catch (BreakException e) {
    // here you know that your condition has been met at least once
}

37
Bence bu kötü bir uygulamadır ve soruna bir çözüm olarak görülmemelidir. Yeni başlayanlar için yanıltıcı olabileceğinden tehlikelidir. Etkili Java 2nd Edition, Bölüm 9, Madde 57'ye göre: 'Yalnızca istisnai durumlar için istisnalar kullanın'. Ayrıca 'Programlama hatalarını belirtmek için çalışma zamanı istisnalarını kullanın'. Kesin olarak, bu çözümü düşünen herkesi @Jesper çözümüne bakmaya teşvik ediyorum.
Louis F.

15
@LouisF. Ben açıkça "Sevdiğimi söyleyemem ama işe yarıyor" dedim. OP "forEach () 'dan nasıl kırılacağını" sordu ve bu bir cevap. Bunun iş mantığını kontrol etmek için kullanılmaması gerektiğine tamamen katılıyorum. Ancak aniden forEach () veya ortasında istisna kullanmanın kötü bir uygulama olmadığı bir kaynağa bağlantı gibi bazı yararlı kullanım durumlarını hayal edebiliyorum. Cevabımı netleştirmek için bir paragraf ekledim.
Honza Zidek

1
Bence bu iyi bir çözüm. Google'da "java istisnaları" ve "en iyi uygulamalar" veya "işaretlenmemiş" gibi birkaç kelimeyle başka aramalar yaptıktan sonra, istisnaların nasıl kullanılacağı konusunda tartışmalar olduğunu görüyorum. Akışta dakikalar süren bir harita gerçekleştirdiği için kodumda bu çözümü kullandım. Ben "isUserCancelRequested" bayrağı için her hesaplama başında kontrol ve böylece doğru olduğunda bir istisna attı böylece kullanıcının görevi iptal edebilmek istedi. Temiz, istisna kodu kodun küçük bir kısmı için izole edilmiş ve çalışıyor.
Jason

2
Not Stream.forEachyok değil bir istisna atma için bu şekilde çalışması garanti edilmez, böylece özel durumları hakkında aynı güçlü bir garanti sağlar, arayana yönlendirilen Stream.forEach.
Radiodef

1
@Radiodef Bu geçerli bir nokta, teşekkürler. Orijinal gönderi hakkındaydı Iterable.forEach(), ama metnime sadece bütünlük için ekledim.
Honza Zidek

43

Bir lambdadaki geri dönüş, her biri için bir süreye eşittir, ancak bir mola eşdeğeri yoktur. Devam etmek için bir dönüş yapabilirsiniz:

someObjects.forEach(obj -> {
   if (some_condition_met) {
      return;
   }
})

2
Genel gereksinimi karşılamak için güzel ve deyimsel bir çözüm. Kabul edilen cevap şartı açıklar.
davidxxx

6
Başka bir deyişle, bu gerçek soru olan "Java 8 akış forEach
kırmak

1
Bu yine de kayıtları kaynak akış yoluyla "çeker", bu da bir çeşit uzak veri kümesinde sayfalama yapıyorsanız kötüdür.
Adrian Baker

23

Aşağıda bir projede kullandığım çözümü bulacaksınız. Bunun yerine forEachsadece şunu kullanın allMatch:

someObjects.allMatch(obj -> {
    return !some_condition_met;
});

11

Ya devam edip etmeyeceğinizi belirten bir yöntem kullanmanız gerekir (bunun yerine mola verir) veya elbette çok çirkin bir yaklaşım olan bir istisna atmanız gerekir.

Böylece böyle bir forEachConditionalyöntem yazabilirsiniz :

public static <T> void forEachConditional(Iterable<T> source,
                                          Predicate<T> action) {
    for (T item : source) {
        if (!action.test(item)) {
            break;
        }
    }
}

Bunun yerine Predicate<T>, kendi işlevsel arayüzünüzü aynı genel yöntemle (a Tve geri dönen bir şey) tanımlamak isteyebilirsiniz, boolancak beklentiyi daha açık bir şekilde belirten isimlerle - Predicate<T>burada ideal değildir.


1
Java 8 zaten kullanılıyorsa , aslında Streams API ve burada fonksiyonel bir yaklaşım kullanmanın bu yardımcı yöntemi oluşturma tercih edilebilir olduğunu öneriyoruz .
skiwi

3
Bu klasik takeWhileişlemdir ve bu soru Streams API'sındaki eksikliğinin ne kadar hissettiğini gösterenlerden biridir.
Marko Topolnik

2
@Marko: takeWhile, her biri için bir eylem gerçekleştirmeyen, öğeleri üreten bir işlem gibi görünüyor. Kesinlikle .NET LINQ içinde TakeWhile yan etkileri olan bir eylem ile kullanmak kötü bir form olacaktır.
Jon Skeet

9

Java8 + rxjava kullanabilirsiniz .

//import java.util.stream.IntStream;
//import rx.Observable;

    IntStream intStream  = IntStream.range(1,10000000);
    Observable.from(() -> intStream.iterator())
            .takeWhile(n -> n < 10)
            .forEach(n-> System.out.println(n));

8
Java 9, akışlarda takeWhile işlemi için destek sunacaktır.
pisaruk

6

Paralel işlemlerinde maksimum performans için kullanımı findAny Findfirst benzerdir () ().

Optional<SomeObject> result =
    someObjects.stream().filter(obj -> some_condition_met).findAny();

Ancak kararlı bir sonuç isteniyorsa, bunun yerine findFirst () öğesini kullanın.

Ayrıca eşleşen desenlerin (anyMatch () / allMatch) yalnızca boole döndüreceğini, eşleşen nesneyi almayacağınızı unutmayın.


6

Aşağıdakilerle Java 9+ ile güncelleyin takeWhile:

MutableBoolean ongoing = MutableBoolean.of(true);
someobjects.stream()...takeWhile(t -> ongoing.value()).forEach(t -> {
    // doing something.
    if (...) { // want to break;
        ongoing.setFalse();
    }
});

3

Böyle bir şeyle başardım

  private void doSomething() {
            List<Action> actions = actionRepository.findAll();
            boolean actionHasFormFields = actions.stream().anyMatch(actionHasMyFieldsPredicate());
            if (actionHasFormFields){
                context.addError(someError);
            }
        }
    }

    private Predicate<Action> actionHasMyFieldsPredicate(){
        return action -> action.getMyField1() != null;
    }

3

Bunu bir peek (..) ve anyMatch (..) karışımı kullanarak elde edebilirsiniz.

Örneğinizi kullanma:

someObjects.stream().peek(obj -> {
   <your code here>
}).anyMatch(obj -> !<some_condition_met>);

Veya sadece genel bir util yöntemi yazın:

public static <T> void streamWhile(Stream<T> stream, Predicate<? super T> predicate, Consumer<? super T> consumer) {
    stream.peek(consumer).anyMatch(predicate.negate());
}

Ve sonra şu şekilde kullanın:

streamWhile(someObjects.stream(), obj -> <some_condition_met>, obj -> {
   <your code here>
});

0

Peki ya bu:

final BooleanWrapper condition = new BooleanWrapper();
someObjects.forEach(obj -> {
   if (condition.ok()) {
     // YOUR CODE to control
     condition.stop();
   }
});

BooleanWrapperAkışı kontrol etmek için uygulamanız gereken bir sınıf nerede .


3
Yoksa AtomicBoolean mı?
Koekje

1
Bu, nesne işlemeyi atlar !condition.ok(), ancak forEach()yine de tüm nesneler üzerinde döngü yapmasını engellemez . Yavaş kısım, forEach()tüketicisi değil , yinelemeyse (örneğin, yavaş bir ağ bağlantısından nesneler alırsa), bu yaklaşım çok yararlı değildir.
zakmck

Evet haklısın, bu durumda cevabım oldukça yanlış.
Thomas Decaux

0
int valueToMatch = 7;
Stream.of(1,2,3,4,5,6,7,8).anyMatch(val->{
   boolean isMatch = val == valueToMatch;
   if(isMatch) {
      /*Do whatever you want...*/
       System.out.println(val);
   }
   return isMatch;
});

Sadece eşleştiği yerde işlem yapar, ve eşleşmeden sonra iterasyonunu durdurur.


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.