El nuevo marco de transmisión Java 8 y sus amigos crean un código Java muy conciso, pero me he encontrado con una situación aparentemente simple que es difícil de hacer de manera concisa.
Considere un List<Thing> things
y método Optional<Other> resolve(Thing thing)
. Quiero mapear los Thing
s a Optional<Other>
s y obtener el primero 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 stream()
método (o es Collection
o proporciona un método para convertirlo o verlo como a 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 un caso muy común. Alguien tiene una mejor idea?
java
lambda
java-8
java-stream
Yona Appletree
fuente
fuente
.flatMap(Optional::toStream)
, con su versión realmente ve lo que está sucediendo.Optional.stream
existe en JDK 9 ...Respuestas:
Java 9
Optional.stream
se ha agregado a JDK 9. Esto le permite hacer lo siguiente, sin la necesidad de ningún método auxiliar:Java 8
Sí, este fue un pequeño agujero en la API, ya que es un poco incómodo convertir un
Optional<T>
en una longitud de cero o unoStream<T>
. Podrías hacer esto:Sin
flatMap
embargo, tener el operador ternario dentro es un poco engorroso, por lo que podría ser mejor escribir una pequeña función auxiliar para hacer esto:Aquí, he incluido la llamada en
resolve()
lugar de tener unamap()
operación separada , pero esto es cuestión de gustos.fuente
static <T> Stream<T> streamopt(Optional<T> opt) { return opt.map(Stream::of).orElse(Stream.empty()); }
Optional
sobrecarga aStream#flatMap
... de esa manera podrías escribirstream().flatMap(this::resolve)
Optional.stream()
.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 aún no ha sucedido, y no quería que la técnica se perdiera en las brumas del historial de edición rechazado de StackOverflow, así que decidí sacarlo a la superficie como una respuesta separada.
Básicamente, la técnica es usar algunos de los
Optional
métodos de una manera inteligente para evitar tener que usar un operador ternario (? :
) o una instrucción if / else.Mi ejemplo en línea se volvería a escribir de esta manera:
Un mi ejemplo que usa un método auxiliar se reescribirá de esta manera:
COMENTARIO
Comparemos las versiones originales y modificadas directamente:
El original es un enfoque directo, aunque profesional: obtenemos un
Optional<Other>
; si tiene un valor, devolvemos una secuencia que contiene ese valor, y si no tiene 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 los 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 inteligentes. El código modificado también comienza con un
Optional<Other>
. Luego llama a loOptional.map
que se define de la siguiente manera:La
map(Stream::of)
llamada devuelve unOptional<Stream<Other>>
. Si un valor estaba presente en la entrada Opcional, el Opcional devuelto contiene una Corriente que contiene el único resultado Otro. 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 tipoStream<Other>
. Si su valor de entrada está presente, obtiene el valor, que es el elemento únicoStream<Other>
. De lo contrario (si el valor de entrada está ausente), devuelve un vacíoStream<Other>
. 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 concisa pero también más oscura". Estoy de acuerdo con esto. Me llevó un tiempo descubrir qué estaba haciendo, y también me llevó un tiempo escribir la descripción anterior de lo que estaba haciendo. La sutileza clave es la transformación de
Optional<Other>
aOptional<Stream<Other>>
. Una vez que entiendes esto tiene sentido, pero no era obvio para mí.Sin embargo, reconoceré que las cosas que inicialmente son oscuras pueden volverse idiomáticas con el tiempo. Puede ser que esta técnica termine siendo la mejor forma en la práctica, al menos hasta que
Optional.stream
se agregue (si alguna vez lo hace).ACTUALIZACIÓN:
Optional.stream
se ha agregado a JDK 9.fuente
No puedes hacerlo más conciso como ya lo estás haciendo.
Afirmas que no quieres
.filter(Optional::isPresent)
y.map(Optional::get)
.Esto se ha resuelto mediante el método que describe @StuartMarks, sin embargo, como resultado, ahora lo asigna a un
Optional<T>
, por lo que ahora debe usar.flatMap(this::streamopt)
yget()
al final.Por lo tanto, todavía consta de dos declaraciones y ahora puede obtener excepciones con el nuevo método. Porque, ¿qué pasa si cada opcional está vacío? ¡Entonces
findFirst()
devolverá un opcional vacío yget()
fallará!Entonces lo que tienes:
es en realidad la mejor manera de lograr lo que quieres, y es que quieres guardar el resultado como un
T
, no como unOptional<T>
.Me tomé la libertad de crear una
CustomOptional<T>
clase que envuelve elOptional<T>
y proporciona un método adicional,flatStream()
. Tenga en cuenta que no puede extenderOptional<T>
:Verás que agregué
flatStream()
, como aquí:Usado como:
Usted todavía tendrá que devolver una
Stream<T>
aquí, ya que no puede volverT
, porque si!optional.isPresent()
, a continuación,T == null
si se declara que tales, pero entonces su.flatMap(CustomOptional::flatStream)
intentaría añadirnull
a una corriente y que no es posible.Como ejemplo:
Usado como:
Ahora lanzará un
NullPointerException
dentro de las operaciones de transmisión.Conclusión
El método que usó, en realidad es el mejor método.
fuente
Una versión ligeramente más corta que usa
reduce
:También puede mover la función de reducción a un método de utilidad estática y luego se convierte en:
fuente
Como mi respuesta anterior parecía no ser muy popular, le daré otra oportunidad.
Una respuesta corta:
Usted está principalmente en el camino correcto. El código más corto para llegar a la salida deseada que se me ocurre es este:
Esto se ajustará a todos sus requisitos:
Optional<Result>
this::resolve
perezosamente según sea necesariothis::resolve
no se llamará después del primer resultado no vacíoOptional<Result>
Respuesta más larga
La única modificación en comparación con la versión inicial de OP fue que la eliminé
.map(Optional::get)
antes de llamar.findFirst()
y la agregué.flatMap(o -> o)
como la última llamada de la cadena.Esto tiene un buen efecto al deshacerse del doble Opcional, siempre que la secuencia encuentra un resultado real.
Realmente no puedes ir más corto que esto en Java.
El fragmento de código alternativo que usa la
for
técnica de bucle más convencional será aproximadamente el mismo número de líneas de código y tendrá más o menos el mismo orden y número de operaciones que debe realizar:this.resolve
,Optional.isPresent
Solo para demostrar que mi solución funciona como se anuncia, escribí un pequeño programa de prueba:
(Tiene pocas líneas adicionales para depurar y verificar que solo se resuelvan tantas llamadas como sea necesario ...)
Al ejecutar esto en una línea de comando, obtuve los siguientes resultados:
fuente
Si no le importa usar una biblioteca de terceros, puede usar Javaslang . Es como Scala, pero implementado en Java.
Viene con una biblioteca de colección inmutable completa que es muy similar a la conocida por Scala. Estas colecciones reemplazan las colecciones de Java y el Stream de Java 8. También tiene su propia implementación de Opción.
Aquí hay una solución para el ejemplo de la pregunta inicial:
Descargo de responsabilidad: soy el creador de Javaslang.
fuente
Tarde a la fiesta, pero ¿qué pasa con
Puede deshacerse del último get () si crea un método util para convertir opcional para transmitir manualmente:
Si devuelve la secuencia directamente desde su función de resolución, guarda una línea más.
fuente
Me gustaría promover métodos de fábrica para crear ayudantes para API funcionales:
El método de fábrica:
Razonamiento:
Al igual que con las referencias de métodos en general, en comparación con las expresiones lambda, no puede capturar accidentalmente una variable del alcance accesible, como:
t -> streamopt(resolve(o))
Es composable, puede, por ejemplo, llamar
Function::andThen
al resultado del método de fábrica:streamopt(this::resolve).andThen(...)
Mientras que en el caso de una lambda, primero deberías lanzarla:
((Function<T, Stream<R>>) t -> streamopt(resolve(t))).andThen(...)
fuente
Null es compatible con Stream proporcionado por My library AbacusUtil . Aquí está el código:
fuente
Si está atascado con Java 8 pero tiene acceso a Guava 21.0 o posterior, puede usarlo
Streams.stream
para convertir un opcional en una transmisión.Por lo tanto, dado
puedes escribir
fuente
¿Qué hay de eso?
https://stackoverflow.com/a/58281000/3477539
fuente
return list.stream().filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList()))
, al igual que la pregunta (y su respuesta vinculada) tiene ....get()
sinisPresent()
, recibirá una advertencia en IntelliJLo más probable es que lo estés haciendo mal.
Java 8 Opcional no está destinado a ser utilizado de esta manera. Por lo general, solo está reservado para las operaciones de transmisión de terminal que pueden o no devolver un valor, como por ejemplo encontrar.
En su caso, podría ser mejor intentar primero encontrar una forma barata 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 resoluble y resuélvalo.
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.
fuente
Optional<Result> searchFor(Term t)
. Eso parece ajustarse a la intención de Opcional. Además, stream () s debe ser evaluado de manera perezosa, por lo que no debe ocurrir ningún trabajo adicional para resolver los términos más allá del primero coincidente.