¿Es posible emitir un Stream en Java 8?

160

¿Es posible emitir una transmisión en Java 8? Digamos que tengo una lista de objetos, puedo hacer algo como esto para filtrar todos los objetos adicionales:

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

Sin embargo, después de esto, si quiero hacer algo con los clientes, necesitaré lanzar cada uno de ellos:

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

Esto se ve un poco feo. ¿Es posible emitir una transmisión completa a un tipo diferente? ¿Como echar Stream<Object>a un Stream<Client>?

Ignore el hecho de que hacer cosas como esta probablemente significaría un mal diseño. Hacemos cosas como esta en mi clase de informática, así que estaba investigando las nuevas características de Java 8 y tenía curiosidad por saber si esto era posible.

Ficcion
fuente
3
Desde el punto de vista del tiempo de ejecución de Java, los dos tipos de Stream ya son iguales, por lo que no se requiere conversión. El truco es escabullirse del compilador. (Es decir, suponiendo que tenga sentido hacerlo).
Hot Licks

Respuestas:

283

No creo que haya una manera de hacer eso de forma inmediata. Una solución posiblemente más limpia sería:

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

o, como se sugiere en los comentarios, podría usar el castmétodo; sin embargo, el primero puede ser más fácil de leer:

Stream.of(objects)
    .filter(Client.class::isInstance)
    .map(Client.class::cast)
    .map(Client::getID)
    .forEach(System.out::println);
asilias
fuente
Esto es más o menos lo que estaba buscando. Supongo que pasé por alto que enviarlo al Cliente mapdevolvería a Stream<Client>. ¡Gracias!
Phiction
+1 nuevas formas interesantes, aunque corren el riesgo de entrar en el código de espagueti de un tipo de nueva generación (horizontal, no vertical)
robermann
@LordOfThePigs Sí, funciona, aunque no estoy seguro de si el código se aclara. He añadido la idea a mi respuesta.
Assylias
38
Puede "simplificar" la instancia del filtro con:Stream.of(objects).filter(Client.class::isInstance).[...]
Nicolas Labrot
La parte sin flecha es realmente hermosa <3
Fabich
14

En la línea de la respuesta de ggovan , hago esto de la siguiente manera:

/**
 * 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();
    }
}

Usando esta función auxiliar:

Stream.of(objects).flatMap(F.instancesOf(Client.class))
        .map(Client::getId)
        .forEach(System.out::println);
Brandon
fuente
10

Tarde a la fiesta, pero creo que es una respuesta útil.

flatMap sería la forma más corta de hacerlo.

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

Si oes un Client, cree una secuencia con un solo elemento; de lo contrario, utilice la secuencia vacía. Estas corrientes se aplanarán en a Stream<Client>.

ggovan
fuente
Traté de implementar esto, pero recibí una advertencia que decía que mi clase "usa operaciones no verificadas o inseguras". ¿Es eso de esperar?
aweibell
por desgracia sí. Si utilizara un operador en if/elselugar del ?:operador, no habría advertencia. Tenga la seguridad de que puede suprimir la advertencia de forma segura.
ggovan
3
En realidad, esto es más largo Stream.of(objects).filter(o->o instanceof Client).map(o -> (Client)o)o incluso más Stream.of(objects).filter(Client.class::isInstance).map(Client.class::cast).
Didier L
4

Esto se ve un poco feo. ¿Es posible emitir una transmisión completa a un tipo diferente? ¿Como echar Stream<Object>a un Stream<Client>?

No, eso no sería posible. Esto no es nuevo en Java 8. Esto es específico de los genéricos. A List<Object>no es un súper tipo de List<String>, por lo que no puedes simplemente lanzar un List<Object>a a List<String>.

Similar es el problema aquí. No puedes echar Stream<Object>a Stream<Client>. Por supuesto, puedes lanzarlo indirectamente de esta manera:

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

pero eso no es seguro y puede fallar en tiempo de ejecución. La razón subyacente de esto es que los genéricos en Java se implementan mediante borrado. Por lo tanto, no hay información de tipo disponible sobre qué tipo Streamestá en tiempo de ejecución. Todo es simplemente Stream.

Por cierto, ¿qué hay de malo en su enfoque? Luce bien para mi.

Rohit Jain
fuente
2
@DR Generics in C#se implementa mediante la reificación, mientras que en Java se implementa mediante el borrado. Ambos se implementan de manera diferente subyacente. Por lo tanto, no puede esperar que funcione de la misma manera en ambos idiomas.
Rohit Jain
1
@DR Entiendo que el borrado plantea muchos problemas para que los principiantes entiendan el concepto de genéricos en Java. Y como no uso C #, no puedo entrar en muchos detalles sobre la comparación. Pero toda la motivación para implementarlo de esta manera en la OMI fue evitar cambios importantes en la implementación de JVM.
Rohit Jain
1
¿Por qué "ciertamente fallará en tiempo de ejecución"? Como usted dice, no hay información de tipo (genérico), por lo que no hay nada que el tiempo de ejecución pueda verificar. Posiblemente podría fallar en tiempo de ejecución, si se alimentan los tipos incorrectos, pero no hay "certeza" sobre eso en absoluto.
Hot Licks
1
@RohitJain: No estoy criticando el concepto genérico de Java, pero esta única consecuencia todavía crea código feo ;-)
DR
1
@DR: los genéricos de Java son feos desde el git-go. Principalmente solo C ++ presenta lujuria.
Hot Licks