Bir akışa iki Java 8 akışı veya fazladan bir öğe ekleme


168

Akışları veya ekstra öğeleri ekleyebilirim, şöyle:

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

Ve devam ederken yeni şeyler ekleyebilirim, şöyle:

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

Ama bu çirkin, çünkü concatstatik. Bir concatörnek yöntemi olsaydı, yukarıdaki örnekleri okumak çok daha kolay olurdu:

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

Ve

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

Sorum şu:

1) concatStatik olmasının iyi bir nedeni var mı ? Yoksa eksik eşdeğer bir örnek yöntemi var mı?

2) Her durumda, bunu yapmanın daha iyi bir yolu var mı?


4
Görünüşe göre şey her zaman böyle değildi , ama nedenini bulamıyorum.
Edwin Dalorzo

Yanıtlar:


126

Eğer eklerseniz statik ithalatı için Stream.concat ve Stream.of aşağıdaki gibi ilk örnek yazılabilir:

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

Genel adlarla statik yöntemlerin içe aktarılması, okunması ve bakımı zorlaşan kodlarla sonuçlanabilir ( ad alanı kirliliği ). Bu nedenle, daha anlamlı adlarla kendi statik yöntemlerinizi oluşturmak daha iyi olabilir . Ancak, gösteri için bu isme sadık kalacağım.

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

Bu iki statik yöntemle (isteğe bağlı olarak statik içe aktarmalarla birlikte), iki örnek aşağıdaki gibi yazılabilir:

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

Kod şimdi önemli ölçüde daha kısadır. Ancak, okunabilirliğin iyileşmediğini kabul ediyorum. Başka bir çözüm daha var.


Birçok durumda, Toplayıcılar akışların işlevselliğini genişletmek için kullanılabilir . Alttaki iki Toplayıcı ile, iki örnek aşağıdaki gibi yazılabilir:

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

İstediğiniz sözdizimi ve yukarıdaki sözdizimi arasındaki tek fark değiştirmek zorunda yani, (...) concat ile toplamak (concat (...)) . İki statik yöntem aşağıdaki gibi uygulanabilir (isteğe bağlı olarak statik içe aktarmalarla birlikte kullanılır):

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

Tabii ki bu çözümle birlikte belirtilmesi gereken bir dezavantaj var. toplama , akışın tüm öğelerini tüketen son bir işlemdir. Bunun üzerine, toplayıcı concat zincirde her kullanıldığında bir ara ArrayList oluşturur . Her iki işlemin de programınızın davranışı üzerinde önemli bir etkisi olabilir. Ancak, okunabilirlik performanstan daha önemliyse , yine de çok yararlı bir yaklaşım olabilir.


1
concatToplayıcıyı çok okunabilir bulmuyorum . Bunun gibi tek parametreli bir statik yönteme sahip olmak ve collectbirleştirme için kullanmak garip görünüyor .
Didier L

@nosid, belki de bu konuya biraz dik bir soru ama neden iddia It's a bad idea to import static methods with namesediyorsunuz? Gerçekten ilgileniyorum - kodun daha özlü ve okunaklı olduğunu ve sorduğum birçok insanın aynı şekilde düşündüğünü görüyorum. Bunun genel olarak neden kötü olduğuna dair bazı örnekler vermek ister misiniz?
kuantum

1
@Quantum: Anlamı nedir compare(reverse(getType(42)), of(6 * 9).hashCode())? Ben demedim o Not statik ithalatı kötü bir fikir, ama statik ithalat gibi jenerik isimler için ofve concatvardır.
nosid

1
@nosid: Modern bir IDE'deki her bir ifadenin üzerine gelmek anlamını hızlıca ortaya çıkarmayacak mı? Her halükarda, "jenerik" adlar için statik içe aktarma işleminin neden kötü olduğuna dair teknik bir neden göremediğim için, bu en iyi ihtimalle kişisel tercih bildirimi olabilir - bu durumda programlama için Not Defteri veya VI (M) kullanmadığınız sürece daha büyük problemlerin var.
kuantum

Scala SDK'nın daha iyi olduğunu söylemeyeceğim, ama ... ayy dedim.
eirirlar

165

Ne yazık ki bu cevap muhtemelen çok az ya da hiç yardımcı değil, ancak bu tasarımın nedenini bulabileceğimi görmek için Java Lambda Posta listesinin bir adli analizi yaptım. Ben de öyle buldum.

Başlangıçta Stream.concat (Stream) için bir örnek yöntemi vardı

Posta listesinde, metodun aslında bir örnek metot olarak uygulandığını açıkça görebiliyorum, Paul Sandoz'un bu konudaki concat işlemi hakkında okuyabileceğiniz gibi .

İçinde, akışın sonsuz olabileceği durumlardan ve bu durumlarda birleştirmenin ne anlama gelebileceğinden kaynaklanabilecek konuları tartışırlar, ancak değişikliğin sebebinin bu olduğunu düşünmüyorum.

Bu diğer iş parçacığında , JDK 8'in bazı eski kullanıcılarının boş argümanlarla kullanıldığında concat örneği yönteminin davranışı hakkında sorgulama yaptığını görürsünüz .

Ancak bu diğer evre , concat yönteminin tasarımının tartışıldığını ortaya koymaktadır.

Streams.concat'e yeniden aktarıldı (Akış, Akış)

Ancak, herhangi bir açıklama yapılmadan, aniden, bu dizide akışları birleştirme hakkında gördüğünüz gibi yöntemler statik yöntemlere değiştirildi . Bu belki de bu değişiklik hakkında biraz ışık tutan tek posta iş parçacığıdır, ancak yeniden düzenleme nedenini belirlemem için yeterince açık değildi. Ancak , yöntemi yardımcı sınıfın dışına ve sınıfına taşımayı önerdikleri bir taahhütte bulunduklarını görebiliriz .concatStreamStreams

Stream.concat'e (Akış, Akış) yeniden düzenlendi

Daha sonra tekrar taşındı gelen Streamsetmek Stream, ama bunun için yine, hiçbir açıklama.

Yani, sonuçta, tasarımın nedeni benim için tamamen açık değil ve iyi bir açıklama bulamadım. Sanırım yine de posta listesindeki soruyu sorabilirsiniz.

Akış Birleştirme İçin Bazı Alternatifler

Michael Hixson tarafından yazılan bu diğer konu, akışları birleştirmenin / birleştirmenin diğer yollarını tartışıyor / soruyor

  1. İki akışı birleştirmek için şunu yapmalıyım:

    Stream.concat(s1, s2)

    bu değil:

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

    ... sağ?

  2. İkiden fazla akışı birleştirmek için şunu yapmalıyım:

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

    bu değil:

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

    ... sağ?


6
+1 Güzel araştırma. Ve bunu Stream.concat varargs alarak kullanacağım:public static <T> Stream<T> concat(Stream<T>... streams) { return Stream.of(streams).reduce(Stream.empty(), Stream::concat);}
MarcG

1
Bugün kendi concat versiyonumu yazdım ve hemen sonra bu konuyu finanse ediyorum. İmza biraz farklıdır, ancak daha genel olduğu için;) örneğin <Integer> Akışı ve <Double> Akışı <Number> Akışı ile birleştirebilirsiniz. @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 Neden bir Function.identity()haritaya ihtiyacınız var ? Sonuçta, aldığı argümanı döndürür. Bunun sonuçta elde edilen akış üzerinde hiçbir etkisi olmamalıdır. Bir şey mi kaçırıyorum?
Edwin Dalorzo

1
IDE'nize yazmaya çalıştınız mı? .Map (identity ()) olmadan derleme hatası alırsınız. Ben Akışı <T> dönmek istiyorum ama deyim: return Stream.of(streams).reduce(Stream.empty(),Stream::concat)Akış <döndürür? (Bir şey <T>, bir şey <? T> 'yi genişletir, diğer şekilde değil, bu nedenle yayınlanamaz) Ek .map(identity())döküm <? T> 'yi <T>' ye genişletir. Java 8 'hedef türleri' yöntem argümanlarının ve dönüş türlerinin ve map () yönteminin imzasının karışımı sayesinde olur. Aslında bu Fonksiyon. <T> kimlik ().
kant

1
@kant Yakalama dönüşümünü? extends T kullanabildiğiniz için bu konuda fazla bir şey göremiyorum . Her halükarda, benim öz kod kod snippet'im Gist'teki tartışmaya devam edelim.
Edwin Dalorzo

12

Benim StreamEx kütüphane Akış API işlevselliğini uzanır. Özellikle, bu sorunu çözen (dahili olarak kullandıkları ) ekleme ve önleme gibi yöntemler sunar concat. Bu yöntemler başka bir akışı veya koleksiyonu veya varargs dizisini kabul edebilir. Kütüphanemi kullanarak sorununuzu şu şekilde çözebilirsiniz ( x != 0ilkel olmayan akış için garip görünüyor):

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

Bu arada filteroperasyonunuz için bir kısayol da var :

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

9

Sadece yap:

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

identity()statik bir içe aktarma nerede Function.identity().

Birden fazla akışı bir akışta birleştirmek, bir akışı düzleştirmekle aynıdır.

Ancak, ne yazık ki, bazı nedenlerden dolayı bir flatten()yöntem yoktur Stream, bu yüzden flatMap()kimlik işleviyle birlikte kullanmanız gerekir .



1

Üçüncü Taraf Kütüphanelerini kullanmanın bir sakıncası yoksa cyclops-tepki , ekleme / önleme işleçleri aracılığıyla bunu yapmanızı sağlayacak genişletilmiş bir Akış türüne sahiptir.

Bireysel değerler, diziler, yinelenebilirler, Akışlar veya reaktif akışlar Yayıncılar örnek yöntemler olarak eklenebilir ve eklenebilir.

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

[Açıklama Ben cyclops-tepki reaksiyonu geliştiricisiyim]


1

Günün sonunda akışları birleştirmekle değil, tüm bu akışların her bir öğesini işlemenin birleşik sonucunu elde etmekle ilgileniyorum.

Akışları birleştirmek hantal (bu nedenle bu iş parçacığı) olabilirken, işlem sonuçlarını birleştirmek oldukça kolaydır.

Çözülmesi gereken anahtar, kendi koleksiyoncunuzu oluşturmak ve yeni koleksiyoncunun tedarikçi fonksiyonunun her seferinde ( yeni değil) aynı koleksiyonu döndürmesini sağlamaktır, aşağıdaki kod bu yaklaşımı göstermektedir.

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

Kendi concat yönteminizi yazmaya ne dersiniz?

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

Bu en azından ilk örneğinizi çok daha okunabilir hale getirir.


1
Tekrarlanan birleştirme işleminden akışlar oluştururken dikkatli olun. Derin bir şekilde birleştirilmiş bir akışın elemanına erişmek, derin çağrı zincirlerine ve hatta StackOverflowError'a neden olabilir.
Legna
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.