Temukan elemen pertama dengan predikat

504

Saya baru saja mulai bermain dengan lambda Java 8 dan saya mencoba menerapkan beberapa hal yang biasa saya gunakan dalam bahasa fungsional.

Misalnya, sebagian besar bahasa fungsional memiliki semacam fungsi mencari yang beroperasi pada urutan, atau daftar yang mengembalikan elemen pertama, yang menjadi predikatnya true. Satu-satunya cara saya bisa melihat untuk mencapai ini di Java 8 adalah:

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

Namun ini tampaknya tidak efisien bagi saya, karena filter akan memindai seluruh daftar, setidaknya untuk pemahaman saya (yang mungkin salah). Apakah ada cara yang lebih baik?

siki
sumber
53
Ini tidak efisien, implementasi Java 8 Stream malas dievaluasi, jadi filter hanya diterapkan pada operasi terminal. Pertanyaan yang sama di sini: stackoverflow.com/questions/21219667/stream-and-lazy-evaluation
Marek Gregor
1
Keren. Itulah yang saya harapkan akan dilakukan. Itu akan menjadi kegagalan desain utama jika tidak.
siki
2
Jika niat Anda benar-benar untuk memeriksa apakah daftar tersebut mengandung elemen seperti itu (tidak memilih yang pertama dari beberapa kemungkinan), .findAny () secara teoritis dapat lebih efisien dalam pengaturan paralel, dan tentu saja mengkomunikasikan maksud itu dengan lebih jelas.
Joachim Lous
Dibandingkan dengan siklus forEach sederhana, ini akan membuat banyak objek di heap dan puluhan panggilan metode dinamis. Meskipun ini mungkin tidak selalu mempengaruhi garis bawah dalam tes kinerja Anda, di hot spot itu membuat perbedaan untuk menjauhkan diri dari penggunaan sepele Stream dan konstruksi kelas berat serupa.
Agoston Horvath

Jawaban:

720

Tidak, filter tidak memindai seluruh aliran. Ini adalah operasi perantara, yang mengembalikan aliran malas (sebenarnya semua operasi perantara mengembalikan aliran malas). Untuk meyakinkan Anda, Anda dapat melakukan tes berikut:

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

Output yang mana:

will filter 1
will filter 10
10

Anda melihat bahwa hanya dua elemen pertama dari aliran yang benar-benar diproses.

Jadi Anda bisa pergi dengan pendekatan Anda yang sangat baik.

Alexis C.
sumber
37
Sebagai catatan, saya gunakan di get();sini karena saya tahu nilai mana yang saya berikan ke pipa aliran dan karenanya akan ada hasilnya. Dalam praktiknya, Anda tidak boleh menggunakan get();, tetapi orElse()/ orElseGet()/ orElseThrow()(untuk kesalahan yang lebih berarti daripada NSEE) karena Anda mungkin tidak tahu apakah operasi yang diterapkan pada pipa aliran akan menghasilkan elemen.
Alexis C.
31
.findFirst().orElse(null);misalnya
Gondy
20
Jangan gunakan orElse null. Itu harus menjadi anti-pola. Itu semua termasuk dalam Opsional jadi mengapa Anda harus mengambil risiko NPE? Saya pikir berurusan dengan Opsional adalah cara yang lebih baik. Cukup uji Opsional dengan isPresent () sebelum Anda menggunakannya.
BeJay
@ BeJay saya tidak mengerti. apa yang harus saya gunakan orElse?
John Henckel
3
@ JohnHenckel Saya pikir apa artinya BeJay adalah bahwa Anda harus meninggalkannya sebagai Optionaltipe, yang merupakan apa yang .findFirstdikembalikan. Salah satu penggunaan Opsional adalah untuk membantu pengembang menghindari keharusan berurusan dengan nullseg alih-alih mengecek myObject != null, Anda dapat memeriksa myOptional.isPresent(), atau menggunakan bagian lain dari antarmuka Opsional. Apakah itu membuatnya lebih jelas?
AMTerp
102

Namun ini tampaknya tidak efisien bagi saya, karena filter akan memindai seluruh daftar

Tidak itu tidak akan - itu akan "pecah" segera setelah elemen pertama yang memenuhi predikat ditemukan. Anda dapat membaca lebih lanjut tentang kemalasan dalam paket stream javadoc , khususnya (penekanan saya):

Banyak operasi streaming, seperti pemfilteran, pemetaan, atau penghapusan duplikat, dapat diimplementasikan dengan malas, membuka peluang untuk optimisasi. Misalnya, "temukan String pertama dengan tiga vokal berurutan" tidak perlu memeriksa semua string input. Operasi aliran dibagi menjadi operasi antara (penghasil aliran) dan operasi terminal (penghasil nilai atau efek samping). Operasi perantara selalu malas.

wha'eve '
sumber
5
Jawaban ini lebih informatif bagi saya, dan menjelaskan alasannya, bukan hanya bagaimana. Saya tidak pernah operasi perantara baru selalu malas; Streaming Java terus mengejutkan saya.
kevinarpe
30
return dataSource.getParkingLots()
                 .stream()
                 .filter(parkingLot -> Objects.equals(parkingLot.getId(), id))
                 .findFirst()
                 .orElse(null);

Saya harus memfilter hanya satu objek dari daftar objek. Jadi saya menggunakan ini, semoga membantu.

CodeShadow
sumber
LEBIH BAIK: karena kami mencari nilai pengembalian boolean, kami dapat melakukannya dengan lebih baik dengan menambahkan cek kosong: mengembalikan dataSource.getParkingLots (). Stream (). Filter (parkingLot -> Objects.equals (parkingLot.getId (), id)) .findFirst (). orElse (null)! = null;
shreedhar bhat
1
@shreedharbhat Anda tidak perlu melakukannya .orElse(null) != null. Sebagai gantinya, gunakan API opsional .isPresentyaitu .findFirst().isPresent().
AMTerp
@shreedharbhat pertama-tama OP tidak mencari nilai pengembalian boolean. Yang kedua jika itu adalah tulisan yang lebih bersih.stream().map(ParkingLot::getId).anyMatch(Predicate.isEqual(id))
AjaxLeung
13

Selain jawaban Alexis C , Jika Anda bekerja dengan daftar array, di mana Anda tidak yakin apakah elemen yang Anda cari ada, gunakan ini.

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

Maka Anda hanya bisa memeriksa apakah suatu adalah null.

Ifesinachi Bryan
sumber
1
Anda harus memperbaiki contoh Anda. Anda tidak dapat menetapkan null ke int polos. stackoverflow.com/questions/2254435/can-an-int-be-null-in-java
RubioRic
Saya telah mengedit posting Anda. 0 (nol) mungkin hasil yang valid ketika Anda mencari di dalam daftar bilangan bulat. Jenis variabel yang diganti oleh Integer dan nilai default dengan nol.
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);
        }
    }
}
aillusions
sumber
0

Jawaban One-Liner yang ditingkatkan: Jika Anda mencari nilai pengembalian boolean, kami dapat melakukannya dengan lebih baik dengan menambahkan isPresent:

return dataSource.getParkingLots().stream().filter(parkingLot -> Objects.equals(parkingLot.getId(), id)).findFirst().isPresent();
shreedhar bhat
sumber
Jika Anda menginginkan nilai pengembalian boolean, Anda harus menggunakan anyMatch
AjaxLeung