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

93

Nos hemos mudado recientemente a Java 8. Ahora, veo aplicaciones inundadas con Optional objetos.

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 propio servicio devuelve opcional:

Desde el fondo de Java 6, veo que el Estilo 1 es más claro y con menos líneas de código. ¿Hay alguna ventaja real que me esté perdiendo aquí?

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

    
pregunta user3198603 18.01.2018 - 15:34

10 respuestas

98

El estilo 2 no va a Java 8 lo suficiente como para ver el beneficio completo. No quieres el if ... use en absoluto. Consulte ejemplos de Oracle . Tomando su consejo, 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));
    
respondido por el Caleth 18.01.2018 - 16:14
43

Si está usando Optional como una capa de "compatibilidad" entre una API más antigua que aún puede devolver null , puede ser útil crear el Opcional (no vacío) en la última etapa en la que está seguro que tienes algo. Por ejemplo, donde escribiste:

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

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 sabe que hay un servicio para empleados no nulos, por lo que puede resumir eso en un Optional con Optional.of() . Luego, cuando llama a getEmployee() a eso, puede o no obtener un empleado. Ese empleado puede (o, posiblemente, no tener) una identificación. Luego, si terminó con una identificación, desea imprimirla.

No es necesario verificar explícitamente cualquier nulo, presencia, etc., en este código.

    
respondido por el Joshua Taylor 18.01.2018 - 20:37
20

No hay casi ningún valor agregado al tener un Optional de un solo valor. Como has visto, eso simplemente reemplaza un cheque por null con un cheque por presencia.

Hay un gran valor agregado en tener un Collection de tales cosas. Todos los métodos de transmisión introducidos para reemplazar los bucles de bajo nivel de estilo antiguo comprenden Optional things y hacen lo correcto al respecto (no procesándolos o finalmente devolviendo otro Optional no configurado). Si trata con los elementos de n con más frecuencia que con los elementos individuales (y el 99% de todos los programas realistas lo hacen), es una gran mejora tener soporte incorporado para las cosas presentes de manera opcional. En el caso ideal, nunca tiene que verificar la presencia de null o .

    
respondido por el Kilian Foth 18.01.2018 - 15:47
14

Mientras use Optional como una API sofisticada para isNotNull() , entonces sí, no encontrará ninguna diferencia con solo verificar null .

Cosas que NO debes usar Optional para

El uso de Optional solo 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 Optional para

Evite que un valor no esté presente, generando uno cuando sea necesario cuando use métodos de 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, advierta a todos 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, finalmente, hay todos los métodos similares a las secuencias que ya se han explicado en otras respuestas, aunque realmente no es usted usted el que decide utilizar Optional sino que utiliza el Optional provisto por otra persona.

    
respondido por el Luis G. 19.01.2018 - 09:43
12

El punto clave para mí en el uso de Opcional es mantenerse claro cuando el desarrollador necesita verificar si la función devuelve valor 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());
    
respondido por el Rodrigo Menezes 18.01.2018 - 19:26
8

Su error principal es que todavía está pensando en términos más de procedimiento. Esto no es una crítica de usted 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 son: Presente y consiga el aspecto más obvio y correcto para usted. Tu segundo error secundario es crear tu Opcional dentro de tu método. La Opcional pretende 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 fuiste seducido por las viles tentadoras gemelas que se ponen y están presentes.

Por supuesto, la pregunta se convierte rápidamente en "¿por qué están presentes e incluso están ahí?"

Una cosa que mucha gente se pierde es que el Presente () es que no es algo que esté hecho para un nuevo código escrito por personas totalmente a bordo con cuánta lambda útil son las gambas, ya quién le gusta lo funcional.

Sin embargo, nos brinda algunos (dos) beneficios buenos, excelentes, 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.

Imagina que tienes una API que se parece a esto:

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ías una clase heredada usando esto como tal (rellena 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 iniciado y estamos luchando para subir a bordo. Entonces, una de las cosas que hacemos es que queremos reemplazar nuestra interfaz anterior con algo que devuelve Opcional. ¿Por qué? Porque como alguien más ya ha mencionado gentilmente: Esto elimina las conjeturas de si algo puede ser nulo Esto ya ha sido señalado por otros. Pero ahora tenemos un problema. Imagínese que tenemos (perdóneme mientras presiono alt + F7 en un método inocente), 46 lugares donde este método se llama en un código legado bien probado que, de lo contrario, hace un excelente trabajo. Ahora tienes que actualizar todos estos.

ESTO es donde brilla el presente.

Porque ahora:     Snickers lastSnickers = snickersCounter.lastConsumedSnickers ();     if (null == lastSnickers) {       lanzar nuevo 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 puedes darle al nuevo junior: él puede hacer algo útil, y podrá explorar el código base al mismo tiempo. ganar-ganar Después de todo, algo parecido a este patrón está bastante extendido. Y ahora no tiene que volver a escribir el código para usar lambdas o algo así. (En este caso particular, sería trivial, pero dejo algunos 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 tu caso, donde solo quieres imprimir algo, simplemente lo harías:

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

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

  • Estás llamando a un método que puede devolver nulo. La idea correcta sería que el método devuelve nulo.
  • Estás usando los métodos heredados de banda para lidiar con este opcional, en lugar de usar los nuevos y sabrosos métodos que contienen la fantasía lambda.

Si desea utilizar Opcional como una simple comprobació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 este se ve así:

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

Por cierto, aunque no 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 los tiempos de concisión cualquier día de la semana.

Este es, por supuesto, un ejemplo muy simple donde es fácil entender todo lo que estamos tratando de hacer. No siempre es así de simple en la vida real. Pero note cómo en este ejemplo, lo que expresamos son nuestras intenciones. Queremos OBTENER al empleado, OBTENER su identificación y, si es posible, imprimirlo. Esta es la segunda gran victoria con Opcional. Nos permite crear 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 incluirlo en un mapa es, en general, una buena idea.

    
respondido por el Haakon Løtveit 19.01.2018 - 14:49
0

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

Mejor estilo sería, si employeeServive.getEmployee () devolvería Opcional y luego el código se convertiría en:

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

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

    
respondido por el user470365 18.01.2018 - 15:46
0

Creo que el mayor beneficio es que si la firma de tu método devuelve un Employee , no verificará si está nulo. Usted sabe por esa firma que está garantizado para devolver un empleado. No necesitas manejar un caso de falla. Con un opcional sabes que lo haces.

En el código que he visto, hay comprobaciones nulas que nunca fallarán, ya que las personas no quieren rastrear el código para determinar si es posible una nula, por lo que lanzan comprobaciones nulas 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, debes aplicar este patrón de manera consistente.

    
respondido por el ArtB 18.01.2018 - 22:50
0

Mi respuesta es: Simplemente no. Al menos piense dos veces si realmente es 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 devolución originalmente anulables. Se ve bien, nunca lanza y nunca te hace pensar en manejar el caso "ausente".

Se ve mejor que a la antigua usanza

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 else cláusulas.

El estilo funcional

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

En ningún caso debe usarse como una panacea. Si fuera de aplicación universal, la semántica del operador . debería reemplazarse por la semántica de ?. operator , el NPE olvidado y todos los problemas ignorados.

Aunque soy un gran fan 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 es mucho más simple?
  • ¿Es mucho más legible que el estilo funcional?
  • ¿es amigable con el depurador?
respondido por el maaartinus 24.01.2018 - 00:17
0

Opcional es un concepto (un tipo de orden superior) que proviene de la programación funcional. Al usarlo, se eliminan las comprobaciones nulas anidadas y se guarda de la pirámide de la fatalidad.

    
respondido por el Petras Purlys 24.01.2018 - 10:33

Lea otras preguntas en las etiquetas