ค้นหาองค์ประกอบแรกโดยภาคแสดง


504

ฉันเพิ่งเริ่มเล่นกับ lambdas Java 8 และฉันพยายามใช้บางสิ่งที่ฉันคุ้นเคยในภาษาที่ใช้งานได้

trueยกตัวอย่างเช่นภาษาการทำงานส่วนใหญ่จะมีบางชนิดของฟังก์ชั่นการค้นหาที่ดำเนินการในลำดับหรือรายการที่ส่งกลับองค์ประกอบแรกซึ่งเป็นคำกริยา วิธีเดียวที่ฉันเห็นเพื่อให้บรรลุใน Java 8 คือ:

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

อย่างไรก็ตามสิ่งนี้ดูเหมือนจะไม่มีประสิทธิภาพสำหรับฉันเนื่องจากตัวกรองจะสแกนรายการทั้งหมดอย่างน้อยก็เพื่อความเข้าใจของฉัน (ซึ่งอาจผิด) มีวิธีที่ดีกว่า?


53
การใช้งาน Java 8 Stream ไม่ได้ผล แต่อย่างใดดังนั้นการกรองจึงถูกนำไปใช้กับการใช้งานเทอร์มินัลเท่านั้น คำถามเดียวกันที่นี่: stackoverflow.com/questions/21219667/stream-and-lazy-evaluation
Marek Gregor

1
เย็น. นั่นคือสิ่งที่ฉันหวังไว้ มันจะเป็นความล้มเหลวในการออกแบบที่สำคัญ
siki

2
หากความตั้งใจของคุณคือการตรวจสอบว่ารายการดังกล่าวมีองค์ประกอบดังกล่าวอยู่หรือไม่ (ไม่ใช่ซิงเกิลแรกของหลาย ๆ ), .findAny () อาจมีประสิทธิภาพมากกว่าในทางทฤษฎีในการตั้งค่าแนวขนานและแน่นอนว่าการสื่อสารนั้นชัดเจนยิ่งขึ้น
Joachim Lous

เปรียบเทียบกับวงจร forEach แบบง่าย ๆ สิ่งนี้จะสร้างวัตถุจำนวนมากบนฮีปและการเรียกใช้เมธอดแบบไดนามิก แม้ว่าสิ่งนี้อาจไม่ส่งผลกระทบต่อกำไรในการทดสอบประสิทธิภาพของคุณเสมอไป แต่ในฮอตสปอตมันทำให้เกิดความแตกต่างในการงดใช้การสตรีมเล็กน้อยและการสร้างเฮฟวี่เวทที่คล้ายกัน
Agoston Horvath

คำตอบ:


720

ไม่ตัวกรองไม่สแกนสตรีมทั้งหมด เป็นการดำเนินการระดับกลางซึ่งส่งกลับกระแสข้อมูลแบบ lazy (อันที่จริงแล้วการดำเนินการระดับกลางทั้งหมดจะส่งคืนค่าแบบไม่ต่อเนื่อง) หากต้องการโน้มน้าวใจคุณคุณสามารถทำแบบทดสอบต่อไปนี้:

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

ผลลัพธ์ใด:

will filter 1
will filter 10
10

คุณเห็นว่ามีการประมวลผลเพียงสององค์ประกอบแรกของสตรีมเท่านั้น

ดังนั้นคุณสามารถไปกับแนวทางของคุณซึ่งดีอย่างสมบูรณ์


37
ตามบันทึกฉันใช้get();ที่นี่เพราะฉันรู้ว่าฉันป้อนค่าใดให้กับไปป์ไลน์สตรีมและด้วยเหตุนี้จึงจะมีผลลัพธ์ ในทางปฏิบัติคุณไม่ควรใช้get();แต่orElse()/ orElseGet()/ orElseThrow()(สำหรับข้อผิดพลาดที่มีความหมายมากกว่า NSEE) เนื่องจากคุณอาจไม่รู้ว่าการดำเนินการที่นำไปใช้กับไปป์ไลน์นั้นจะส่งผลให้เกิดองค์ประกอบหรือไม่
Alexis C.

31
.findFirst().orElse(null);เช่น
Gondy

20
อย่าใช้ orElse null นั่นควรเป็นรูปแบบต่อต้าน มันรวมอยู่ในตัวเลือกแล้วทำไมคุณถึงต้องเสี่ยงกับ NPE ฉันคิดว่าการจัดการกับทางเลือกเป็นวิธีที่ดีกว่า เพียงทดสอบตัวเลือกด้วย isPresent () ก่อนที่จะใช้
BeJay

@BeJay ฉันไม่เข้าใจ ฉันควรใช้orElseอะไรแทน
John Henckel

3
@JohnHenckel ฉันคิดว่าสิ่งที่ BeJay หมายถึงคือคุณควรปล่อยให้มันเป็นOptionalประเภทซึ่งเป็นสิ่งที่.findFirstผลตอบแทน หนึ่งในการใช้ตัวเลือกคือการช่วยให้นักพัฒนาหลีกเลี่ยงการจัดการกับnullseg แทนการตรวจสอบmyObject != nullคุณสามารถตรวจสอบmyOptional.isPresent()หรือใช้ส่วนอื่น ๆ ของอินเทอร์เฟซเสริม นั่นทำให้ชัดเจนขึ้นหรือไม่?
AMTerp

102

อย่างไรก็ตามเรื่องนี้ดูเหมือนว่าจะไม่มีประสิทธิภาพสำหรับฉันเนื่องจากตัวกรองจะสแกนรายการทั้งหมด

ไม่มันจะไม่ - มันจะ "แตก" ทันทีที่พบองค์ประกอบแรกที่ตอบสนองต่อเพรดิเคต คุณสามารถอ่านเพิ่มเติมเกี่ยวกับความเกียจคร้านในjavadoc แพ็คเกจการสตรีมโดยเฉพาะ (เน้นที่เหมือง):

การดำเนินการสตรีมจำนวนมากเช่นการกรองการแมปหรือการลบซ้ำสามารถดำเนินการอย่างเกียจคร้านซึ่งเป็นการเปิดเผยโอกาสในการปรับให้เหมาะสม ตัวอย่างเช่น "ค้นหาสตริงแรกที่มีสามสระต่อเนื่องกัน" ไม่จำเป็นต้องตรวจสอบสตริงอินพุตทั้งหมด การดำเนินงานของสตรีมจะแบ่งออกเป็นการดำเนินงานระดับกลาง (การผลิตสตรีม) และการดำเนินการเทอร์มินัล การดำเนินการระดับกลางมักขี้เกียจ


5
คำตอบนี้ให้ข้อมูลฉันมากกว่าและอธิบายว่าทำไมไม่เพียงแค่นั้น ฉันไม่เคยดำเนินการระดับกลางใหม่ ๆ เสมอขี้เกียจ; ลำธาร Java ยังคงทำให้ฉันประหลาดใจ
kevinarpe

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

ฉันต้องกรองวัตถุเดียวจากรายการของวัตถุ ดังนั้นฉันจึงใช้มันหวังว่ามันจะช่วยได้


ดีกว่า: เนื่องจากเรากำลังมองหาค่าส่งคืนบูลีนเราสามารถทำได้ดีกว่าโดยการเพิ่มการตรวจสอบว่าง: คืน dataSource.getParkingLots (). stream (). stream (). filter (parkingLot -> Objects.equals (parkingLot.getId (), id)) .findFirst (). orElse (null)! = null;
shreedhar bhat

1
@shreedharbhat .orElse(null) != nullคุณไม่ควรต้องทำ แต่ให้ใช้ API ของตัวเลือกของคือ.isPresent .findFirst().isPresent()
AMTerp

@shreedharbhat ครั้งแรกของ OP ทั้งหมดไม่ได้มองหาค่าส่งคืนบูลีน ประการที่สองถ้าพวกเขาเป็นมันจะดีกว่าที่จะเขียน.stream().map(ParkingLot::getId).anyMatch(Predicate.isEqual(id))
AjaxLeung

13

นอกจากคำตอบของAlexis Cหากคุณทำงานกับรายการอาร์เรย์ซึ่งคุณไม่แน่ใจว่าองค์ประกอบที่คุณค้นหานั้นมีอยู่หรือไม่ให้ใช้สิ่งนี้

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

จากนั้นคุณก็สามารถตรวจสอบว่าคือnull


1
คุณควรแก้ไขตัวอย่างของคุณ คุณไม่สามารถกำหนด null ให้กับ int ธรรมดา stackoverflow.com/questions/2254435/can-an-int-be-null-in-java
RubioRic

ฉันแก้ไขโพสต์ของคุณแล้ว 0 (ศูนย์) อาจเป็นผลลัพธ์ที่ถูกต้องเมื่อคุณค้นหาในรายการจำนวนเต็ม ประเภทตัวแปรแทนที่ด้วยจำนวนเต็มและค่าเริ่มต้นเป็นโมฆะ
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

คำตอบ One-Liner ที่ได้รับการปรับปรุง:หากคุณกำลังมองหาค่าส่งคืนบูลีนเราสามารถทำได้ดีขึ้นโดยการเพิ่ม isPresent:

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

หากคุณต้องการค่าบูลีนคืนคุณควรใช้ anyMatch
AjaxLeung
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.