En caso de Java 8 getters devolver tipo opcional?


Optional el tipo introducido en Java 8 es algo nuevo para muchos desarrolladores.

¿Es una buena práctica un método getter que devuelve el tipo Optional<Foo> en lugar del clásico Foo? Supongamos que el valor puede ser null.

Author: Nayuki, 2014-10-12

5 answers

Por supuesto, la gente hará lo que quiera. Pero teníamos una clara intención al agregar esta característica, y era no para ser un tipo de propósito general Tal vez, tanto como a mucha gente le hubiera gustado que lo hiciéramos. Nuestra intención era proporcionar un mecanismo limitado para los tipos de retorno de métodos de biblioteca donde debía haber una forma clara de representar "sin resultado", y usar null para ello era abrumadoramente probable que causara errores.

Por ejemplo, probablemente nunca deberías usarlo para algo que devuelve una matriz de resultados, o una lista de resultados; en su lugar devuelve una matriz o lista vacía. Casi nunca debe usarlo como un campo de algo o un parámetro de método.

Creo que usarlo rutinariamente como un valor de retorno para los getters definitivamente sería un uso excesivo.

No hay nada incorrecto con la opción de que se debe evitar, simplemente no es lo que muchas personas desean que sea, y en consecuencia estábamos bastante preocupados por el riesgo de celo uso excesivo.

(Anuncio de servicio público: NUNCA llame a Optional.get a menos que pueda demostrar que nunca será nulo; en su lugar, use uno de los métodos seguros como orElse o ifPresent. En retrospectiva, deberíamos haber llamado get algo como getOrElseThrowNoSuchElementException o algo que hiciera mucho más claro que este era un método altamente peligroso que socavaba todo el propósito de Optional en primer lugar. Lección aprendida. (ACTUALIZACIÓN: Java 10 tiene Optional.orElseThrow(), que es semánticamente equivalente a get(), pero cuyo nombre es más apropiado.))

 358
Author: Brian Goetz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-12 12:23:55

Después de hacer un poco de investigación por mi cuenta, me he encontrado con una serie de cosas que podrían sugerir cuando esto es apropiado. La más autorizada es la siguiente cita de un artículo de Oracle:

"Es importante tener en cuenta que la intención de la clase Opcional es no reemplazar cada referencia nula. En su lugar, su propósito es ayudar a diseñar API más comprensibles para que con solo leer la firma de un método, pueda saber si puede esperar un valor opcional. Esto le obliga a desenvolver activamente una Opción para hacer frente a la ausencia de un valor." - Cansado de las Excepciones de Puntero Nulo? Considere El Uso De Java SE 8 Opcional!

También encontré este extracto de Java 8 Opcional: Cómo usarlo

"Opcional no está destinado a ser utilizado en estos contextos, ya que no nos comprará nada:

  • en la capa del modelo de dominio (no serializable)
  • in DTO (same reason)
  • en los parámetros de entrada de los métodos
  • en los parámetros del constructor "

Que también parece plantear algunos puntos válidos.

No pude encontrar ninguna connotación negativa o banderas rojas para sugerir que Optional debería evitarse. Creo que la idea general es, si es útil o mejora la usabilidad de tu API, úsala.

 59
Author: Justin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-04 20:20:28

Diría que en general es una buena idea usar el tipo opcional para valores devueltos que pueden ser nullables. Sin embargo, w.r.t. to frameworks asumo que reemplazar getters clásicos con tipos opcionales causará muchos problemas cuando se trabaja con frameworks (por ejemplo, Hibernate) que dependen de convenciones de codificación para getters y setters.

 12
Author: Claas Wilke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-12 17:43:36

La razón por la que se agregó Optional a Java es porque esto:

return Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .findFirst()
    .getOrThrow(() -> new InternalError(...));

Es más limpio que esto:

Method matching =
    Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .getFirst();
if (matching == null)
  throw new InternalError("Enclosing method not found");
return matching;

Mi punto es que Opcional se escribió para soportar la programación funcional, que se agregó a Java al mismo tiempo. (El ejemplo viene cortesía de un blog de Brian Goetz. Un mejor ejemplo podría usar el método orElse(), ya que este código lanzará una excepción de todos modos, pero se obtiene la imagen.)

Pero ahora, la gente está usando Opcional para una muy diferente motivo. Lo están usando para abordar un defecto en el diseño del lenguaje. El defecto es el siguiente: No hay forma de especificar cuáles de los parámetros y valores de retorno de una API pueden ser null. Puede ser mencionado en los javadocs, pero la mayoría de los desarrolladores ni siquiera escriben javadocs para su código, y no muchos revisarán los javadocs mientras escriben. Así que esto conduce a una gran cantidad de código que siempre comprueba los valores nulos antes de usarlos, a pesar de que a menudo no pueden ser nulos porque ya estaban validado repetidamente nueve o diez veces la pila de llamadas.

Creo que había una verdadera sed de resolver este defecto, porque muchas personas que vieron la nueva clase Opcional asumieron que su propósito era agregar claridad a las API. Es por eso que la gente hace preguntas como "¿deberían los getters devolver los Opcionales?"No, probablemente no deberían, a menos que espere que el getter se use en programación funcional, lo cual es muy poco probable. De hecho, si nos fijamos en donde Opcional se utiliza en la API de Java, es principalmente en las clases Stream, que son el núcleo de la programación funcional. (No he comprobado muy a fondo, pero las clases de flujo podrían ser el solo lugar en el que se utilizan.)

Si planea usar un getter en un poco de código funcional, podría ser una buena idea tener un getter estándar y un segundo que devuelva Opcional.

Ah, y si necesita que su clase sea serializable, no debe usar Opcional.

Los opcionales son una solución muy mala para la API falla porque a) son muy prolijas, y b) Nunca tuvieron la intención de resolver ese problema en primer lugar.

Una solución mucho mejor para el defecto de la API es el Comprobador de Nullness . Este es un procesador de anotación que le permite especificar qué parámetros y valores de retorno pueden ser nulos anotándolos con @Nullable. De esta manera, el compilador puede escanear el código y averiguar si un valor que realmente puede ser null se está pasando a un valor donde null no está permitido. Por por defecto, asume que no se permite que nada sea nulo a menos que esté anotado. De esta manera, no tiene que preocuparse por los valores nulos. Pasar un valor null a un parámetro resultará en un error del compilador. Probar un objeto para null que no puede ser null produce una advertencia del compilador. El efecto de esto es cambiar NullPointerException de un error de tiempo de ejecución a un error de tiempo de compilación.

Esto lo cambia todo.

En cuanto a sus getters, no use Opcional. Y trata de diseñar tus clases para que ninguna de los miembros posiblemente puede ser nulo. Y tal vez intente agregar el Comprobador de Nullness a su proyecto y declarar los parámetros getters y setter @Nullable si lo necesitan. Solo he hecho esto con nuevos proyectos. Probablemente produce una gran cantidad de advertencias en los proyectos existentes escritos con un montón de pruebas superfluas para null, por lo que podría ser difícil de adaptar. Pero también atrapará muchos errores. Me encanta. Mi código es mucho más limpio y más confiable debido a ello.

(También hay un nuevo lenguaje que aborda esto. Kotlin, que compila a código de bytes Java, le permite especificar si un objeto puede ser null cuando lo declara. Es un enfoque más limpio.)

Adición a la Publicación Original (versión 2)

Después de pensarlo mucho, a regañadientes he llegado a la conclusión de que es aceptable devolver Opcional con una condición: Que el valor recuperado pueda ser nulo. He visto un montón de código donde la gente devuelve rutinariamente Opcional de getters eso no puede devolver null. Veo esto como una práctica de codificación muy mala que solo agrega complejidad al código, lo que hace que los errores sean más probables. Pero cuando el valor devuelto podría ser null, siga adelante y envuélvalo dentro de un Opcional.

Tenga en cuenta que los métodos que están diseñados para la programación funcional, y que requieren una referencia de función, se escribirán (y deberían) en dos formas, una de las cuales utiliza Opcional. Por ejemplo, Optional.map() y Optional.flatMap() ambos toman referencias de función. La primera toma una referencia a un getter ordinario, y el segundo toma una que devuelve Opcional. Así que usted no está haciendo a nadie un favor por devolver un Opcional donde el valor no puede ser nulo.

Habiendo dicho todo eso, todavía veo que el enfoque utilizado por el Verificador de Nullness es la mejor manera de lidiar con los nulls, ya que convierten NullPointerExceptions de errores de tiempo de ejecución a errores de tiempo de compilación.

 4
Author: MiguelMunoz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-10-06 03:29:46

Si está utilizando serializadores modernos y otros marcos de trabajo que entienden Optional entonces he encontrado que estas pautas funcionan bien al escribir Entity frijoles y capas de dominio:

  1. Si la capa de serialización (generalmente una DB) permite un valor null para una celda en la columna BAR en la tabla FOO, entonces el getter Foo.getBar() puede devolver Optional indicando al desarrollador que este valor puede esperarse razonablemente que sea nulo y que deben manejar esto. Si el DB garantiza el valor no será nulo, entonces el getter debería no envolver esto en un Optional.
  2. Foo.bar debe ser private y no se Optional. Realmente no hay razón para que sea Optional si es private.
  3. El setter Foo.setBar(String bar), deberá tomar el tipo de bar y no Optional. Si está bien usar un argumento null entonces indícalo en el comentario JavaDoc. Si no está bien usar null un IllegalArgumentException o alguna lógica de negocio apropiada es, en mi humilde opinión, más apropiado.
  4. Los constructores no necesitan Optional argumentos (por razones similares al punto 3). Generalmente solo incluyo argumentos en el constructor que debe no ser null en la base de datos de serialización.

Para hacer lo anterior más eficiente, es posible que desee editar sus plantillas IDE para generar getters y las plantillas correspondientes para toString(), equals(Obj o) etc. o use campos directamente para esos (la mayoría de los generadores IDE ya tratan con nulls).

 2
Author: Mark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-30 19:20:32