Czy można przesłać strumień w Javie 8?

160

Czy można przesyłać strumień w Javie 8? Powiedzmy, że mam listę obiektów, mogę zrobić coś takiego, aby odfiltrować wszystkie dodatkowe obiekty:

Stream.of(objects).filter(c -> c instanceof Client)

Po tym jednak, jeśli chcę coś zrobić z klientami, musiałbym obsadzić każdego z nich:

Stream.of(objects).filter(c -> c instanceof Client)
    .map(c -> ((Client) c).getID()).forEach(System.out::println);

To wygląda trochę brzydko. Czy można rzucić cały strumień na inny typ? Jak rzucać Stream<Object>do Stream<Client>?

Proszę zignorować fakt, że robienie takich rzeczy prawdopodobnie oznaczałoby zły projekt. Robimy takie rzeczy na moich zajęciach z informatyki, więc szukałem nowych funkcji java 8 i byłem ciekawy, czy to jest możliwe.

Fikcja
źródło
3
Z punktu widzenia środowiska wykonawczego Java oba typy strumieni są już takie same, więc rzutowanie nie jest wymagane. Sztuczka polega na tym, aby przemknąć go obok kompilatora. (To znaczy, zakładając, że ma to sens.)
Hot Licks

Odpowiedzi:

283

Nie sądzę, aby można było to zrobić po wyjęciu z pudełka. Prawdopodobnie czystszym rozwiązaniem byłoby:

Stream.of(objects)
    .filter(c -> c instanceof Client)
    .map(c -> (Client) c)
    .map(Client::getID)
    .forEach(System.out::println);

lub, jak sugerowano w komentarzach, możesz skorzystać z castmetody - ta pierwsza może być jednak łatwiejsza do odczytania:

Stream.of(objects)
    .filter(Client.class::isInstance)
    .map(Client.class::cast)
    .map(Client::getID)
    .forEach(System.out::println);
asylias
źródło
To jest dokładnie to, czego szukałem. Wydaje mi się, że przeoczyłem, że przesłanie go do klienta mapzwróciłoby plik Stream<Client>. Dzięki!
Fikcja,
+1 ciekawych nowych sposobów, chociaż ryzykują
wpadnięcie
@LordOfThePigs Tak, działa, chociaż nie jestem pewien, czy kod stanie się bardziej przejrzysty. Dodałem ten pomysł do mojej odpowiedzi.
assylias
38
Możesz „uprościć” filtr instanceOf za pomocą:Stream.of(objects).filter(Client.class::isInstance).[...]
Nicolas Labrot
Część bez strzały jest naprawdę piękna <3
Fabich
14

Zgodnie z odpowiedzią ggovana , robię to w następujący sposób:

/**
 * Provides various high-order functions.
 */
public final class F {
    /**
     * When the returned {@code Function} is passed as an argument to
     * {@link Stream#flatMap}, the result is a stream of instances of
     * {@code cls}.
     */
    public static <E> Function<Object, Stream<E>> instancesOf(Class<E> cls) {
        return o -> cls.isInstance(o)
                ? Stream.of(cls.cast(o))
                : Stream.empty();
    }
}

Korzystanie z tej funkcji pomocniczej:

Stream.of(objects).flatMap(F.instancesOf(Client.class))
        .map(Client::getId)
        .forEach(System.out::println);
Brandon
źródło
10

Spóźniłem się na imprezę, ale myślę, że to przydatna odpowiedź.

flatMap byłby to najkrótszy sposób.

Stream.of(objects).flatMap(o->(o instanceof Client)?Stream.of((Client)o):Stream.empty())

Jeśli ojest Clientto, utwórz Stream z jednym elementem, w przeciwnym razie użyj pustego strumienia. Te strumienie zostaną następnie spłaszczone do pliku Stream<Client>.

ggovan
źródło
Próbowałem to zaimplementować, ale otrzymałem ostrzeżenie, że moja klasa „używa operacji niesprawdzonych lub niebezpiecznych” - czy można się tego spodziewać?
aweibell
Niestety tak. Gdybyś używał if/elsezamiast ?:operatora, nie byłoby ostrzeżenia. Zapewniamy, że możesz bezpiecznie usunąć ostrzeżenie.
ggovan
3
Właściwie to dłużej niż Stream.of(objects).filter(o->o instanceof Client).map(o -> (Client)o)lub nawet Stream.of(objects).filter(Client.class::isInstance).map(Client.class::cast).
Didier L
4

To wygląda trochę brzydko. Czy można rzucić cały strumień na inny typ? Jak rzucać Stream<Object>do Stream<Client>?

Nie, to nie byłoby możliwe. Nie jest to nowość w Javie 8. Jest to specyficzne dla typów ogólnych. A List<Object>nie jest super typem List<String>, więc nie możesz po prostu rzucić a List<Object>na List<String>.

Podobnie jest w tym przypadku. Nie można rzucać Stream<Object>się Stream<Client>. Oczywiście możesz rzucić to pośrednio w ten sposób:

Stream<Client> intStream = (Stream<Client>) (Stream<?>)stream;

ale to nie jest bezpieczne i może zawieść w czasie wykonywania. Przyczyną tego jest to, że generyczne w Javie są implementowane przy użyciu wymazywania. Tak więc nie ma dostępnych informacji o typie, który typ Streamjest w czasie wykonywania. Wszystko jest sprawiedliwe Stream.

A tak przy okazji, co jest nie tak z twoim podejściem? Dla mnie wygląda dobrze.

Rohit Jain
źródło
2
@DR Generics C#jest implementowane przy użyciu reifikacji, podczas gdy w Javie jest zaimplementowane za pomocą erasure. Oba są realizowane w różny sposób. Nie możesz więc oczekiwać, że będzie działać tak samo w obu językach.
Rohit Jain
1
@DR Rozumiem, że wymazywanie stwarza wiele problemów dla początkujących, aby zrozumieć koncepcję generyków w Javie. A ponieważ nie używam C #, nie mogę wdawać się w szczegóły porównania. Jednak cała motywacja stojąca za wdrożeniem go w ten sposób IMO polegała na uniknięciu większych zmian we wdrażaniu JVM.
Rohit Jain
1
Dlaczego miałoby to „z pewnością zawieść w czasie wykonywania”? Jak mówisz, nie ma (ogólnych) informacji o typie, więc środowisko wykonawcze nie ma nic do sprawdzenia. Może to prawdopodobnie zawieść w czasie wykonywania, jeśli zostaną wprowadzone niewłaściwe typy, ale nie ma co do tego żadnej „pewności”.
Hot Licks
1
@RohitJain: Nie krytykuję ogólnej koncepcji Javy, ale ta jedna konsekwencja wciąż tworzy brzydki kod ;-)
DR
1
@DR - Generics Java są brzydkie od git-go. Głównie tylko C ++ zawiera pożądanie.
Hot Licks