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:
Optional<Other> result =
things.stream()
.map(this::resolve)
.flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
.findFirst();
Un mi ejemplo 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 y 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, 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 lo Optional.map
que se define de la siguiente manera:
Si hay un valor presente, aplíquele la función de mapeo proporcionada, y si el resultado no es nulo, devuelva un Opcional que describa el resultado. De lo contrario, devuelva un Opcional vacío.
La map(Stream::of)
llamada devuelve un Optional<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 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 vacío Stream<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>
a Optional<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.
.flatMap(Optional::toStream)
, con su versión realmente ve lo que está sucediendo.