¿Por qué está usando una opción preferencial para verificar nula la variable?

25

Tome los dos ejemplos de código:

if(optional.isPresent()) {
    //do your thing
}

if(variable != null) {
    //do your thing
}

Por lo que puedo decir, la diferencia más obvia es que el Opcional requiere la creación de un objeto adicional.

Sin embargo, muchas personas han comenzado a adoptar rápidamente los opcionales. ¿Cuál es la ventaja de usar opcionales versus un cheque nulo?

William Dunne
fuente
44
casi nunca desea usar isPresent, por lo general debería usar ifPresent o map
jk.
66
@jk. Porque las ifdeclaraciones son taaaaaai en la última década, y todos están usando abstracciones de mónada y lambdas ahora.
user253751
2
@immibis Una vez tuve un largo debate sobre este tema con otro usuario. El punto es que al if(x.isPresent) fails_on_null(x.get)salir del sistema de tipos y mantener la garantía de que el código no se romperá "en su cabeza" sobre la distancia (ciertamente corta) entre la condición y la llamada a la función. En optional.ifPresent(fails_on_null)el sistema de tipos le garantizamos esto, y no tiene que preocuparse.
ziggystar
1
El principal defecto en Java con el uso Optional.ifPresent(y varias otras construcciones de Java) es que solo puede modificar efectivamente las variables finales y no puede lanzar excepciones comprobadas. Esa es una razón suficiente para evitar a menudo ifPresent, desafortunadamente.
Mike

Respuestas:

19

Optionalaprovecha el sistema de tipos para hacer el trabajo que de lo contrario tendría que hacer todo en su cabeza: recordar si una referencia dada puede ser o no null. Esto es bueno. Siempre es inteligente dejar que el compilador maneje trabajos aburridos y reservar el pensamiento humano para un trabajo creativo e interesante.

Sin Optional, cada referencia en su código es como una bomba sin explotar. Acceder a él puede hacer algo útil, o puede terminar su programa con una excepción.

Con Opcional y sin null, cada acceso a una referencia normal tiene éxito, y cada referencia a una Opcional tiene éxito a menos que esté desarmado y no haya podido comprobarlo. Esa es una gran victoria en mantenibilidad.

Desafortunadamente, la mayoría de los idiomas que ahora ofrecen Optionalno se han eliminado null, por lo que solo puede beneficiarse del concepto instituyendo una política estricta de "absolutamente no null, nunca". Por lo tanto, Optionalpor ejemplo, Java no es tan convincente como debería ser idealmente.

Kilian Foth
fuente
Dado que su respuesta es la mejor, podría valer la pena agregar el uso de Lambdas como una ventaja - para cualquiera que lea esto en el futuro (vea mi respuesta)
William Dunne
La mayoría de los idiomas modernos proporcionan tipos no anulables, Kotlin, mecanografiado, AFAIK.
Zen
55
En mi opinión, esto se maneja mejor con una anotación @Nullable. No hay razón para usar Optionalsolo para recordarle que el valor podría ser nulo
Hilikus
18

Un Optionaltrae un tipeo más fuerte a las operaciones que pueden fallar, como lo han cubierto las otras respuestas, pero eso está lejos de ser lo más interesante o valioso que Optionalstrae a la mesa. Mucho más útil es la capacidad de retrasar o evitar la verificación de fallas, y componer fácilmente muchas operaciones que pueden fallar.

Considere si tenía su optionalvariable de su código de ejemplo, entonces tuvo que realizar dos pasos adicionales que cada uno podría fallar. Si falla algún paso en el camino, en su lugar, desea devolver un valor predeterminado. Usando Optionalscorrectamente, terminas con algo como esto:

return optional.flatMap(x -> x.anotherOptionalStep())
               .flatMap(x -> x.yetAnotherOptionalStep())
               .orElse(defaultValue);

Con nullHubiera tenido que verificar tres veces nullantes de continuar, lo que agrega mucha complejidad y dolores de cabeza de mantenimiento al código. Optionalstener ese control integrado en las funciones flatMapy orElse.

Tenga en cuenta que no llamé isPresentuna vez, lo que debe pensar como un olor a código cuando lo uso Optionals. Eso no significa necesariamente que nunca debas usarlo isPresent, solo que debes analizar detenidamente cualquier código que lo haga, para ver si hay una mejor manera. De lo contrario, tiene razón, solo está obteniendo un beneficio de seguridad de tipo marginal sobre el uso null.

También tenga en cuenta que no estoy tan preocupado por encapsular todo esto en una sola función, para proteger otras partes de mi código de punteros nulos de resultados intermedios. Si tiene más sentido tener mi .orElse(defaultValue)en otra función, por ejemplo, tengo muchos menos reparos en ponerla allí, y es mucho más fácil componer las operaciones entre diferentes funciones según sea necesario.

Karl Bielefeldt
fuente
10

Destaca la posibilidad de nulo como una respuesta válida, que la gente a menudo asume (correcta o incorrectamente) no se devolverá. Destacar las pocas veces que nulo es válido permite omitir ensuciar su código con un gran número de comprobaciones nulas sin sentido.

Idealmente, establecer una variable en nulo sería un error de tiempo de compilación en cualquier lugar menos en un opcional; eliminando excepciones de puntero nulo en tiempo de ejecución. Obviamente, la compatibilidad con versiones anteriores impide esto, lamentablemente

Richard Tingle
fuente
4

Dejame darte un ejemplo:

class UserRepository {
     User findById(long id);
}

Está bastante claro para qué está destinado este método. Pero, ¿qué sucede si el repositorio no contiene un usuario con una identificación dada? ¿Podría arrojar una excepción o tal vez devuelva nulo?

Segundo ejemplo

class UserRepository {
     Optional<User> findById(long id);
}

Ahora, ¿qué sucede aquí si no hay un usuario con una identificación dada? Correcto, obtienes la instancia Optional.absent () y puedes verificarlo con Optional.isPresent (). La firma del método con Opcional es más explícita sobre su contrato. Es inmediatamente claro cómo se supone que debe comportarse el método.

También tiene un beneficio al leer el código del cliente:

User user = userRepository.findById(userId).get();

He entrenado mi ojo para ver .get () como un código de olor inmediato. Significa que el cliente ignora a propósito el contrato del método invocado. Es mucho más fácil ver esto que sin Opcional, porque en ese caso no se sabe inmediatamente cuál es el contrato del método llamado (si permite nulo o no en su devolución), por lo que debe verificarlo primero.

Opcional se usa con la convención de que los métodos con el tipo de retorno Opcional nunca devuelven nulo y generalmente también con la convención (menos fuerte) ese método sin el tipo de retorno Opcional nunca devuelve nulo (pero es mejor anotarlo con @Nonnull, @NotNull o similar) .

qbd
fuente
3

Un le Optional<T>permite tener "falla" o "ningún resultado" como un valor de respuesta / retorno válido para sus métodos (piense, por ejemplo, en una búsqueda en la base de datos). Usar un en Optionallugar de usar nullpara indicar la falla / ningún resultado tiene algunas ventajas:

  • Comunica claramente que el "fracaso" es una opción. El usuario de su método no tiene que adivinar si nullpodría ser devuelto.
  • El valor null, al menos en mi opinión, no debe usarse para indicar nada, ya que la semántica podría no ser totalmente clara. Debe usarse para decirle al recolector de basura que el objeto al que se hace referencia anteriormente puede ser recolectado.
  • La Optionalclase proporciona muchos métodos agradables para trabajar condicionalmente con los valores (por ejemplo, ifPresent()) o definir valores predeterminados de forma transparente ( orElse()).

Desafortunadamente, no elimina por completo la necesidad de nullverificaciones en el código, ya que el Optionalobjeto en sí podría serlo null.

pansen
fuente
44
Esto no es realmente para lo que es opcional. Para informar un error, use una excepción. Si no puede hacer eso, utilice un tipo que contiene ya sea un resultado o un código de error .
James Youngman
Estoy totalmente en desacuerdo. Opcional.empty () es una excelente manera de mostrar el fracaso o la inexistencia en mi opinión.
LegendLength
@LegendLength, debo estar totalmente en desacuerdo en caso de falla. Si una función falla, será mejor que me dé una explicación, no solo un vacío, "Fallé"
Winston Ewert
Lo siento, estoy de acuerdo, quiero decir 'falla esperada' ya que no encuentro a un empleado con el nombre 'x' durante una búsqueda.
LegendLength
3

Además de las otras respuestas, la otra gran ventaja de los opcionales cuando se trata de escribir código limpio es que puede usar una expresión Lambda en el caso de que el valor esté presente, como se muestra a continuación:

opTr.ifPresent(tr -> transactions.put(tr.getTxid(), tr));
William Dunne
fuente
2

Considere el siguiente método:

void dance(Person partner)

Ahora veamos un código de llamada:

dance(null);

Esto provoca un bloqueo potencialmente difícil de rastrear en otro lugar, porque danceno esperaba un valor nulo.

dance(optionalPerson)

Esto provoca un error de compilación, porque dance esperaba un PersonnoOptional<Person>

dance(optionalPerson.get())

Si no tuviera una persona, esto ocasiona una excepción en esta línea, en el punto donde intenté convertirla Optional<Person>en una Persona de manera ilegítima . La clave es que, a diferencia de pasar un valor nulo, la excepción se traza hasta aquí, no en alguna otra ubicación del programa.

Para poner todo junto: el uso indebido opcional produce problemas más fáciles de rastrear, el mal uso de nulo causa problemas difíciles de rastrear.

Winston Ewert
fuente
1
Si está lanzando excepciones cuando Optional.get()su código está mal escrito, casi nunca debería llamar a Opcional.get sin verificar Optional.isPresent()primero (como se indica en el OP) o podría escribir optionalPersion.ifPresent(myObj::dance).
Chris Cooper
@QmunkE, sí, solo debe llamar a Optional.get () si ya sabe que opcional no está vacío (ya sea porque llamó a isPresent o porque su algoritmo lo garantiza). Sin embargo, me preocupa que las personas comprueben ciegamente el presente y luego ignoren los valores ausentes, creando una gran cantidad de fallas silenciosas que serán difíciles de rastrear.
Winston Ewert
1
Me gustan los opcionales. Pero diré que los punteros nulos viejos y simples rara vez son un problema en el código del mundo real. Casi siempre fallan cerca de la línea de código ofensiva. Y creo que las personas exageran cuando hablan de este tema.
LegendLength
@LegendLength, mi experiencia es que el 95% de los casos son fácilmente rastreables para identificar de dónde viene el NULL. Sin embargo, el 5% restante es extremadamente difícil de rastrear.
Winston Ewert
-1

En Objective-C, todas las referencias a objetos son opcionales. Debido a esto, el código que publicaste es una tontería.

if (variable != nil) {
    [variable doThing];
}

Código como el anterior es inaudito en Objective-C. En cambio, el programador simplemente:

[variable doThing];

Si variablecontiene un valor, se doThingle llamará. Si no, no hay daño. El programa lo tratará como un no-op y continuará ejecutándose.

Este es el beneficio real de los opcionales. No tiene que ensuciar su código if (obj != nil).

Daniel T.
fuente
¿No es eso solo una forma de fallar silenciosamente? En algunos casos, es posible que le importe que el valor sea nulo y desee hacer algo al respecto.
LegendLength
-3

if (opcional.is Presente ()) {

El problema obvio aquí es que si "opcional" realmente está faltante (es decir, es nulo), entonces el código se va a volar con un NullReferenceException (o similar) tratando de llamar a cualquier método en una referencia de objeto nulo!

Es posible que desee escribir un método estático de clase Helper que determine la nulidad de cualquier tipo de objeto dado, algo como esto:

function isNull( object o ) 
{
   return ( null == o ); 
}

if ( isNull( optional ) ) ... 

o, quizás, más útilmente:

function isNotNull( object o ) 
{
   return ( null != o ); 
}

if ( isNotNull( optional ) ) ... 

Pero, ¿alguno de estos es realmente más legible / comprensible / mantenible que el original?

if ( null == optional ) ... 
if ( null != optional ) ... 
Phill W.
fuente