¿Por qué usar Opcional en Java 8+ en lugar de las comprobaciones de puntero nulo tradicionales?

110

Recientemente nos hemos mudado a Java 8. Ahora, veo aplicaciones inundadas de Optionalobjetos.

Antes de Java 8 (Estilo 1)

Employee employee = employeeServive.getEmployee();

if(employee!=null){
    System.out.println(employee.getId());
}

Después de Java 8 (Estilo 2)

Optional<Employee> employeeOptional = Optional.ofNullable(employeeService.getEmployee());
if(employeeOptional.isPresent()){
    Employee employee = employeeOptional.get();
    System.out.println(employee.getId());
}

No veo ningún valor agregado de Optional<Employee> employeeOptional = employeeService.getEmployee();cuando el servicio en sí mismo devuelve opcional:

Viniendo de un fondo de Java 6, veo el Estilo 1 como más claro y con menos líneas de código. ¿Hay alguna ventaja real que me falta aquí?

Consolidado de la comprensión de todas las respuestas y más investigación en el blog

usuario3198603
fuente
20
Oracle tiene un extenso artículo sobre el uso de Opcional .
Mike Partridge el
28
¡Qué asco! employeeOptional.isPresent()parece estar perdiendo por completo el punto de los tipos de opciones. Según el comentario de @ MikePartridge, esto no es recomendable ni idiomático. Verificando explícitamente si un tipo de opción es algo o nada es un no-no. Se supone que debes simplemente mapo flatMapsobre ellos.
Andres F.
77
Vea una respuesta de desbordamiento de pilaOptional de Brian Goetz , el arquitecto del lenguaje Java en Oracle.
Basil Bourque
66
Esto es lo que sucede cuando apaga el cerebro en nombre de las "mejores prácticas".
Immibis
99
Ese es un uso bastante pobre de opcional pero incluso eso tiene beneficios. Con los valores nulos, todo puede ser nulo, por lo que debe estar constantemente comprobando, las llamadas opcionales indican que es probable que falte esta variable en particular, asegúrese de verificarla
Richard Tingle

Respuestas:

108

Style 2 no va a Java 8 lo suficiente como para ver el beneficio completo. No quieres el if ... useen absoluto. Ver los ejemplos de Oracle . Siguiendo sus consejos, obtenemos:

Estilo 3

// Changed EmployeeServive to return an optional, no more nulls!
Optional<Employee> employee = employeeServive.getEmployee();
employee.ifPresent(e -> System.out.println(e.getId()));

O un fragmento más largo

Optional<Employee> employee = employeeServive.getEmployee();
// Sometimes an Employee has forgotten to write an up-to-date timesheet
Optional<Timesheet> timesheet = employee.flatMap(Employee::askForCurrentTimesheet); 
// We don't want to do the heavyweight action of creating a new estimate if it will just be discarded
client.bill(timesheet.orElseGet(EstimatedTimesheet::new));
Caleth
fuente
19
de hecho, prohibimos la mayoría de los usos de isPresent (capturado por la revisión de código)
jk.
10
@jk. de hecho, si hubiera una sobrecargavoid ifPresent(Consumer<T>, Runnable)isPresentget
abogaría
10
@Caleth: Puede que te interesen los Java 9 ifPresentOrElse(Consumer<? super T>, Runnable).
wchargin
99
Encuentro un inconveniente en este estilo java 8, en comparación con el java 6 one: engaña a las herramientas de cobertura de código. Con la declaración if, muestra cuándo se perdió una rama, no aquí.
Thierry
14
@Aaron "reduce drásticamente la legibilidad del código". ¿Qué parte reduce exactamente la legibilidad? Suena más como un "siempre lo he hecho de manera diferente y no quiero cambiar mis hábitos" que el razonamiento objetivo.
Voo
47

Si está utilizando Optionalcomo una capa de "compatibilidad" entre una API anterior que aún puede regresar null, puede ser útil crear el Opcional (no vacío) en la última etapa de que está seguro de que tiene algo. Por ejemplo, donde escribiste:

Optional<Employee> employeeOptional = Optional.ofNullable(employeeService.getEmployee());
if(employeeOptional.isPresent()){
    Employee employeeOptional= employeeOptional.get();
    System.out.println(employee.getId());
}

Yo optaría por:

Optional.of(employeeService)                 // definitely have the service
        .map(EmployeeService::getEmployee)   // getEmployee() might return null
        .map(Employee::getId)                // get ID from employee if there is one
        .ifPresent(System.out::println);     // and if there is an ID, print it

El punto es que usted sabe que hay un no nulo empleado del servicio , por lo que puede envolver que hasta en un Optionalcon Optional.of(). Luego, cuando recurras getEmployee()a eso, puedes o no conseguir un empleado. Ese empleado puede (o posiblemente no tener) una identificación. Luego, si terminó con una identificación, desea imprimirla.

No hay necesidad de verificar explícitamente cualquier nulo, presencia, etc., en este código.

Joshua Taylor
fuente
44
¿Cuál es la ventaja de usar (...).ifPresent(aMethod)más if((...).isPresent()) { aMethod(...); }? Parece ser simplemente otra sintaxis para hacer casi la misma operación.
Ruslan
2
@Ruslan ¿Por qué usar una sintaxis especial para una llamada específica cuando ya tiene una solución general que funciona igual de bien para todos los casos? Solo estamos aplicando la misma idea de map and co aquí también.
Voo
1
@Ruslan ... una API devuelve nulos en lugar de colecciones o matrices vacías. Por ejemplo, se puede hacer algo así (asumiendo una clase padre para los que getChildren () devuelve un conjunto de niños, o null si no hay niños): Set<Children> children = Optional.of(parent).map(Parent::getChildren).orElseGet(Collections::emptySet); Ahora puedo procesar childrende manera uniforme, por ejemplo, children.forEach(relative::sendGift);. Aquellos incluso podrían combinarse: Optional.of(parent).map(Parent::getChildren).orElseGet(Collections::emptySet).forEach(relative::sendGift);. Toda la repetitiva de la comprobación nula, etc., ...
Joshua Taylor
1
@Ruslan ... se delega a código probado y verdadero en la biblioteca estándar. Esto reduce la cantidad de código que, como programador, que escribir, probar, depurar, etc.
Joshua Taylor
1
Me alegro de que esté usando Swift. if let employee = employeeService.employee {print (employee.Id); }
gnasher729
23

Casi no hay valor agregado al tener un Optionalvalor único. Como has visto, eso simplemente reemplaza un cheque por nullun cheque de presencia.

Hay un gran valor agregado en tener una Collectionde esas cosas. Todos los métodos de transmisión introducidos para reemplazar los bucles de bajo nivel de estilo antiguo entienden las Optionalcosas y hacen lo correcto al respecto (no las procesan o finalmente devuelven otro desarmado Optional). Si maneja n elementos con más frecuencia que con elementos individuales (y el 99% de todos los programas realistas lo hacen), entonces es una gran mejora tener soporte incorporado para las cosas opcionalmente presentes. En el caso ideal, nunca tiene que verificar null ni presencia.

Kilian Foth
fuente
24
Aparte de colecciones / arroyos, un opcional también es ideal para hacer las API más auto-documentado, por ejemplo Employee getEmployee()vs Optional<Employee> getEmployee(). Aunque técnicamente ambos podrían regresar null, uno de ellos es mucho más probable que cause problemas.
amon
16
Hay mucho valor en un opcional de un solo valor. Poder hacerlo .map()sin tener que buscar nulos en todo el camino es genial. Ej Optional.of(service).map(Service::getEmployee).map(Employee::getId).ifPresent(this::submitIdForReview);.
Joshua Taylor el
99
No estoy de acuerdo con su comentario inicial sin valor agregado. Opcional proporciona contexto a su código. Un vistazo rápido puede indicarle la corrección de una función y todos los casos están cubiertos. Mientras que con la verificación nula, ¿debería verificar nula cada tipo de referencia en cada método? Un caso similar puede aplicarse a las clases y modificadores públicos / privados; agregan valor cero a su código, ¿verdad?
ArTs
3
@JoshuaTaylor Honestamente, en comparación con esa expresión, prefiero el cheque pasado de moda null.
Kilian Foth
2
Otra gran ventaja de Opcional, incluso con valores individuales, es que no puede olvidarse de buscar nulos cuando debería hacerlo. De lo contrario, es muy fácil olvidar el cheque y terminar con una NullPointerException ...
Sean Burton
17

Siempre y cuando use Optionalcomo una API elegante isNotNull(), entonces sí, no encontrará ninguna diferencia con solo verificar null.

Cosas que NO debes usar Optionalpara

Usar Optionalsolo para verificar la presencia de un valor es Bad Code ™:

// BAD CODE ™ --  just check getEmployee() != null  
Optional<Employee> employeeOptional =  Optional.ofNullable(employeeService.getEmployee());  
if(employeeOptional.isPresent()) {  
    Employee employee = employeeOptional.get();  
    System.out.println(employee.getId());
}  

Cosas que deberías usar Optionalpara

Evite que un valor no esté presente, produciendo uno cuando sea necesario cuando utilice métodos API de retorno nulo:

Employee employee = Optional.ofNullable(employeeService.getEmployee())
                            .orElseGet(Employee::new);
System.out.println(employee.getId());

O, si crear un nuevo empleado es demasiado costoso:

Optional<Employee> employee = Optional.ofNullable(employeeService.getEmployee());
System.out.println(employee.map(Employee::getId).orElse("No employee found"));

Además, hacer que todos sepan que sus métodos podrían no devolver un valor (si, por alguna razón, no puede devolver un valor predeterminado como el anterior):

// Your code without Optional
public Employee getEmployee() {
    return someCondition ? null : someEmployee;
}
// Someone else's code
Employee employee = getEmployee(); // compiler doesn't complain
// employee.get...() -> NPE awaiting to happen, devs criticizing your code

// Your code with Optional
public Optional<Employee> getEmployee() {
    return someCondition ? Optional.empty() : Optional.of(someEmployee);
}
// Someone else's code
Employee employee = getEmployee(); // compiler complains about incompatible types
// Now devs either declare Optional<Employee>, or just do employee = getEmployee().get(), but do so consciously -- not your fault.

Y, por último, están todos los métodos de flujo que ya se han explicado en otras respuestas, aunque esos no son realmente los que usted decide usar, Optionalsino que utiliza los Optionalproporcionados por otra persona.

walen
fuente
1
@Kapep De hecho. Tuvimos este debate en / r / java, y dije : «Lo veo Optionalcomo una oportunidad perdida. "Aquí, tengo esta nueva Optionalcosa que hice para que te veas obligado a verificar valores nulos. Ah, y por cierto ... agregué un get()método para que no tengas que verificar nada;);)" . (...) Optionales agradable como un letrero de neón "ESTO PODRÍA SER NULO", pero la simple existencia de su get()método lo paraliza » . Pero OP estaba pidiendo razones para usar Optional, no razones en contra o fallas de diseño.
walen
1
Employee employee = Optional.ofNullable(employeeService.getEmployee());En su tercer fragmento, ¿no debería ser el tipo Optional<Employee>?
Charlie Harding
2
@immibis ¿De qué manera lisiado? La razón principal para usar getes si tiene que interactuar con algún código heredado. No puedo pensar en muchas razones válidas en el nuevo código para usar get. Dicho esto, al interactuar con el código heredado a menudo no hay alternativa, pero aún así tiene un punto que la existencia de getprincipiantes hace que el código sea incorrecto.
Voo
1
@immibis que no mencioné ni Mapuna vez y no estoy al tanto de que alguien haga un cambio tan drástico no compatible con versiones anteriores, así que asegúrese de que pueda diseñar intencionalmente API incorrectas Optional, no estoy seguro de lo que eso prueba. Sin Optionalembargo, tendría sentido para algún tryGetmétodo.
Voo
2
@Voo Mapes un ejemplo con el que todos están familiarizados. Por supuesto, no pueden Map.getdevolver un Opcional debido a la compatibilidad con versiones anteriores, pero puede imaginar fácilmente otra clase similar a un Mapa que no tendría tales restricciones de compatibilidad. Decir class UserRepository {Optional<User> getUserDetails(int userID) {...}}. Ahora desea obtener los detalles del usuario conectado, y sabe que existen porque lograron iniciar sesión y su sistema nunca elimina usuarios. Entonces lo haces theUserRepository.getUserDetails(loggedInUserID).get().
immibis
12

El punto clave para mí al usar Opcional es mantenerse claro cuando el desarrollador necesita verificar si el valor de retorno de la función o no.

//service 1
Optional<Employee> employeeOptional = employeeService.getEmployee();
if(employeeOptional.isPresent()){
    Employee employeeOptional= employeeOptional.get();
    System.out.println(employee.getId());
}

//service 2
Employee employee = employeeService.getEmployeeXTPO();
System.out.println(employee.getId());
Rodrigo Menezes
fuente
8
Me desconcierta que todas las cosas ingeniosas y funcionales con opciones, aunque sean realmente agradables, se consideren más importantes que este punto aquí. Antes de Java 8, tenía que buscar en Javadoc para saber si tenía que verificar la respuesta de una llamada. Después de Java 8, sabe por el tipo de retorno si puede recuperar "nada" o no.
Buhb
3
Bueno, existe el problema del "código antiguo" en el que las cosas aún podrían volver nulas ... pero sí, ser capaz de distinguir de la firma es genial.
Haakon Løtveit
55
Sí, ciertamente funciona mejor en idiomas donde opcional <T> es la única forma de expresar que puede faltar un valor, es decir, idiomas sin ntr ptr.
dureuill
8

Su principal error es que todavía está pensando en términos más procesales. Esto no pretende ser una crítica de ti como persona, es simplemente una observación. Pensar en términos más funcionales viene con tiempo y práctica, y por lo tanto, los métodos están presentes y se ven como las cosas correctas más obvias para usted. Su error secundario secundario es crear su Opcional dentro de su método. El Opcional está destinado a ayudar a documentar que algo puede o no devolver un valor. Puede que no consigas nada.

Esto te ha llevado a escribir un código perfectamente legible que parece perfectamente razonable, pero te sedujeron las viles tentadoras gemelas que se ponen y están presentes.

Por supuesto, la pregunta se convierte rápidamente en "¿por qué está presente y llega hasta allí?"

Una cosa que mucha gente extraña aquí es que Presente () es que no es algo creado para un nuevo código escrito por personas totalmente conscientes de cuán malditas son las lambdas útiles y a las que les gusta lo funcional.

Sin embargo, nos da algunos (dos) beneficios buenos, geniales y glamorosos (?):

  • Facilita la transición del código heredado para usar las nuevas funciones.
  • Facilita las curvas de aprendizaje de Opcional.

El primero es bastante simple.

Imagine que tiene una API que se ve así:

public interface SnickersCounter {
  /** 
   * Provides a proper count of how many snickers have been consumed in total.
   */
  public SnickersCount howManySnickersHaveBeenEaten();

  /**
    * returns the last snickers eaten.<br>
    * If no snickers have been eaten null is returned for contrived reasons.
    */
  public Snickers lastConsumedSnickers();
}

Y tenía una clase heredada usando esto como tal (complete los espacios en blanco):

Snickers lastSnickers = snickersCounter.lastConsumedSnickers();
if(null == lastSnickers) {
  throw new NoSuchSnickersException();
}
else {
  consumer.giveDiabetes(lastSnickers);
}

Un ejemplo artificial para estar seguro. Pero ten paciencia conmigo aquí.

Java 8 se ha lanzado ahora y estamos luchando por subir a bordo. Entonces, una de las cosas que hacemos es que queremos reemplazar nuestra interfaz anterior con algo que devuelva Opcional. ¿Por qué? Porque como alguien más ya ha mencionado amablemente: esto elimina las conjeturas de si algo puede ser nulo o no. Esto ya ha sido señalado por otros. Pero ahora tenemos un problema. Imagine que tenemos (disculpe mientras presiono alt + F7 en un método inocente), 46 lugares donde se llama a este método en código heredado bien probado que de lo contrario hace un excelente trabajo. Ahora tienes que actualizar todo esto.

ESTO es donde brilla IsPresent.

Porque ahora: Snickers lastSnickers = snickersCounter.lastConsumedSnickers (); if (null == lastSnickers) {lanzar una nueva NoSuchSnickersException (); } else {consumer.giveDiabetes (lastSnickers); }

se convierte en:

Optional<Snickers> lastSnickers = snickersCounter.lastConsumedSnickers();
if(!lastSnickers.isPresent()) {
  throw new NoSuchSnickersException();
}
else {
  consumer.giveDiabetes(lastSnickers.get());
}

Y este es un cambio simple que puede darle al nuevo junior: puede hacer algo útil y podrá explorar la base de código al mismo tiempo. ganar-ganar Después de todo, algo similar a este patrón está bastante extendido. Y ahora no tiene que volver a escribir el código para usar lambdas ni nada. (En este caso particular, sería trivial, pero dejo pensar ejemplos en los que sería difícil como ejercicio para el lector).

Tenga en cuenta que esto significa que la forma en que lo hizo es esencialmente una forma de lidiar con el código heredado sin hacer reescrituras costosas. Entonces, ¿qué pasa con el nuevo código?

Bueno, en su caso, donde solo desea imprimir algo, simplemente haría:

snickersCounter.lastConsumedSnickers (). ifPresent (System.out :: println);

Lo cual es bastante simple y perfectamente claro. El punto que burbujea lentamente hacia la superficie es que existen casos de uso para get () y isPresent (). Están ahí para permitirle modificar mecánicamente el código existente para usar los tipos más nuevos sin pensar demasiado en ello. Por lo tanto, lo que está haciendo está mal orientado de las siguientes maneras:

  • Está llamando a un método que puede devolver nulo. La idea correcta sería que el método devuelve nulo.
  • Estás utilizando los métodos de bandaid heredados para lidiar con esta opción, en lugar de utilizar los nuevos métodos sabrosos que contienen fantasía de lambda.

Si desea utilizar Opcional como una simple verificación de seguridad nula, lo que debería haber hecho es simplemente esto:

new Optional.ofNullable(employeeServive.getEmployee())
    .map(Employee::getId)
    .ifPresent(System.out::println);

Por supuesto, la buena versión de esto se ve así:

employeeService.getEmployee()
    .map(Employee::getId)
    .ifPresent(System.out::println);

Por cierto, aunque de ninguna manera es obligatorio, recomiendo usar una nueva línea por operación, para que sea más fácil de leer. Fácil de leer y comprender mejor que la concisión cualquier día de la semana.

Por supuesto, este es un ejemplo muy simple en el que es fácil entender todo lo que estamos tratando de hacer. No siempre es así de simple en la vida real. Pero observe cómo en este ejemplo, lo que estamos expresando son nuestras intenciones. Queremos OBTENER el empleado, OBTENER su identificación y, si es posible, imprimirlo. Esta es la segunda gran victoria con Opcional. Nos permite crear un código más claro. También creo que hacer cosas como hacer un método que haga un montón de cosas para que puedas alimentarlo a un mapa es, en general, una buena idea.

Haakon Løtveit
fuente
1
El problema aquí es que intenta hacer que suene como si estas versiones funcionales fueran tan legibles como las versiones anteriores, en un caso incluso diciendo que un ejemplo era "perfectamente claro". Ese no es el caso. Ese ejemplo fue claro, pero no perfectamente , ya que requiere más reflexión. map(x).ifPresent(f)simplemente no tiene el mismo tipo de sentido intuitivo que if(o != null) f(x)tiene. La ifversión es perfectamente clara. Este es otro caso de personas saltando en un carro de banda popular, * no * un caso de progreso real.
Aaron
1
Tenga en cuenta que no estoy diciendo que haya cero beneficios en el uso de la programación funcional o de Optional. Me refería solo al uso de opcional en este caso específico, y más aún a la legibilidad, ya que varias personas actúan como si este formato fuera igual o más legible que if.
Aaron
1
Depende de nuestras nociones de claridad. Si bien haces muy buenos puntos, también me gustaría señalar que para muchas personas las lambdas eran nuevas y extrañas en algún momento. Me atrevería a apostar una cookie de Internet que mucha gente dice que está presente y sentir un alivio maravilloso: ahora podrían continuar actualizando el código heredado y luego aprender la sintaxis lambda más tarde. Por otro lado, las personas con Lisp, Haskell o experiencia similar en el lenguaje probablemente estaban encantadas de poder aplicar patrones familiares a sus problemas en Java ...
Haakon Løtveit
@ Aaron - la claridad no es el objetivo de los tipos opcionales. Personalmente, encuentro que no disminuyen la claridad, y hay algunos casos (aunque no este caso) en los que lo aumentan, pero el beneficio clave es que (siempre y cuando evite usarlo isPresenty getsea ​​lo más realista posible) mejorar la seguridad . Es más difícil escribir código incorrecto que no puede verificar la ausencia de un valor si el tipo es Optional<Whatever>más que usar un nulo Whatever.
Jules
Incluso si elimina los valores de inmediato, siempre y cuando no los useget , se verá obligado a elegir qué hacer cuando ocurra un valor faltante: usaría orElse()(o orElseGet()) para proporcionar un valor predeterminado o orElseThrow()arrojar un valor elegido. excepción que documenta la naturaleza del problema , que es mejor que un NPE genérico que no tiene sentido hasta que se profundiza en la traza de la pila.
Jules
0

No veo beneficio en el Estilo 2. Todavía debe darse cuenta de que necesita la verificación nula y ahora es aún más grande, por lo tanto, menos legible.

Sería un mejor estilo, si employeeServive.getEmployee () devolvería Opcional y luego el código sería:

Optional<Employee> employeeOptional = employeeServive.getEmployee();
if(employeeOptional.isPresent()){
    Employee employee= employeeOptional.get();
    System.out.println(employee.getId());
}

De esta manera no puede llamar empleeeServive.getEmployee (). GetId () y observa que debe manejar el valor opcional de alguna manera. Y si en todos sus métodos de base de código nunca devuelve nulo (o casi nunca con excepciones bien conocidas por su equipo a esta regla), aumentará la seguridad contra NPE.

user470365
fuente
12
Opcional se convierte en una complicación sin sentido en el momento en que lo usa isPresent(). Usamos opcional para no tener que probar.
candied_orange
2
Como otros han dicho, no lo use isPresent(). Este es el enfoque incorrecto de los opcionales. Uso mapo en su flatMaplugar.
Andrés F.
1
@CandiedOrange Y sin embargo, la respuesta más votada (que dice usar ifPresent) es solo otra forma de probar.
immibis
1
@CandiedOrange ifPresent(x)es tanto una prueba como if(present) {x}. El valor de retorno es irrelevante.
Immibis
1
@CandiedOrange No obstante, originalmente dijiste "para que no tengamos que hacer la prueba". No puedes decir "para que no tengamos que probar" cuando, de hecho, todavía estás probando ... lo que estás haciendo.
Aaron
0

Creo que el mayor beneficio es que si su firma del método devuelve una Employeeque no comprobar NULL. Usted sabe con esa firma que se garantiza que devolverá a un empleado. No necesita manejar un caso de falla. Con un opcional sabes que lo haces.

En el código que he visto, hay cheques nulos que nunca fallarán, ya que las personas no quieren rastrear el código para determinar si es posible un nulo, por lo que arrojan cheques nulos en todas partes a la defensiva. Esto hace que el código sea un poco más lento, pero lo más importante es que hace que el código sea mucho más ruidoso.

Sin embargo, para que esto funcione, debe aplicar este patrón de manera consistente.

Trineo
fuente
1
La forma más sencilla de lograr esto es usando @Nullabley @ReturnValuesAreNonnullByDefaultjunto con el análisis estático.
maaartinus
@maaartinus Agregar herramientas adicionales en forma de análisis estático y documentación en las anotaciones del decorador, ¿es más simple que el soporte de API en tiempo de compilación?
Trineo el
Agregar herramientas adicionales es más simple, ya que no requiere cambios de código. Además, también debes tenerlo, ya que atrapa otros errores también. +++ Escribí un guión trivial añadiendo package-info.javacon @ReturnValuesAreNonnullByDefaulttodos mis paquetes, por lo que todo lo que tengo que hacer es añadir @Nullableen el que tiene que cambiar a Optional. Esto significa menos caracteres, sin basura adicional y sin sobrecarga de tiempo de ejecución. No tengo que buscar la documentación como se muestra al pasar el mouse sobre el método. La herramienta de análisis estático detecta errores y posteriores cambios de nulabilidad.
maaartinus el
Mi mayor preocupación Optionales que agrega una forma alternativa de lidiar con la nulabilidad. Si estuvo en Java desde el principio, puede estar bien, pero ahora es solo un dolor. Seguir el camino de Kotlin sería en mi humilde opinión mucho mejor. +++ Tenga en cuenta que List<@Nullable String>todavía es una lista de cadenas, mientras List<Optional<String>>que no lo es.
maaartinus el
0

Mi respuesta es: simplemente no. Al menos piénsalo dos veces si es realmente una mejora

Una expresión (tomada de otra respuesta) como

Optional.of(employeeService)                 // definitely have the service
        .map(EmployeeService::getEmployee)   // getEmployee() might return null
        .map(Employee::getId)                // get ID from employee if there is one
        .ifPresent(System.out::println);     // and if there is an ID, print it

parece ser la forma funcional correcta de tratar con los métodos de retorno originalmente anulables. Se ve bien, nunca arroja y nunca te hace pensar en manejar el caso "ausente".

Se ve mejor que la forma antigua

Employee employee = employeeService.getEmployee();
if (employee != null) {
    ID id = employee.getId();
    if (id != null) {
        System.out.println(id);
    }
}

lo que hace obvio que puede haber elsecláusulas.

El estilo funcional

  • se ve completamente diferente (y es ilegible para las personas que no están acostumbradas)
  • es un dolor de depurar
  • no se puede extender fácilmente para manejar el caso "ausente" ( orElseno siempre es suficiente)
  • induce a error a ignorar el caso "ausente"

En ningún caso debe usarse como panacea. Si fuera universalmente aplicable, entonces la semántica del .operador debería ser reemplazada por la semántica del ?.operador , el NPE olvidado y todos los problemas ignorados.


Si bien soy un gran fanático de Java, debo decir que el estilo funcional es solo el sustituto de la declaración de un pobre hombre de Java

employeeService
.getEmployee()
?.getId()
?.apply(id => System.out.println(id));

bien conocido de otros idiomas. ¿Estamos de acuerdo en que esta declaración

  • no es (realmente) funcional?
  • es muy similar al código antiguo, pero mucho más simple?
  • ¿Es mucho más legible que el estilo funcional?
  • es amigable para el depurador?
maaartinus
fuente
Parece que está describiendo la implementación de C # de este concepto con una función de lenguaje completamente diferente a la implementación de Java con una clase de biblioteca central. A mí me parecen iguales
Caleth
@Caleth De ninguna manera. Podríamos hablar sobre el "concepto de simplificar una evaluación nula-segura", pero no lo llamaría un "concepto". Podemos decir que Java implementa lo mismo usando una clase de biblioteca central, pero es solo un desastre. Simplemente comience employeeService.getEmployee().getId().apply(id => System.out.println(id));y haga que sea nulo seguro una vez que use el operador de navegación segura y una vez que use el Optional. Claro, podríamos llamarlo un "detalle de implementación", pero la implementación es importante. Hay casos en que los lamdas hacen que el código sea más simple y agradable, pero aquí es solo un feo abuso.
maaartinus
Biblioteca de clases: Optional.of(employeeService).map(EmployeeService::getEmployee).map(Employee::getId).ifPresent(System.out::println);característica del lenguaje: employeeService.getEmployee()?.getId()?.apply(id => System.out.println(id));. Dos sintaxis, pero terminan pareciéndose mucho a mí. Y el "concepto" es Optionaltambién Nullableconocido como akaMaybe
Caleth
No entiendo por qué crees que uno de esos es "abuso feo", y el otro bien. Podría entenderte pensando tanto "abuso feo" como bien.
Caleth
@Caleth Una diferencia es que, en lugar de agregar dos signos de interrogación, debe reescribirlo todo. El problema no es que la función Idioma y los códigos de clase de la Biblioteca difieran mucho. Está bien. El problema es que el código original y los códigos de clase de la Biblioteca difieren mucho. La misma idea, diferente código. Eso es muy malo. +++Lo que se abusa son lamdas para manejar la nulabilidad. Los lamdas están bien, pero Optionalno lo están. La anulabilidad simplemente necesita un soporte de idioma adecuado como en Kotlin. Otro problema: List<Optional<String>>no está relacionado con List<String>dónde necesitaríamos que estén relacionados.
maaartinus
0

Opcional es un concepto (un tipo de orden superior) que proviene de la programación funcional. Usarlo elimina la comprobación nula anidada y te salva de la pirámide de la fatalidad.

Petras Purlys
fuente