Usos para Opcional

Habiendo estado usando Java 8 ahora por más de 6 meses, estoy bastante contento con los nuevos cambios de la API. Un área en la que todavía no estoy seguro es cuándo usar Optional . Parece que me muevo entre querer usarlo en todas partes, algo puede ser null y no llegar a nada.

Parece que hay muchas situaciones en las que podría usarlo, y nunca estoy seguro de si agrega beneficios (legibilidad / seguridad nula) o simplemente causa una sobrecarga adicional.

Entonces, tengo algunos ejemplos, y estaría interesado en las opiniones de la comunidad sobre si Optional es beneficioso.

1 – Como un método público return type cuando el método podría devolver null :

 public Optional findFoo(String id); 

2 – Como parámetro de método cuando el parámetro puede ser null :

 public Foo doSomething(String id, Optional barOptional); 

3 – Como miembro opcional de un frijol:

 public class Book { private List pages; private Optional index; } 

4 – En Collections :

En general, no creo:

 List<Optional> 

agrega algo, especialmente porque uno puede usar filter() para eliminar valores null , etc., pero ¿hay algún buen uso para Optional en las colecciones?

¿Alguno de los casos que me he perdido?

El punto principal de Optional es proporcionar un medio para que una función devuelva un valor para indicar la ausencia de un valor de retorno. Vea esta discusión . Esto permite que la persona que llama continúe una cadena de llamadas a métodos fluidos.

Esto coincide más estrechamente con el caso de uso n . ° 1 en la pregunta del PO. Aunque, la ausencia de un valor es una formulación más precisa que nula, ya que algo como IntStream.findFirst nunca podría devolver nulo.

Para el caso de uso n.º 2 , al pasar un argumento opcional a un método, esto podría funcionar, pero es bastante torpe. Supongamos que tiene un método que toma una cadena seguida de una segunda cadena opcional. Aceptar un Optional como el segundo arg daría como resultado un código como este:

 foo("bar", Optional.of("baz")); foo("bar", Optional.empty()); 

Incluso aceptar null es más agradable:

 foo("bar", "baz"); foo("bar", null); 

Probablemente lo mejor es tener un método sobrecargado que acepte un único argumento de cadena y proporcione un valor predeterminado para el segundo:

 foo("bar", "baz"); foo("bar"); 

Esto tiene limitaciones, pero es mucho mejor que cualquiera de los anteriores.

Los casos de uso n. ° 3 y n . ° 4 , que tienen un campo Optional en una clase o en una estructura de datos, se consideran un uso indebido de la API. En primer lugar, va en contra del objective principal de diseño de Optional como se indica en la parte superior. En segundo lugar, no agrega ningún valor.

Hay tres formas de lidiar con la ausencia de un valor en Optional : proporcionar un valor sustituto, llamar a una función para proporcionar un valor sustituto o lanzar una excepción. Si está almacenando en un campo, lo haría en la inicialización o tiempo de asignación. Si está agregando valores a una lista, como se mencionó en el PO, tiene la opción adicional de simplemente no agregar el valor, lo que significa “aplanamiento” de los valores ausentes.

Estoy seguro de que alguien podría inventar algunos casos artificiales en los que realmente desea almacenar un Optional en un campo o una colección, pero en general, es mejor evitar hacerlo.

Llego tarde al juego, pero por lo que sea, quiero agregar mis 2 centavos. Va en contra del objective de diseño de Optional , que está bien resumido por la respuesta de Stuart Marks , pero aún estoy convencido de su validez (obviamente).

Use Opcional en todas partes

En general

Escribí una publicación de blog completa sobre el uso de Optional pero básicamente se trata de esto:

  • diseñe sus clases para evitar la opcionalidad siempre que sea posible
  • en todos los casos restantes, el valor predeterminado debería ser usar Optional lugar de null
  • posiblemente haga excepciones para:
    • variables locales
    • devolver valores y argumentos a métodos privados
    • bloques de código crítico de rendimiento (sin conjeturas, use un generador de perfiles)

Las primeras dos excepciones pueden reducir la sobrecarga percibida de envolver y desenvolver referencias en Optional . Se eligen de tal manera que un nulo nunca puede pasar legalmente un límite de una instancia a otra.

Tenga en cuenta que esto casi nunca permitirá s Optional en colecciones que es casi tan malo como null s. Simplemente no lo hagas. 😉

En cuanto a tus preguntas

  1. Sí.
  2. Si la sobrecarga no es una opción, sí.
  3. Si otros enfoques (subclases, decoración, …) no son una opción, sí.
  4. ¡Por favor no!

Ventajas

Hacer esto reduce la presencia de null s en su código base, aunque no los erradica. Pero ese ni siquiera es el punto principal. Hay otras ventajas importantes:

Clarifica la intención

El uso de Optional expresa claramente que la variable es, bueno, opcional. Cualquier lector de tu código o consumidor de tu API será golpeado en la cabeza con el hecho de que puede no haber nada allí y que es necesario un control antes de acceder al valor.

Elimina la incertidumbre

Sin Optional el significado de una ocurrencia null no está claro. Podría ser una representación legal de un estado (ver Map.get ) o un error de implementación como una inicialización faltante o fallida.

Esto cambia drásticamente con el uso persistente de Optional . Aquí, ya la ocurrencia de null significa la presencia de un error. (Porque si se permitiera que faltara el valor, se habría utilizado un Optional .) Esto hace que la depuración de una excepción de puntero nulo sea mucho más fácil ya que la pregunta sobre el significado de este null ya está respondida.

Más controles nulos

Ahora que ya nada puede ser null , esto se puede aplicar en todas partes. Ya sea con anotaciones, afirmaciones o verificaciones simples, nunca tendrá que pensar si este argumento o ese tipo de devolución puede ser nulo. No puede!

Desventajas

Por supuesto, no hay una bala de plata …

Actuación

Envolver valores (especialmente primitivos) en una instancia adicional puede degradar el rendimiento. En bucles estrechos esto puede ser notable o incluso peor.

Tenga en cuenta que el comstackdor podría evitar la referencia extra para las vidas de corta vida de Optional s. En Java 10 , los tipos de valores pueden reducir aún más o eliminar la penalización.

Publicación por entregas

Optional no es serializable, pero una solución no es demasiado complicada.

Invariancia

Debido a la invariancia de los tipos generics en Java, ciertas operaciones se vuelven engorrosas cuando el tipo de valor real se inserta en un argumento de tipo genérico. Aquí se da un ejemplo (ver “Polimorfismo paramétrico”) .

Personalmente, prefiero usar la herramienta de inspección de código de IntelliJ para usar las @NotNull y @Nullable ya que son en gran parte tiempo de comstackción (puede tener algunas comprobaciones de tiempo de ejecución) Esto tiene una sobrecarga menor en términos de legibilidad de código y rendimiento de tiempo de ejecución. No es tan riguroso como el uso de Opcional, sin embargo, esta falta de rigor debe estar respaldada por pruebas de unidades decentes.

 public @Nullable Foo findFoo(@NotNull String id); public @NotNull Foo doSomething(@NotNull String id, @Nullable Bar barOptional); public class Book { private List pages; private @Nullable Index index; } List<@Nullable Foo> list = .. 

Esto funciona con Java 5 y no es necesario ajustar y desenvolver valores. (o crea objetos de envoltura)

Creo que el Guava opcional y su página wiki lo explica bastante bien:

Además del aumento en la legibilidad que proviene de dar un nombre nulo, la mayor ventaja de Opcional es su prueba de idiotez. Te obliga a pensar activamente sobre el caso ausente si deseas que tu progtwig compile en absoluto, ya que debes desenvolver activamente el Opcional y abordar ese caso. Null hace que sea inquietantemente fácil simplemente olvidar cosas, y aunque FindBugs nos ayuda, no creemos que aborde el problema casi tan bien.

Esto es especialmente relevante cuando devuelve valores que pueden o no estar “presentes”. Es mucho más probable que usted (y otros) olviden que otro método (a, b) podría devolver un valor nulo de lo que probablemente olviden que a podría ser nulo cuando implemente otro método. La devolución opcional hace imposible que las personas que llaman olviden ese caso, ya que tienen que desenvolver el objeto ellos mismos para que compile su código. – (Fuente: Wiki de Guava – Usar y evitar nulo – ¿Cuál es el punto? )

Optional agrega algunos gastos generales, pero creo que su clara ventaja es hacer explícito que un objeto puede estar ausente e impone que los progtwigdores manejen la situación. ¡Previene que alguien olvide al amado != null Verificación != null .

Tomando el ejemplo de 2 , creo que es un código mucho más explícito para escribir:

 if(soundcard.isPresent()){ System.out.println(soundcard.get()); } 

que

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

Para mí, el Optional captura mejor el hecho de que no hay tarjeta de sonido presente.

Mi 2 ¢ sobre tus puntos:

  1. public Optional findFoo(String id); – No estoy seguro de esto. Quizás devuelva un Result que podría estar vacío o contener un Foo . Es un concepto similar, pero no realmente un Optional .
  2. public Foo doSomething(String id, Optional barOptional); – Preferiría @Nullable y una comprobación de Findbugs, como en la respuesta de Peter Lawrey – vea también esta discusión .
  3. Su ejemplo de libro: no estoy seguro de si usaría el Opcional internamente, eso podría depender de la complejidad. Para la “API” de un libro, usaría un Optional getIndex() para indicar explícitamente que el libro podría no tener un índice.
  4. No lo usaría en colecciones, más bien no permitiría valores nulos en colecciones

En general, trataría de minimizar el pasar alrededor de null s. (Una vez quemado …) Creo que vale la pena encontrar las abstracciones apropiadas e indicar a los compañeros progtwigdores lo que representa cierto valor de retorno.

Desde el tutorial de Oracle :

El objective de Opcional no es reemplazar cada referencia nula en su base de código, sino ayudar a diseñar mejores API en las que, con solo leer la firma de un método, los usuarios puedan decir si esperan un valor opcional. Además, Opcional te obliga a desenvolver activamente un Opcional para lidiar con la ausencia de un valor; como resultado, protege su código contra excepciones de puntero nulo no intencionadas.

Aquí hay un uso interesante (creo) para … Pruebas.

Tengo la intención de poner a prueba en gran medida uno de mis proyectos y, por lo tanto, construir afirmaciones; solo hay cosas que debo verificar y otras que no.

Por lo tanto, construyo cosas para afirmar y uso una afirmación para verificarlas, así:

 public final class NodeDescriptor { private final Optional label; private final List> children; private NodeDescriptor(final Builder builder) { label = Optional.fromNullable(builder.label); final ImmutableList.Builder> listBuilder = ImmutableList.builder(); for (final Builder element: builder.children) listBuilder.add(element.build()); children = listBuilder.build(); } public static  Builder newBuilder() { return new Builder(); } public void verify(@Nonnull final Node node) { final NodeAssert nodeAssert = new NodeAssert(node); nodeAssert.hasLabel(label); } public static final class Builder { private String label; private final List> children = Lists.newArrayList(); private Builder() { } public Builder withLabel(@Nonnull final String label) { this.label = Preconditions.checkNotNull(label); return this; } public Builder withChildNode(@Nonnull final Builder child) { Preconditions.checkNotNull(child); children.add(child); return this; } public NodeDescriptor build() { return new NodeDescriptor(this); } } } 

En la clase NodeAssert, hago esto:

 public final class NodeAssert extends AbstractAssert, Node> { NodeAssert(final Node actual) { super(Preconditions.checkNotNull(actual), NodeAssert.class); } private NodeAssert hasLabel(final String label) { final String thisLabel = actual.getLabel(); assertThat(thisLabel).overridingErrorMessage( "node's label is null! I didn't expect it to be" ).isNotNull(); assertThat(thisLabel).overridingErrorMessage( "node's label is not what was expected!\n" + "Expected: '%s'\nActual : '%s'\n", label, thisLabel ).isEqualTo(label); return this; } NodeAssert hasLabel(@Nonnull final Optional label) { return label.isPresent() ? hasLabel(label.get()) : this; } } 

¡Lo que significa que la afirmación realmente solo se dispara si quiero verificar la etiqueta!

Ya hay varios recursos sobre esta característica:

  • ¿Cansado de las excepciones del puntero nulo? Considere usar Java SE 8’s opcional
  • Tipo de opción : “un tipo polimórfico que representa la encapsulación de un valor opcional”
  • Java 8 Opcional: Cómo usarlo
  • El diseño de opcional
  • Monadic Java
  • Procesamiento de datos con Java SE 8 Streams

Parece Optional solo es útil si el tipo T en Opcional es un tipo primitivo como int , long , char , etc. Para las clases “reales”, no tiene sentido para mí, ya que puedes usar un valor null todos modos.

Creo que fue tomado de aquí (o de otro concepto de lenguaje similar).

Nullable

En C # este Nullable se introdujo hace mucho tiempo para ajustar los tipos de valor.

No creo que Opcional sea un sustituto general de los métodos que potencialmente devuelven valores nulos.

La idea básica es: la ausencia de un valor no significa que potencialmente esté disponible en el futuro. Es una diferencia entre findById (-1) y findById (67).

La información principal de Opcionales para la persona que llama es que no puede contar con el valor otorgado, pero puede estar disponible en algún momento. Quizás desaparecerá nuevamente y volverá más tarde una vez más. Es como un interruptor de encendido / apagado. Usted tiene la “opción” de encender o apagar la luz. Pero no tienes opción si no tienes una luz para encender.

Así que me parece demasiado complicado introducir Opcionales en todas partes donde se devolvió potencialmente nulo previamente. Seguiré usando null, pero solo en áreas restringidas como la raíz de un árbol, la inicialización diferida y los métodos de búsqueda explícitos.

Java SE 8 presenta una nueva clase llamada java.util.Optional,

Puede crear un Opcional u Opcional vacío con valor nulo.

 Optional emptyOptional= Optional.empty(); 

Y aquí hay un Opcional con un valor no nulo:

 String valueString = new String("TEST"); Optional optinalValueString = Optional.of(valueString ); 

Hacer algo si hay un valor presente

Ahora que tiene un objeto Opcional, puede acceder a los métodos disponibles para tratar explícitamente la presencia o ausencia de valores. En lugar de tener que recordar hacer una comprobación nula, de la siguiente manera:

 String nullString = null; if(nullString != null){ System.out.println(nullString ); } 

Puede usar el método ifPresent () de la siguiente manera:

 Optional optinalString= null; optinalString.ifPresent(System.out::println); package optinalTest; import java.util.Optional; public class OptionalTest { public Optional getOptionalNullString() { return null; // return Optional.of("TESt"); } public static void main(String[] args) { OptionalTest optionalTest = new OptionalTest(); Optional> optionalNullString = Optional.ofNullable(optionalTest.getOptionalNullString()); if (optionalNullString.isPresent()) { System.out.println(optionalNullString.get()); } } } 

Si no te gusta revisar de esta manera

 if(obj!=null){ } 

entonces puedes hacerlo de esta manera, eso es todo lo que veo

 if(Optional.ofNullable(obj).isPresent()){ } 

Básicamente, es una ilusión de que manejas los valores nulos de manera más efectiva.