Thêm hai luồng Java 8 hoặc một phần tử phụ vào luồng


168

Tôi có thể thêm luồng hoặc các yếu tố bổ sung, như thế này:

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

Và tôi có thể thêm những thứ mới khi tôi đi, như thế này:

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

Nhưng điều này là xấu xí, bởi vì concatlà tĩnh. Nếu concatlà một phương thức cá thể, các ví dụ trên sẽ dễ đọc hơn nhiều:

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

Câu hỏi của tôi là:

1) Có bất kỳ lý do tốt tại sao concatlà tĩnh? Hoặc có một số phương pháp ví dụ tương đương tôi đang thiếu?

2) Trong mọi trường hợp, có cách nào tốt hơn để làm điều này?


4
Có vẻ như mọi thứ không phải lúc nào cũng như thế này , nhưng tôi không thể tìm ra lý do tại sao.
Edwin Dalorzo 30/03/2016

Câu trả lời:


126

Nếu bạn thêm nhập khẩu tĩnh cho Stream.concatStream.of , ví dụ đầu tiên có thể được viết như sau:

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

Nhập phương thức tĩnh với tên chung có thể dẫn đến mã trở nên khó đọc và duy trì ( ô nhiễm không gian tên ). Vì vậy, có thể tốt hơn để tạo các phương thức tĩnh của riêng bạn với các tên có ý nghĩa hơn. Tuy nhiên, để trình diễn tôi sẽ gắn bó với cái tên này.

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

Với hai phương thức tĩnh này (tùy chọn kết hợp với nhập tĩnh), hai ví dụ có thể được viết như sau:

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

Mã bây giờ ngắn hơn đáng kể. Tuy nhiên, tôi đồng ý rằng khả năng đọc đã không được cải thiện. Vì vậy, tôi có một giải pháp khác.


Trong rất nhiều tình huống, Collector có thể được sử dụng để mở rộng chức năng của các luồng. Với hai Bộ sưu tập ở phía dưới, hai ví dụ có thể được viết như sau:

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

Sự khác biệt duy nhất giữa cú pháp mong muốn của bạn và cú pháp ở trên là, bạn phải thay thế concat (...) bằng collat ​​(concat (...)) . Hai phương thức tĩnh có thể được thực hiện như sau (tùy chọn được sử dụng kết hợp với nhập tĩnh):

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

Tất nhiên có một nhược điểm với giải pháp này cần được đề cập. thu thập là một hoạt động cuối cùng tiêu thụ tất cả các yếu tố của luồng. Trên hết, concat concat tạo một ArrayList trung gian mỗi khi nó được sử dụng trong chuỗi. Cả hai hoạt động có thể có tác động đáng kể đến hành vi của chương trình của bạn. Tuy nhiên, nếu dễ đọc quan trọng hơn hiệu suất , nó vẫn có thể là một cách tiếp cận rất hữu ích.


1
Tôi không tìm thấy người concatsưu tầm dễ đọc. Có vẻ lạ đối với một phương thức tĩnh tham số đơn có tên như thế này và cũng được sử dụng collectđể nối.
Didier L

@nosid, có lẽ là một câu hỏi hơi trực giao cho chủ đề này nhưng tại sao bạn lại yêu cầu It's a bad idea to import static methods with names? Tôi thực sự quan tâm - tôi thấy nó làm cho mã ngắn gọn và dễ đọc hơn và rất nhiều người tôi đã hỏi cũng nghĩ như vậy. Quan tâm để cung cấp một số ví dụ tại sao nói chung là xấu?
lượng tử

1
@Quantum: Ý nghĩa của nó là compare(reverse(getType(42)), of(6 * 9).hashCode())gì? Lưu ý rằng tôi đã không nói rằng nhập khẩu tĩnh là một ý tưởng tồi, nhưng nhập khẩu tĩnh cho các tên chung giống như ofconcatđược.
nosid

1
@nosid: Không di chuột qua từng trạng thái trong một IDE hiện đại có nhanh chóng tiết lộ ý nghĩa không? Ở mức độ nào, tôi nghĩ rằng đây có thể là một tuyên bố sở thích cá nhân tốt nhất, vì tôi vẫn không thấy lý do kỹ thuật nào tại sao nhập khẩu tĩnh cho tên "chung" là xấu - trừ khi bạn đang sử dụng Notepad hoặc VI (M) để lập trình trong trường hợp này bạn có vấn đề lớn hơn
lượng tử

Tôi sẽ không nói rằng Scala SDK tốt hơn, nhưng ... rất tiếc tôi đã nói điều đó.
eirirlar

165

Thật không may, câu trả lời này có lẽ ít hoặc không giúp được gì, nhưng tôi đã phân tích pháp y về danh sách Gửi thư Lambda của Java để xem liệu tôi có thể tìm ra nguyên nhân của thiết kế này không. Đây là những gì tôi tìm ra.

Ban đầu, có một phương thức ví dụ cho Stream.concat (Stream)

Trong danh sách gửi thư, tôi có thể thấy rõ phương thức ban đầu được triển khai như một phương thức cá thể, như bạn có thể đọc trong luồng này của Paul Sandoz, về hoạt động concat.

Trong đó, họ thảo luận về các vấn đề có thể phát sinh từ những trường hợp trong đó luồng có thể là vô hạn và sự kết hợp có nghĩa là gì trong những trường hợp đó, nhưng tôi không nghĩ đó là lý do để sửa đổi.

Bạn thấy trong luồng này , một số người dùng ban đầu của JDK 8 đã nghi ngờ về hành vi của phương thức cá thể concat khi được sử dụng với các đối số null.

Tuy nhiên, chủ đề khác này tiết lộ rằng thiết kế của phương pháp concat đang được thảo luận.

Được tái cấu trúc thành Streams.concat (Luồng, Luồng)

Nhưng không có bất kỳ lời giải thích nào, đột nhiên, các phương thức đã được thay đổi thành các phương thức tĩnh, như bạn có thể thấy trong luồng này về việc kết hợp các luồng . Đây có lẽ là chủ đề thư duy nhất làm sáng tỏ một chút về sự thay đổi này, nhưng tôi không đủ rõ ràng để xác định lý do cho việc tái cấu trúc. Nhưng chúng ta có thể thấy họ đã thực hiện một cam kết trong đó họ đề nghị chuyển concatphương thức ra khỏi Streamvà vào lớp người trợ giúpStreams .

Được tái cấu trúc thành Stream.concat (Luồng, Luồng)

Sau đó, nó đã được chuyển lại từ Streamsđến Stream, nhưng một lần nữa, không có lời giải thích cho điều đó.

Vì vậy, điểm mấu chốt, lý do cho thiết kế không hoàn toàn rõ ràng đối với tôi và tôi không thể tìm thấy một lời giải thích tốt. Tôi đoán bạn vẫn có thể đặt câu hỏi trong danh sách gửi thư.

Một số lựa chọn thay thế cho nối dòng

Chủ đề khác này của Michael Hixson thảo luận / hỏi về các cách khác để kết hợp / kết hợp các luồng

  1. Để kết hợp hai luồng, tôi nên làm điều này:

    Stream.concat(s1, s2)

    không phải cái này:

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

    ... đúng?

  2. Để kết hợp nhiều hơn hai luồng, tôi nên làm điều này:

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

    không phải cái này:

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

    ... đúng?


6
+1 Nghiên cứu hay. Và tôi sẽ sử dụng điều này như Stream.concat của tôi lấy các biến thể:public static <T> Stream<T> concat(Stream<T>... streams) { return Stream.of(streams).reduce(Stream.empty(), Stream::concat);}
MarcG

1
Hôm nay tôi đã viết phiên bản concat của riêng mình, và ngay sau đó tôi tài trợ cho chủ đề này. Chữ ký hơi khác một chút nhưng nhờ đó nó chung chung hơn;) ví dụ bạn có thể hợp nhất Luồng <Số nguyên> và Luồng <Đôi> thành Luồng <Số>. @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 Tại sao bạn cần Function.identity()bản đồ? Rốt cuộc, nó trả về cùng một đối số mà nó nhận được. Điều này sẽ không có hiệu lực trong luồng kết quả. Tui bỏ lỡ điều gì vậy?
Edwin Dalorzo

1
Bạn đã thử gõ nó vào IDE của bạn chưa? Nếu không có .map (danh tính ()), bạn sẽ gặp lỗi biên dịch. Tôi muốn trả về Luồng <T> nhưng câu lệnh: return Stream.of(streams).reduce(Stream.empty(),Stream::concat)trả về Luồng <? kéo dài T>. (Đôi khi <T> là kiểu con của Cái gì đó <? kéo dài T>, không phải theo cách khác, vì vậy nó không thể được chọn) .map(identity())Diễn viên bổ sung <? mở rộng T> đến <T>. Nó xảy ra nhờ sự pha trộn của java 8 'loại mục tiêu' của các đối số phương thức và các kiểu trả về và chữ ký của phương thức map (). Trên thực tế, đó là Hàm. <T> danh tính ().
kant

1
@kant Tôi không thấy nhiều điểm khi làm ? extends T, vì bạn có thể sử dụng chuyển đổi chụp . Bằng mọi giá, đây là đoạn mã chính của tôi. Hãy tiếp tục thảo luận trong Gist.
Edwin Dalorzo

12

Thư viện StreamEx của tôi mở rộng chức năng của Stream API. Đặc biệt nó cung cấp các phương thức như chắp thêm và trả trước để giải quyết vấn đề này (bên trong họ sử dụng concat). Các phương thức này có thể chấp nhận một luồng hoặc tập hợp hoặc mảng varargs khác. Sử dụng thư viện của tôi, vấn đề của bạn có thể được giải quyết theo cách này (lưu ý rằng x != 0trông lạ đối với luồng không nguyên thủy):

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

Nhân tiện, cũng có một lối tắt cho filterhoạt động của bạn :

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

9

Cứ làm đi:

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

nơi identity()là một nhập tĩnh củaFunction.identity() .

Ghép nhiều luồng vào một luồng cũng giống như làm phẳng một luồng.

Tuy nhiên, thật không may, vì một số lý do không có flatten()phương pháp trên Stream, vì vậy bạn phải sử dụng flatMap()với chức năng nhận dạng.



1

Nếu bạn không phiền khi sử dụng Thư viện bên thứ 3, cyclops-Reac có loại Luồng mở rộng sẽ cho phép bạn thực hiện điều đó thông qua các toán tử chắp thêm / trả trước.

Các giá trị riêng lẻ, mảng, lặp lại, Luồng hoặc luồng phản ứng Các nhà xuất bản có thể được thêm vào và thêm vào làm phương thức ví dụ.

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

[Tiết lộ Tôi là nhà phát triển chính của cyclops-Reac]


1

Vào cuối ngày, tôi không quan tâm đến việc kết hợp các luồng, nhưng trong việc thu được kết quả tổng hợp của việc xử lý từng phần tử của tất cả các luồng đó.

Mặc dù việc kết hợp các luồng có thể chứng tỏ là cồng kềnh (do đó là luồng này), việc kết hợp các kết quả xử lý của chúng khá dễ dàng.

Chìa khóa để giải quyết là tạo bộ sưu tập của riêng bạn và đảm bảo rằng chức năng nhà cung cấp cho bộ sưu tập mới trả về cùng một bộ sưu tập mỗi lần ( không phải bộ mới ), mã dưới đây minh họa cách tiếp cận này.

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

Làm thế nào về việc viết phương pháp concat của riêng bạn?

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

Điều này ít nhất làm cho ví dụ đầu tiên của bạn dễ đọc hơn rất nhiều.


1
Hãy thận trọng khi xây dựng các luồng từ nối liên tục. Truy cập một phần tử của luồng kết nối sâu có thể dẫn đến các chuỗi cuộc gọi sâu hoặc thậm chí StackOverflowError.
Legna
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.