การเพิ่ม Java 8 สตรีมสองรายการหรือองค์ประกอบพิเศษให้กับสตรีม


168

ฉันสามารถเพิ่มสตรีมหรือองค์ประกอบเพิ่มเติมเช่นนี้

Stream stream = Stream.concat(stream1, Stream.concat(stream2, Stream.of(element));

และฉันสามารถเพิ่มสิ่งใหม่ตามที่ฉันไปเช่นนี้

Stream stream = Stream.concat(
                       Stream.concat(
                              stream1.filter(x -> x!=0), stream2)
                              .filter(x -> x!=1),
                                  Stream.of(element))
                                  .filter(x -> x!=2);

แต่นี่น่าเกลียดเพราะconcatเป็นแบบคงที่ หากconcatเป็นวิธีการแบบอินสแตนซ์ตัวอย่างด้านบนจะง่ายต่อการอ่านมากขึ้น:

 Stream stream = stream1.concat(stream2).concat(element);

และ

 Stream stream = stream1
                 .filter(x -> x!=0)
                 .concat(stream2)
                 .filter(x -> x!=1)
                 .concat(element)
                 .filter(x -> x!=2);

คำถามของฉันคือ:

1) มีเหตุผลที่ดีใด ๆ ที่ทำให้concatเกิดไฟฟ้าสถิตย์? หรือมีวิธีอินสแตนซ์ที่เทียบเท่าที่ฉันหายไปไหม

2) ในกรณีใด ๆ มีวิธีที่ดีกว่าในการทำเช่นนี้?


4
ดูเหมือนว่ามันไม่ได้เป็นแบบนี้เสมอไป แต่ฉันก็ไม่สามารถหาสาเหตุได้
Edwin Dalorzo

คำตอบ:


126

หากคุณเพิ่มการนำเข้าแบบคงที่สำหรับStream.concatและStream.ofตัวอย่างแรกสามารถเขียนได้ดังนี้:

Stream<Foo> stream = concat(stream1, concat(stream2, of(element)));

การนำเข้าวิธีการคงที่ที่มีชื่อสามัญอาจส่งผลให้เกิดรหัสที่ยากต่อการอ่านและบำรุงรักษา ( มลภาวะของ namespace ) ดังนั้นอาจเป็นการดีกว่าที่จะสร้างวิธีการคงที่ของคุณเองด้วยชื่อที่มีความหมายมากกว่า อย่างไรก็ตามสำหรับการสาธิตฉันจะใช้ชื่อนี้

public static <T> Stream<T> concat(Stream<? extends T> lhs, Stream<? extends T> rhs) {
    return Stream.concat(lhs, rhs);
}
public static <T> Stream<T> concat(Stream<? extends T> lhs, T rhs) {
    return Stream.concat(lhs, Stream.of(rhs));
}

ด้วยวิธีการคงที่ทั้งสองนี้ (เป็นทางเลือกร่วมกับการนำเข้าแบบคงที่) ทั้งสองตัวอย่างสามารถเขียนได้ดังนี้:

Stream<Foo> stream = concat(stream1, concat(stream2, element));

Stream<Foo> stream = concat(
                         concat(stream1.filter(x -> x!=0), stream2).filter(x -> x!=1),
                         element)
                     .filter(x -> x!=2);

รหัสนี้สั้นลงอย่างมาก อย่างไรก็ตามฉันยอมรับว่าการอ่านไม่ได้ปรับปรุง ดังนั้นฉันมีทางออกอื่น


ในหลายสถานการณ์นักสะสมสามารถใช้เพื่อขยายการทำงานของสตรีม กับสองนักสะสมที่ด้านล่างทั้งสองตัวอย่างสามารถเขียนดังนี้

Stream<Foo> stream = stream1.collect(concat(stream2)).collect(concat(element));

Stream<Foo> stream = stream1
                     .filter(x -> x!=0)
                     .collect(concat(stream2))
                     .filter(x -> x!=1)
                     .collect(concat(element))
                     .filter(x -> x!=2);

ข้อแตกต่างระหว่างไวยากรณ์ที่คุณต้องการและไวยากรณ์ข้างต้นคือว่าคุณต้องเปลี่ยนconcat ( ... )ที่มีการเก็บรวบรวม (concat ( ... )) วิธีการคงที่ทั้งสองวิธีสามารถนำมาใช้ได้ดังนี้ (ใช้เป็นทางเลือกร่วมกับการนำเข้าแบบคงที่):

private static <T,A,R,S> Collector<T,?,S> combine(Collector<T,A,R> collector, Function<? super R, ? extends S> function) {
    return Collector.of(
        collector.supplier(),
        collector.accumulator(),
        collector.combiner(),
        collector.finisher().andThen(function));
}
public static <T> Collector<T,?,Stream<T>> concat(Stream<? extends T> other) {
    return combine(Collectors.toList(),
        list -> Stream.concat(list.stream(), other));
}
public static <T> Collector<T,?,Stream<T>> concat(T element) {
    return concat(Stream.of(element));
}

แน่นอนว่ามีข้อเสียเปรียบกับวิธีแก้ปัญหาที่ควรกล่าวถึงนี้ collectคือการดำเนินการขั้นสุดท้ายที่ใช้องค์ประกอบทั้งหมดของสตรีม ยิ่งไปกว่านั้นตัวรวบรวมconcatจะสร้างArrayListระดับกลางในแต่ละครั้งที่ถูกใช้ในเชน การดำเนินการทั้งสองอย่างมีผลกระทบอย่างมากต่อพฤติกรรมของโปรแกรมของคุณ อย่างไรก็ตามหากความสามารถในการอ่านสำคัญกว่าประสิทธิภาพอาจเป็นวิธีที่มีประโยชน์มาก


1
ฉันไม่พบconcatนักสะสมที่อ่านได้มาก ดูเหมือนจะแปลกที่มีวิธีการแบบคงที่พารามิเตอร์เดียวที่มีชื่อเช่นนี้และยังใช้collectสำหรับการต่อข้อมูล
Didier L

@ nosid อาจเป็นคำถามที่ตั้งฉากกับหัวข้อนี้เล็กน้อย แต่ทำไมคุณถึงอ้างสิทธิ์It's a bad idea to import static methods with names? ฉันสนใจอย่างแท้จริง - ฉันพบว่ามันทำให้รหัสกระชับและอ่านง่ายขึ้นและผู้คนจำนวนมากที่ฉันถามด้วยความคิดแบบเดียวกัน สนใจที่จะให้ตัวอย่างบางส่วนว่าทำไมถึงไม่ดี?
ควอนตัม

1
@Quantum: ความหมายของcompare(reverse(getType(42)), of(6 * 9).hashCode())อะไร? โปรดทราบว่าฉันไม่ได้บอกว่าการนำเข้าแบบคงที่เป็นแนวคิดที่ไม่ดี แต่การนำเข้าแบบคงที่สำหรับชื่อทั่วไปที่มีลักษณะเหมือนofและconcatเป็น
nosid

1
@nosid: จะไม่วางตัวเหนือรูปปั้นแต่ละตัวใน IDE สมัยใหม่เปิดเผยความหมายอย่างรวดเร็วหรือไม่? อย่างไรก็ตามฉันคิดว่านี่อาจเป็นคำสั่งการตั้งค่าส่วนบุคคลที่ดีที่สุดเนื่องจากฉันยังไม่เห็นเหตุผลทางเทคนิคว่าทำไมการนำเข้าชื่อ "ทั่วไป" คงที่ไม่ดี - ถ้าคุณใช้ Notepad หรือ VI (M) สำหรับการเขียนโปรแกรมในกรณีนี้ คุณมีปัญหาที่ใหญ่กว่า
ควอนตัม

ฉันจะไม่พูดว่า Scala SDK นั้นดีกว่า แต่ ... โอ๊ะฉันพูดแล้ว
eirirlar

165

น่าเสียดายที่คำตอบนี้อาจจะช่วยได้ไม่มากก็น้อย แต่ฉันได้ทำการวิเคราะห์ทางนิติเวชของรายการส่งจดหมาย Java Lambda เพื่อดูว่าฉันสามารถหาสาเหตุของการออกแบบนี้ได้หรือไม่ นี่คือสิ่งที่ฉันค้นพบ

ในการเริ่มต้นมีวิธีการอินสแตนซ์สำหรับ Stream.concat (สตรีม)

ในรายชื่อผู้รับจดหมายฉันเห็นได้อย่างชัดเจนว่าวิธีการเดิมถูกนำไปใช้เป็นวิธีการอินสแตนซ์ตามที่คุณสามารถอ่านในกระทู้นี้โดย Paul Sandoz เกี่ยวกับการดำเนินการ concat

ในนั้นพวกเขาหารือเกี่ยวกับปัญหาที่อาจเกิดขึ้นจากกรณีที่สตรีมอาจไม่มีที่สิ้นสุดและสิ่งที่เรียงต่อกันจะหมายถึงในกรณีเหล่านั้น แต่ฉันไม่คิดว่านั่นเป็นเหตุผลสำหรับการปรับเปลี่ยน

คุณเห็นในเธรดอื่นนี้ที่ผู้ใช้ JDK 8 บางรายก่อนถามเกี่ยวกับพฤติกรรมของวิธีการใช้อินสแตนซ์ concat เมื่อใช้กับอาร์กิวเมนต์ null

หัวข้ออื่น ๆนี้เผยให้เห็นว่าการออกแบบของวิธีการ concat อยู่ภายใต้การสนทนา

Refactored to Streams.concat (สตรีมสตรีม)

แต่โดยไม่มีคำอธิบายใด ๆ ทันใดนั้นวิธีการก็เปลี่ยนเป็นวิธีคงที่ดังที่คุณเห็นในหัวข้อนี้เกี่ยวกับการรวมกระแสข้อมูล นี่อาจเป็นเพียงเธรดเมลเดียวที่ให้แสงสว่างเล็กน้อยเกี่ยวกับการเปลี่ยนแปลงนี้ แต่ก็ไม่ชัดเจนพอที่ฉันจะกำหนดเหตุผลของการปรับโครงสร้างใหม่ แต่เราจะได้เห็นพวกเขาไม่กระทำในสิ่งที่พวกเขาแนะนำให้ย้ายconcatวิธีการออกจากและเข้าไปในระดับผู้ช่วยStreamStreams

Refactored to Stream.concat (สตรีมสตรีม)

ต่อมามันถูกย้ายอีกครั้งจากStreamsไปStreamยังอีกครั้ง แต่ไม่มีคำอธิบายสำหรับสิ่งนั้น

บรรทัดล่างเหตุผลสำหรับการออกแบบไม่ชัดเจนสำหรับฉันและฉันไม่สามารถหาคำอธิบายที่ดีได้ ฉันเดาว่าคุณยังคงสามารถถามคำถามในรายการส่งเมล์ได้

ทางเลือกบางอย่างสำหรับการต่อข้อมูลสตรีม

นี้หัวข้ออื่น ๆ โดยไมเคิลฮิกซ์สันกล่าวถึง / ถามเกี่ยวกับวิธีการอื่น ๆ ที่จะรวม / ลำธาร concat

  1. หากต้องการรวมสองสตรีมฉันควรทำสิ่งนี้:

    Stream.concat(s1, s2)

    ไม่ใช่สิ่งนี้:

    Stream.of(s1, s2).flatMap(x -> x)

    ... ใช่มั้ย

  2. ในการรวมสตรีมมากกว่าสองรายการฉันควรทำสิ่งนี้:

    Stream.of(s1, s2, s3, ...).flatMap(x -> x)

    ไม่ใช่สิ่งนี้:

    Stream.of(s1, s2, s3, ...).reduce(Stream.empty(), Stream::concat)

    ... ใช่มั้ย


6
+1 การวิจัยที่ดี และฉันจะใช้นี้เป็นสละ Stream.concat ฉัน varargs:public static <T> Stream<T> concat(Stream<T>... streams) { return Stream.of(streams).reduce(Stream.empty(), Stream::concat);}
MarcG

1
วันนี้ฉันเขียนเวอร์ชั่นของตัวเองและหลังจากนั้นฉันก็ให้ทุนหัวข้อนี้ ลายเซ็นแตกต่างกันเล็กน้อย แต่ต้องขอบคุณที่ทั่วไปมากขึ้น;) ตัวอย่างเช่นคุณสามารถรวม Stream <Integer> และ Stream <Double> กับ Stream <Number> @SafeVarargs private static <T> Stream<T> concat(Stream<? extends T>... streams) { return Stream.of(streams).reduce(Stream.empty(),Stream::concat).map(Function.identity());}
Kant

@kant ทำไมคุณต้องมีFunction.identity()แผนที่ ท้ายที่สุดแล้วมันจะส่งกลับอาร์กิวเมนต์ที่ได้รับ สิ่งนี้จะไม่มีผลใด ๆ ในสตรีมผลลัพธ์ ฉันพลาดอะไรไปรึเปล่า?
Edwin Dalorzo

1
คุณพยายามพิมพ์ใน IDE ของคุณหรือไม่ หากไม่มี. map (identity ()) คุณจะได้รับข้อผิดพลาดในการรวบรวม ฉันต้องการส่งคืนสตรีม <T> แต่มีคำสั่ง: return Stream.of(streams).reduce(Stream.empty(),Stream::concat)ส่งคืนสตรีม <? ขยาย T>. (Someting <T> เป็นประเภทย่อยของ Something <? extends T> ไม่ใช่วิธีอื่นดังนั้นจึงไม่สามารถร่ายได้) .map(identity())นักแสดงเพิ่มเติม<? ขยาย T> ถึง <T> มันเกิดขึ้นได้ด้วยการผสมผสานของ java 8 'target types' ของ method arguments และ return types และ Signature ของ map () method จริงๆแล้วมันคือ Function <T> identity ()
kant

1
@kant ฉันไม่เห็นจุดมากในการทำ? extends Tตั้งแต่คุณสามารถใช้การแปลงการจับภาพ ไม่ว่าจะด้วยวิธีใดก็ตามนี่คือข้อมูลโค้ดสรุปสาระสำคัญของฉันมาพูดคุยกันในเรื่องสรุปสาระสำคัญต่อไป
Edwin Dalorzo

12

ห้องสมุดStreamExของฉันขยายการทำงานของ Stream API โดยเฉพาะอย่างยิ่งที่จะนำเสนอวิธีการเช่นผนวกและย่อหน้าที่แก้ปัญหานี้ (ภายในพวกเขาใช้concat) วิธีการเหล่านี้สามารถยอมรับสตรีมหรือคอลเลกชันอื่นหรืออาร์เรย์ varargs การใช้ห้องสมุดของฉันปัญหาของคุณสามารถแก้ไขได้ด้วยวิธีนี้ (โปรดสังเกตว่าx != 0ดูแปลกสำหรับสตรีมที่ไม่ใช่ดั้งเดิม):

Stream<Integer> stream = StreamEx.of(stream1)
             .filter(x -> !x.equals(0))
             .append(stream2)
             .filter(x -> !x.equals(1))
             .append(element)
             .filter(x -> !x.equals(2));

ด้วยวิธีนี้ยังมีทางลัดสำหรับfilterการทำงานของคุณ:

Stream<Integer> stream = StreamEx.of(stream1).without(0)
                                 .append(stream2).without(1)
                                 .append(element).without(2);

9

แค่ทำ:

Stream.of(stream1, stream2, Stream.of(element)).flatMap(identity());

ที่เป็นการนำเข้าคงที่ของidentity()Function.identity()

การต่อหลายสตรีมเข้าในสตรีมเดียวจะเหมือนกับการสตรีมที่แบนราบ

อย่างไรก็ตามน่าเสียดายด้วยเหตุผลบางอย่างที่ไม่มีflatten()วิธีการStreamดังนั้นคุณต้องใช้flatMap()กับฟังก์ชันข้อมูลประจำตัว



1

หากคุณไม่สนใจการใช้cyclops-reactไลบรารีของบุคคลที่สามจะมีประเภทสตรีมเพิ่มเติมที่อนุญาตให้คุณทำเช่นนั้นผ่านตัวดำเนินการผนวก / เสริม

ค่าส่วนบุคคล, อาร์เรย์, iterables, สตรีมหรือปฏิกิริยารีสตรีมผู้เผยแพร่สามารถต่อท้ายและเสริมเป็นวิธีการอินสแตนซ์

Stream stream = ReactiveSeq.of(1,2)
                           .filter(x -> x!=0)
                           .append(ReactiveSeq.of(3,4))
                           .filter(x -> x!=1)
                           .append(5)
                           .filter(x -> x!=2);

[การเปิดเผยข้อมูลฉันเป็นผู้นำการพัฒนาไซคลอปตอบสนอง]


1

ในตอนท้ายของวันฉันไม่สนใจที่จะรวมสตรีม แต่ได้รับผลรวมจากการประมวลผลแต่ละองค์ประกอบของสตรีมเหล่านั้นทั้งหมด

ในขณะที่การรวมกระแสข้อมูลอาจพิสูจน์ว่ายุ่งยาก (เช่นเธรดนี้) การรวมผลลัพธ์การประมวลผลของพวกเขาค่อนข้างง่าย

กุญแจสำคัญในการแก้ไขคือการสร้างตัวสะสมของคุณเองและตรวจสอบให้แน่ใจว่าฟังก์ชั่นผู้จัดหาสำหรับตัวรวบรวมใหม่ส่งคืนคอลเลกชันเดียวกันทุกครั้ง ( ไม่ใช่ตัวใหม่ ) รหัสด้านล่างแสดงวิธีการนี้

package scratchpad;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collector;
import java.util.stream.Stream;

public class CombineStreams {
    public CombineStreams() {
        super();
    }

    public static void main(String[] args) {
        List<String> resultList = new ArrayList<>();
        Collector<String, List<String>, List<String>> collector = Collector.of(
                () -> resultList,
                (list, item) -> {
                    list.add(item);
                },
                (llist, rlist) -> {
                    llist.addAll(rlist);
                    return llist;
                }
        );
        String searchString = "Wil";

        System.out.println("After processing first stream\n"
                + createFirstStream().filter(name -> name.contains(searchString)).collect(collector));
        System.out.println();

        System.out.println("After processing second stream\n"
                + createSecondStream().filter(name -> name.contains(searchString)).collect(collector));
        System.out.println();

        System.out.println("After processing third stream\n"
                + createThirdStream().filter(name -> name.contains(searchString)).collect(collector));
        System.out.println();

    }

    private static Stream<String> createFirstStream() {
        return Arrays.asList(
                "William Shakespeare",
                "Emily Dickinson",
                "H. P. Lovecraft",
                "Arthur Conan Doyle",
                "Leo Tolstoy",
                "Edgar Allan Poe",
                "Robert Ervin Howard",
                "Rabindranath Tagore",
                "Rudyard Kipling",
                "Seneca",
                "John Donne",
                "Sarah Williams",
                "Oscar Wilde",
                "Catullus",
                "Alfred Tennyson",
                "William Blake",
                "Charles Dickens",
                "John Keats",
                "Theodor Herzl"
        ).stream();
    }

    private static Stream<String> createSecondStream() {
        return Arrays.asList(
                "Percy Bysshe Shelley",
                "Ernest Hemingway",
                "Barack Obama",
                "Anton Chekhov",
                "Henry Wadsworth Longfellow",
                "Arthur Schopenhauer",
                "Jacob De Haas",
                "George Gordon Byron",
                "Jack London",
                "Robert Frost",
                "Abraham Lincoln",
                "O. Henry",
                "Ovid",
                "Robert Louis Stevenson",
                "John Masefield",
                "James Joyce",
                "Clark Ashton Smith",
                "Aristotle",
                "William Wordsworth",
                "Jane Austen"
        ).stream();
    }

    private static Stream<String> createThirdStream() {
        return Arrays.asList(
                "Niccolò Machiavelli",
                "Lewis Carroll",
                "Robert Burns",
                "Edgar Rice Burroughs",
                "Plato",
                "John Milton",
                "Ralph Waldo Emerson",
                "Margaret Thatcher",
                "Sylvie d'Avigdor",
                "Marcus Tullius Cicero",
                "Banjo Paterson",
                "Woodrow Wilson",
                "Walt Whitman",
                "Theodore Roosevelt",
                "Agatha Christie",
                "Ambrose Bierce",
                "Nikola Tesla",
                "Franz Kafka"
        ).stream();
    }
}

0

วิธีการเกี่ยวกับการเขียนวิธี concat ของคุณเอง?

public static Stream<T> concat(Stream<? extends T> a, 
                               Stream<? extends T> b, 
                               Stream<? extends T> args)
{
    Stream<T> concatenated = Stream.concat(a, b);
    for (Stream<T> stream : args)
    {
        concatenated = Stream.concat(concatenated, stream);
    }
    return concatenated;
}

อย่างน้อยนี่ทำให้ตัวอย่างแรกของคุณอ่านได้ง่ายขึ้น


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