Yüklemine göre ilk elemanı bul


504

Java 8 lambdas ile oynamaya yeni başladım ve işlevsel dillerde alışkın olduğum şeylerden bazılarını uygulamaya çalışıyorum.

Örneğin, çoğu işlevsel dilde sıralar üzerinde çalışan bir tür bulma işlevi veya yüklemin bulunduğu ilk öğeyi döndüren listeler bulunur true. Java 8'de bunu başarmanın tek yolu:

lst.stream()
    .filter(x -> x > 5)
    .findFirst()

Bununla birlikte, filtre tüm listeyi tarayacağından, en azından benim anlayışım için (bu yanlış olabilir) benim için verimsiz görünüyor. Daha iyi bir yol var mı?


53
Verimsiz değil, Java 8 Stream uygulaması tembel olarak değerlendirildi, bu nedenle filtre sadece terminal çalışmasına uygulanır. Burada aynı soru: stackoverflow.com/questions/21219667/stream-and-lazy-evaluation
Marek Gregor

1
Güzel. Bunu yapmasını umuyordum. Aksi takdirde büyük bir tasarım flop olurdu.
siki

2
Niyetiniz, listenin böyle bir öğeyi içerip içermediğini gerçekten kontrol etmekse (muhtemelen birkaçının ilkini değil) .findAny (), paralellik ortamında teorik olarak daha verimli olabilir ve elbette bu amacı daha net bir şekilde iletebilir.
Joachim Lous

Basit bir forEach döngüsüyle karşılaştırıldığında, bu yığın üzerinde çok sayıda nesne ve onlarca dinamik yöntem çağrısı oluşturur. Bu, performans testlerinizde her zaman alt çizgiyi etkilemeyebilir, ancak sıcak noktalarda Stream ve benzeri ağır yapıların önemsiz kullanımından kaçınmak bir fark yaratır.
Agoston Horvath

Yanıtlar:


720

Hayır, filtre tüm akışı taramaz. Tembel bir akış döndüren bir ara işlemdir (aslında tüm ara işlemler tembel bir akış döndürür). Sizi ikna etmek için aşağıdaki testi yapmanız yeterlidir:

List<Integer> list = Arrays.asList(1, 10, 3, 7, 5);
int a = list.stream()
            .peek(num -> System.out.println("will filter " + num))
            .filter(x -> x > 5)
            .findFirst()
            .get();
System.out.println(a);

Hangi çıktılar:

will filter 1
will filter 10
10

Akışın yalnızca ilk iki öğesinin gerçekten işlendiğini görürsünüz.

Böylece mükemmel olan yaklaşımınızla gidebilirsiniz.


37
Not olarak, get();burada kullandım çünkü akış boru hattına hangi değerleri beslediğimi ve dolayısıyla bir sonuç olacağını biliyorum. Uygulamada, kullanmak gerekir get();, ancak orElse()/ orElseGet()/ orElseThrow()operasyonları bir eleman ile sonuçlanacaktır akışı boru hattına uygulanan eğer bilmiyor olabilir olarak (yerine bir NSEE bir daha anlamlı hata için).
Alexis C.

31
.findFirst().orElse(null);örneğin
Gondy

20
OrElse null kullanmayın. Bu bir anti-desen olmalı. Her şey İsteğe bağlı olarak dahil edilmiştir, bu yüzden neden bir NPE riskini almalısınız? Opsiyonel ile uğraşmanın daha iyi yol olduğunu düşünüyorum. İsteğe bağlı kullanmadan önce isPresent () ile sınayın.
BeJay

@ Anlayamıyorum. yerine ne kullanmalıyım orElse?
John Henckel

3
@JohnHenckel BeJay'in ne anlama geldiğini bir Optionaltür olarak bırakmanız gerektiğini düşünüyorum , bu da .findFirstgeri döner. İsteğe bağlı kullanımlarından biri, geliştiricilerin İsteğe bağlı arabirimin diğer bölümlerini denetlemek , denetlemek veya kullanmak nullyerine seg ile uğraşmaktan kaçınmasına yardımcı olmaktır . Bu daha net mi oldu? myObject != nullmyOptional.isPresent()
AMTerp

102

Filtre tüm listeyi tarayacağından, bu benim için verimsiz görünüyor

Hayır olmaz - yüklemi sağlayan ilk unsur bulunur bulunmaz "kırılacaktır". Tembellik hakkında daha fazla bilgi için özellikle javadoc akış paketinde (vurgu mayını):

Filtreleme, eşleme veya yinelenen kaldırma gibi birçok akış işlemi, optimizasyon fırsatlarını ortaya çıkararak tembel bir şekilde gerçekleştirilebilir. Örneğin, "art arda üç sesli harf içeren ilk Dizeyi bul" öğesinin tüm giriş dizelerini incelemesine gerek yoktur. Akım işlemleri ara (Akım üreten) operasyonlara ve terminal (değer veya yan etki üreten) operasyonlara ayrılmıştır. Ara işlemler her zaman tembeldir.


5
Bu cevap benim için daha bilgilendiriciydi ve nedenini açıklıyor, sadece nasıl değil. Asla yeni ara operasyonlar her zaman tembel değildir; Java akışları beni şaşırtmaya devam ediyor.
kevinarpe

30
return dataSource.getParkingLots()
                 .stream()
                 .filter(parkingLot -> Objects.equals(parkingLot.getId(), id))
                 .findFirst()
                 .orElse(null);

Nesneler listesinden yalnızca bir nesneyi filtrelemek zorunda kaldım. Bu yüzden kullandım, umarım yardımcı olur.


DAHA İYİ: bir boole dönüş değeri aradığımız için, null check: return dataSource.getParkingLots (). Stream (). Filter (parkingLot -> Objects.equals (parkingLot.getId (), id)) .findFirst (). veyaElse (null)! = null;
shreedhar bhat

1
@shreedharbhat Yapmanıza gerek yok .orElse(null) != null. Bunun yerine, İsteğe Bağlı API'ların .isPresentyani .findFirst().isPresent().
AMTerp

@ shreedharbhat öncelikle OP bir boolean dönüş değeri aramıyordu. İkincisi, eğer olsaydı, yazmak daha temiz olurdu.stream().map(ParkingLot::getId).anyMatch(Predicate.isEqual(id))
AjaxLeung

13

Alexis C'nin cevabına ek olarak , aradığınız öğenin var olup olmadığından emin olmadığınız bir dizi listesiyle çalışıyorsanız, bunu kullanın.

Integer a = list.stream()
                .peek(num -> System.out.println("will filter " + num))
                .filter(x -> x > 5)
                .findFirst()
                .orElse(null);

Sonra basitçe olmadığını kontrol edebilir bir olduğunu null.


1
Örneğinizi düzeltmelisiniz. Düz int'e null atayamazsınız. stackoverflow.com/questions/2254435/can-an-int-be-null-in-java
RubioRic

Yayınınızı düzenledim. Bir tamsayı listesinde arama yaparken 0 (sıfır) geçerli bir sonuç olabilir. Değişken türü Tamsayı ve varsayılan değer null olarak değiştirildi.
RubioRic

0

import org.junit.Test;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

// Stream is ~30 times slower for same operation...
public class StreamPerfTest {

    int iterations = 100;
    List<Integer> list = Arrays.asList(1, 10, 3, 7, 5);


    // 55 ms
    @Test
    public void stream() {

        for (int i = 0; i < iterations; i++) {
            Optional<Integer> result = list.stream()
                    .filter(x -> x > 5)
                    .findFirst();

            System.out.println(result.orElse(null));
        }
    }

    // 2 ms
    @Test
    public void loop() {

        for (int i = 0; i < iterations; i++) {
            Integer result = null;
            for (Integer walk : list) {
                if (walk > 5) {
                    result = walk;
                    break;
                }
            }
            System.out.println(result);
        }
    }
}


0

Geliştirilmiş One-Liner yanıtı: Bir boolean dönüş değeri arıyorsanız, isPresent ekleyerek daha iyi yapabiliriz:

return dataSource.getParkingLots().stream().filter(parkingLot -> Objects.equals(parkingLot.getId(), id)).findFirst().isPresent();

Bir boolean dönüş değeri istiyorsanız, anyMatch
AjaxLeung
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.