Tìm phần tử đầu tiên theo vị ngữ


504

Tôi mới bắt đầu chơi với Java 8 lambdas và tôi đang cố gắng thực hiện một số điều mà tôi đã quen với các ngôn ngữ chức năng.

Ví dụ, hầu hết các ngôn ngữ chức năng có một số loại chức năng tìm kiếm hoạt động theo trình tự hoặc danh sách trả về phần tử đầu tiên, trong đó vị ngữ là true. Cách duy nhất tôi có thể thấy để đạt được điều này trong Java 8 là:

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

Tuy nhiên điều này có vẻ không hiệu quả đối với tôi, vì bộ lọc sẽ quét toàn bộ danh sách, ít nhất là theo sự hiểu biết của tôi (có thể sai). Có cách nào tốt hơn?


53
Không hiệu quả, việc triển khai Luồng Java 8 được đánh giá lười biếng, do đó, bộ lọc chỉ được áp dụng cho hoạt động của thiết bị đầu cuối. Câu hỏi tương tự ở đây: stackoverflow.com/questions/21219667/stream-and-lazy-ev Assessment
Marek Gregor

1
Mát mẻ. Đó là những gì tôi hy vọng nó sẽ làm. Nó sẽ là một thiết kế chính flop nếu không.
siki

2
Nếu ý định của bạn thực sự là kiểm tra xem danh sách có chứa một yếu tố như vậy hay không (không phải là đầu tiên trong số một số có thể), về mặt lý thuyết có thể hiệu quả hơn trong cài đặt song song, và dĩ nhiên truyền đạt ý định đó rõ ràng hơn.
Joachim Lous

So với một chu trình forEach đơn giản, điều này sẽ tạo ra rất nhiều đối tượng trên heap và hàng chục lệnh gọi phương thức động. Mặc dù điều này có thể không phải lúc nào cũng ảnh hưởng đến điểm mấu chốt trong các bài kiểm tra hiệu suất của bạn, nhưng ở những điểm nóng, điều này tạo ra sự khác biệt để tránh sử dụng Stream và các cấu trúc nặng tương tự.
Agoston Horvath

Câu trả lời:


720

Không, bộ lọc không quét toàn bộ luồng. Đó là một hoạt động trung gian, trả về một luồng lười biếng (thực ra tất cả các hoạt động trung gian đều trả về một luồng lười biếng). Để thuyết phục bạn, bạn chỉ cần thực hiện bài kiểm tra sau:

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

Đầu ra nào:

will filter 1
will filter 10
10

Bạn thấy rằng chỉ có hai yếu tố đầu tiên của luồng thực sự được xử lý.

Vì vậy, bạn có thể đi với cách tiếp cận của bạn là hoàn toàn tốt.


37
Như một lưu ý, tôi đã sử dụng get();ở đây vì tôi biết những giá trị nào tôi cung cấp cho đường ống truyền phát và do đó sẽ có kết quả. Trong thực tế, bạn không nên sử dụng get();, nhưng orElse()/ orElseGet()/ orElseThrow()(đối với một lỗi có ý nghĩa hơn thay vì NSEE) vì bạn có thể không biết liệu các hoạt động được áp dụng cho đường ống dẫn sẽ dẫn đến một yếu tố.
Alexis C.

31
.findFirst().orElse(null);ví dụ
Gondy

20
Đừng sử dụng orElse null. Đó phải là một mô hình chống. Tất cả được bao gồm trong Tùy chọn, vậy tại sao bạn nên mạo hiểm NPE? Tôi nghĩ rằng đối phó với Tùy chọn là cách tốt hơn. Chỉ cần kiểm tra Tùy chọn với isPftime () trước khi bạn sử dụng.
BeJay

@BeJay tôi không hiểu. Tôi nên sử dụng gì thay vì orElse?
John Henckel

3
@JohnHenckel Tôi nghĩ ý nghĩa của BeJay là bạn nên để nó như một Optionalkiểu, đó là những gì .findFirsttrả về. Một trong những cách sử dụng Tùy chọn là giúp nhà phát triển tránh phải xử lý nullseg thay vì kiểm tra myObject != null, bạn có thể kiểm tra myOptional.isPresent()hoặc sử dụng các phần khác của giao diện Tùy chọn. Điều đó đã làm cho nó rõ ràng hơn?
AMTerp

102

Tuy nhiên điều này có vẻ không hiệu quả với tôi, vì bộ lọc sẽ quét toàn bộ danh sách

Không, nó sẽ không - nó sẽ "phá vỡ" ngay khi phần tử đầu tiên thỏa mãn vị ngữ được tìm thấy. Bạn có thể đọc thêm về sự lười biếng trong gói luồng javadoc , đặc biệt (nhấn mạnh của tôi):

Nhiều hoạt động truyền phát, chẳng hạn như lọc, ánh xạ hoặc loại bỏ trùng lặp, có thể được triển khai một cách lười biếng, cho thấy cơ hội tối ưu hóa. Ví dụ: "tìm Chuỗi đầu tiên có ba nguyên âm liên tiếp" không cần kiểm tra tất cả các chuỗi đầu vào. Hoạt động luồng được chia thành các hoạt động trung gian (Sản xuất luồng) và hoạt động của thiết bị đầu cuối (giá trị hoặc hiệu ứng phụ). Hoạt động trung gian luôn lười biếng.


5
Câu trả lời này có nhiều thông tin hơn cho tôi và giải thích lý do tại sao, không chỉ bằng cách nào. Tôi không bao giờ hoạt động trung gian mới luôn lười biếng; Các luồng Java tiếp tục làm tôi ngạc nhiên.
kevinarpe 6/07/2015

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

Tôi phải lọc ra chỉ một đối tượng từ danh sách các đối tượng. Vì vậy, tôi đã sử dụng điều này, hy vọng nó sẽ giúp.


TỐT HƠN: vì chúng tôi đang tìm kiếm một giá trị trả về boolean, chúng tôi có thể làm điều đó tốt hơn bằng cách thêm null .findFirst (). orElse (null)! = null;
shreedhar bhat

1
@shreedharbhat Bạn không cần phải làm .orElse(null) != null. Thay vào đó, hãy sử dụng API tùy chọn .isPresenttức là .findFirst().isPresent().
AMTerp

@shreedharbhat trước hết OP không tìm kiếm giá trị trả về boolean. Thứ hai, nếu chúng là như vậy, nó sẽ sạch hơn để viết.stream().map(ParkingLot::getId).anyMatch(Predicate.isEqual(id))
AjaxLeung

13

Ngoài câu trả lời của Alexis C , Nếu bạn đang làm việc với một danh sách mảng, trong đó bạn không chắc chắn liệu phần tử bạn đang tìm kiếm có tồn tại hay không, hãy sử dụng phần tử này.

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

Sau đó, bạn có thể chỉ cần kiểm tra xem mộtnull.


1
Bạn nên sửa ví dụ của bạn. Bạn không thể gán null cho int int. stackoverflow.com/questions/2254435/can-an-int-be-null-in-java
RubioRic

Tôi đã chỉnh sửa bài viết của bạn. 0 (không) có thể là kết quả hợp lệ khi bạn đang tìm kiếm trong danh sách các số nguyên. Thay thế loại biến bằng Số nguyên và giá trị mặc định bằng null.
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

Câu trả lời One-liner được cải thiện: Nếu bạn đang tìm kiếm giá trị trả về boolean, chúng tôi có thể làm điều đó tốt hơn bằng cách thêm isPftime:

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

Nếu bạn muốn giá trị trả về boolean, bạn nên sử dụng anyMatch
AjaxLeung
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.