tutorial suma streams procesamiento parte libreria funcional ejemplo datos con java lambda java-8 java-stream

suma - Usando Java 8''s Opcional con Stream:: flatMap



stream java 8 ejemplo (9)

El nuevo marco de flujo de Java 8 y amigos crean un código Java muy conciso, pero me he topado con una situación aparentemente simple que es difícil de hacer de manera concisa.

Considere una List<Thing> things y método Optional<Other> resolve(Thing thing) . Quiero asignar las Thing a Optional<Other> y obtener el primer Other . La solución obvia sería usar things.stream().flatMap(this::resolve).findFirst() , pero flatMap requiere que devuelva una secuencia, y Optional no tiene un método stream() (o es un Collection o proporcionar un método para convertirlo o verlo como una Collection ).

Lo mejor que se me ocurre es esto:

things.stream() .map(this::resolve) .filter(Optional::isPresent) .map(Optional::get) .findFirst();

Pero eso parece terriblemente largo para lo que parece ser un caso muy común. Alguien tiene una mejor idea?


Java 9

Se ha agregado Optional.stream a JDK 9. Esto le permite hacer lo siguiente, sin la necesidad de ningún método de ayuda:

Optional<Other> result = things.stream() .map(this::resolve) .flatMap(Optional::stream) .findFirst();

Java 8

Sí, este fue un pequeño agujero en la API, en el sentido de que es un inconveniente convertir una Opcional en una secuencia de longitud cero o una. Podrías hacer esto:

Optional<Other> result = things.stream() .map(this::resolve) .flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty()) .findFirst();

Sin embargo, tener el operador ternario dentro del flatMap es un poco engorroso, por lo que sería mejor escribir una pequeña función auxiliar para hacer esto:

/** * Turns an Optional<T> into a Stream<T> of length zero or one depending upon * whether a value is present. */ static <T> Stream<T> streamopt(Optional<T> opt) { if (opt.isPresent()) return Stream.of(opt.get()); else return Stream.empty(); } Optional<Other> result = things.stream() .flatMap(t -> streamopt(resolve(t))) .findFirst();

Aquí, he insertado la llamada para resolver () en lugar de tener una operación de mapa separada (), pero esta es una cuestión de gusto.


Como mi respuesta anterior no parecía ser muy popular, voy a dar otra oportunidad.

Una respuesta corta:

En general estás en el camino correcto. El código más corto para llegar a su salida deseada podría ser esto:

things.stream() .map(this::resolve) .filter(Optional::isPresent) .findFirst() .flatMap( Function.identity() );

Esto se ajustará a todos sus requisitos:

  1. Encontrará la primera respuesta que se resuelve como no válida. Optional<Result>
  2. Llama this::resolve perezosamente según sea necesario
  3. this::resolve no se invocará después del primer resultado no vacío
  4. Devolverá Optional<Result>

Respuesta más larga

La única modificación en comparación con la versión inicial de OP fue que .map(Optional::get) antes de llamar a .findFirst() y agregué .flatMap(o -> o) como la última llamada de la cadena.

Esto tiene un buen efecto al eliminar el doble opcional, siempre que el flujo encuentre un resultado real.

Realmente no se puede ir más corto que esto en Java.

El fragmento de código alternativo que utiliza la técnica de bucle for más convencional va a tener el mismo número de líneas de código y tiene más o menos el mismo orden y número de operaciones que necesita realizar:

  1. Llamar a this.resolve . this.resolve ,
  2. filtrado basado en Optional.isPresent
  3. devolviendo el resultado y
  4. alguna forma de lidiar con el resultado negativo (cuando no se encontró nada)

Solo para demostrar que mi solución funciona según lo anunciado, escribí un pequeño programa de prueba:

public class { public static void main( String... args ) { try { final int integer = Stream.of( args ) .peek( s -> System.out.println( "Looking at " + s ) ) .map( ::resolve ) .filter( Optional::isPresent ) .findFirst() .flatMap( o -> o ) .orElseThrow( NoSuchElementException::new ) .intValue(); System.out.println( "First integer found is " + integer ); } catch ( NoSuchElementException e ) { System.out.println( "No integers provided!" ); } } private static Optional<Integer> resolve( String string ) { try { return Optional.of( Integer.valueOf( string ) ); } catch ( NumberFormatException e ) { System.out.println( ''"'' + string + ''"'' + " is not an integer"); return Optional.empty(); } } }

(Tiene pocas líneas adicionales para la depuración y verifica que solo se pueden resolver tantas llamadas como sea necesario ...)

Ejecutando esto en una línea de comando, obtuve los siguientes resultados:

$ java StackOferflow a b 3 c 4 Looking at a "a" is not an integer Looking at b "b" is not an integer Looking at 3 First integer found is 3


Estoy agregando esta segunda respuesta basada en una edición propuesta por el usuario srborlongan a mi otra respuesta . Creo que la técnica propuesta fue interesante, pero no fue realmente adecuada como una edición de mi respuesta. Otros estuvieron de acuerdo y la edición propuesta fue rechazada. (Yo no era uno de los votantes). Sin embargo, la técnica tiene mérito. Hubiera sido mejor si srborlongan hubiera publicado su propia respuesta. Esto no ha sucedido todavía, y no quería que la técnica se perdiera en las brumas del historial de edición rechazado de , así que decidí mostrarlo como una respuesta separada.

Básicamente, la técnica consiste en utilizar algunos de los métodos Optional de una manera inteligente para evitar tener que usar un operador ternario ( ? : O una instrucción if / else.

Mi ejemplo en línea sería reescrito de esta manera:

Optional<Other> result = things.stream() .map(this::resolve) .flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty)) .findFirst();

Un ejemplo mío que usa un método auxiliar se reescribirá de esta manera:

/** * Turns an Optional<T> into a Stream<T> of length zero or one depending upon * whether a value is present. */ static <T> Stream<T> streamopt(Optional<T> opt) { return opt.map(Stream::of) .orElseGet(Stream::empty); } Optional<Other> result = things.stream() .flatMap(t -> streamopt(resolve(t))) .findFirst();

COMENTARIO

Comparemos las versiones originales versus las modificadas directamente:

// original .flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty()) // modified .flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))

El original es un enfoque directo y sencillo: obtenemos un Optional<Other> ; si tiene un valor, devolvemos una secuencia que contiene ese valor, y si no tiene ningún valor, devolvemos una secuencia vacía. Bastante simple y fácil de explicar.

La modificación es inteligente y tiene la ventaja de que evita condicionales. (Sé que a algunas personas no les gusta el operador ternario. Si se usa incorrectamente, puede hacer que el código sea difícil de entender). Sin embargo, a veces las cosas pueden ser demasiado ingeniosas. El código modificado también comienza con un Optional<Other> . Luego llama a Optional.map que se define de la siguiente manera:

Si hay un valor presente, aplique la función de mapeo proporcionada a él, y si el resultado es no nulo, devuelva un Opcional que describa el resultado. De lo contrario, devuelva un Opcional vacío.

La llamada de map(Stream::of) devuelve un Optional<Stream<Other>> . Si un valor estaba presente en la entrada Opcional, el Opcional devuelto contiene un Flujo que contiene el único Otro resultado. Pero si el valor no estaba presente, el resultado es un Opcional vacío.

A continuación, la llamada a orElseGet(Stream::empty) devuelve un valor de tipo Stream<Other> . Si su valor de entrada está presente, obtiene el valor, que es el elemento único Stream<Other> . De lo contrario (si el valor de entrada está ausente), devuelve un Stream<Other> vacío. Entonces el resultado es correcto, igual que el código condicional original.

En los comentarios que discutieron sobre mi respuesta, con respecto a la edición rechazada, describí esta técnica como "más conciso pero también más oscuro". Estoy de acuerdo con esto. Me tomó un tiempo averiguar qué estaba haciendo, y también me tomó un tiempo escribir la descripción anterior de lo que estaba haciendo. La sutileza clave es la transformación de Optional<Other> a Optional<Stream<Other>> . Una vez que asimilas esto tiene sentido, pero no fue obvio para mí.

Reconozco, sin embargo, que las cosas que inicialmente son oscuras pueden volverse idiomáticas con el tiempo. Es posible que esta técnica termine siendo la mejor en la práctica, al menos hasta que se agregue Optional.stream (si es que alguna vez lo hace).

ACTUALIZACIÓN: Optional.stream se ha agregado a JDK 9.


Lo más probable es que lo estés haciendo mal.

Java 8 Opcional no debe usarse de esta manera. Por lo general, solo está reservado para operaciones de flujo de terminal que pueden o no devolver un valor, como encontrar, por ejemplo.

En su caso, sería mejor intentar primero encontrar una forma económica de filtrar los elementos que se pueden resolver y luego obtener el primer elemento como opcional y resolverlo como una última operación. Mejor aún: en lugar de filtrar, encuentre el primer elemento que se pueda resolver y resuélvalo.

things.filter(Thing::isResolvable) .findFirst() .flatMap(this::resolve) .get();

La regla general es que debe esforzarse por reducir el número de elementos en la secuencia antes de transformarlos en otra cosa. YMMV por supuesto.


No puedes hacerlo de manera más concisa como lo estás haciendo.

Usted afirma que no desea .filter(Optional::isPresent) y .map(Optional::get) .

Esto se ha resuelto con el método que describe @StuartMarks, sin embargo, como resultado, ahora lo asigna a una Optional<T> , por lo que ahora necesita usar .flatMap(this::streamopt) y get() al final.

¡Entonces todavía consiste en dos declaraciones y ahora puedes obtener excepciones con el nuevo método! Porque, ¿qué pasa si cada opción opcional está vacía? ¡Entonces findFirst() devolverá una opción vacía y tu get() fallará!

Entonces, ¿qué tienes?

things.stream() .map(this::resolve) .filter(Optional::isPresent) .map(Optional::get) .findFirst();

es en realidad la mejor manera de lograr lo que desea, y es que desea guardar el resultado como una T , no como una Optional<T> .

Me tomé la libertad de crear una CustomOptional<T> que envuelve el Optional<T> y proporciona un método extra, flatStream() . Tenga en cuenta que no puede extender Optional<T> :

class CustomOptional<T> { private final Optional<T> optional; private CustomOptional() { this.optional = Optional.empty(); } private CustomOptional(final T value) { this.optional = Optional.of(value); } private CustomOptional(final Optional<T> optional) { this.optional = optional; } public Optional<T> getOptional() { return optional; } public static <T> CustomOptional<T> empty() { return new CustomOptional<>(); } public static <T> CustomOptional<T> of(final T value) { return new CustomOptional<>(value); } public static <T> CustomOptional<T> ofNullable(final T value) { return (value == null) ? empty() : of(value); } public T get() { return optional.get(); } public boolean isPresent() { return optional.isPresent(); } public void ifPresent(final Consumer<? super T> consumer) { optional.ifPresent(consumer); } public CustomOptional<T> filter(final Predicate<? super T> predicate) { return new CustomOptional<>(optional.filter(predicate)); } public <U> CustomOptional<U> map(final Function<? super T, ? extends U> mapper) { return new CustomOptional<>(optional.map(mapper)); } public <U> CustomOptional<U> flatMap(final Function<? super T, ? extends CustomOptional<U>> mapper) { return new CustomOptional<>(optional.flatMap(mapper.andThen(cu -> cu.getOptional()))); } public T orElse(final T other) { return optional.orElse(other); } public T orElseGet(final Supplier<? extends T> other) { return optional.orElseGet(other); } public <X extends Throwable> T orElseThrow(final Supplier<? extends X> exceptionSuppier) throws X { return optional.orElseThrow(exceptionSuppier); } public Stream<T> flatStream() { if (!optional.isPresent()) { return Stream.empty(); } return Stream.of(get()); } public T getTOrNull() { if (!optional.isPresent()) { return null; } return get(); } @Override public boolean equals(final Object obj) { return optional.equals(obj); } @Override public int hashCode() { return optional.hashCode(); } @Override public String toString() { return optional.toString(); } }

Verás que agregué flatStream() , como aquí:

public Stream<T> flatStream() { if (!optional.isPresent()) { return Stream.empty(); } return Stream.of(get()); }

Usado como:

String result = Stream.of("a", "b", "c", "de", "fg", "hij") .map(this::resolve) .flatMap(CustomOptional::flatStream) .findFirst() .get();

Aún tendrá que devolver un Stream<T> aquí, ya que no puede devolver T , porque if !optional.isPresent() , entonces T == null si lo declara así, pero entonces su .flatMap(CustomOptional::flatStream) intentaría agregar null a una secuencia y eso no es posible.

Como ejemplo:

public T getTOrNull() { if (!optional.isPresent()) { return null; } return get(); }

Usado como:

String result = Stream.of("a", "b", "c", "de", "fg", "hij") .map(this::resolve) .map(CustomOptional::getTOrNull) .findFirst() .get();

Ahora lanzará una NullPointerException dentro de las operaciones de la secuencia.

Conclusión

El método que usaste, en realidad es el mejor método.


Null es compatible con Stream proporcionado por My library AbacusUtil . Aquí está el código:

Stream.of(things).map(e -> resolve(e).orNull()).skipNull().first();


Si no te importa utilizar una biblioteca de terceros, puedes utilizar Javaslang . Es como Scala, pero implementado en Java.

Viene con una biblioteca de colecciones inmutables completa que es muy similar a la conocida de Scala. Estas colecciones reemplazan las colecciones de Java y Stream de Java 8. También tiene su propia implementación de Opción.

import javaslang.collection.Stream; import javaslang.control.Option; Stream<Option<String>> options = Stream.of(Option.some("foo"), Option.none(), Option.some("bar")); // = Stream("foo", "bar") Stream<String> strings = options.flatMap(o -> o);

Aquí hay una solución para el ejemplo de la pregunta inicial:

import javaslang.collection.Stream; import javaslang.control.Option; public class Test { void run() { // = Stream(Thing(1), Thing(2), Thing(3)) Stream<Thing> things = Stream.of(new Thing(1), new Thing(2), new Thing(3)); // = Some(Other(2)) Option<Other> others = things.flatMap(this::resolve).headOption(); } Option<Other> resolve(Thing thing) { Other other = (thing.i % 2 == 0) ? new Other(i + "") : null; return Option.of(other); } } class Thing { final int i; Thing(int i) { this.i = i; } public String toString() { return "Thing(" + i + ")"; } } class Other { final String s; Other(String s) { this.s = s; } public String toString() { return "Other(" + s + ")"; } }

Descargo de responsabilidad: soy el creador de Javaslang.


Tarde a la fiesta, pero ¿qué pasa?

things.stream() .map(this::resolve) .filter(Optional::isPresent) .findFirst().get();

Puede deshacerse de la última get () si crea un método util para convertir de forma opcional a la transmisión manual:

things.stream() .map(this::resolve) .flatMap(Util::optionalToStream) .findFirst();

Si devuelve la transmisión directamente desde su función de resolución, guarda una línea más.


Una versión ligeramente más corta que usa reduce :

things.stream() .map(this::resolve) .reduce(Optional.empty(), (a, b) -> a.isPresent() ? a : b );

También puede mover la función de reducción a un método de utilidad estático y luego se convierte en:

.reduce(Optional.empty(), Util::firstPresent );