¿Deberían los getters de Java 8 devolver el tipo opcional?

288

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

¿Un método getter que devuelve el Optional<Foo>tipo en lugar del clásico es Foouna buena práctica? Suponga que el valor puede ser null.

leonprou
fuente
8
Aunque es probable que esto atraiga respuestas obstinadas, es una buena pregunta. Espero una respuesta con hechos reales sobre el tema.
Justin
8
La pregunta es si la nullablitity es inevitable. Un componente puede tener una propiedad que puede ser nula, pero aún así, el programador que usa ese componente puede decidir mantener estrictamente esa propiedad no null. Entonces el programador no debería tener que lidiar con Optionaleso. O, en otras palabras, nullrealmente representa la ausencia de un valor como con el resultado de una búsqueda (donde Optionalsea ​​apropiado) o es nullsolo un miembro del conjunto de valores posibles.
Holger
1
Consulte también la discusión sobre @NotNullanotaciones: stackoverflow.com/q/4963300/873282
koppor

Respuestas:

515

Por supuesto, las personas harán lo que quieran. Pero tuvimos una clara intención al añadir esta característica, y era no ser un tipo de propósito general Tal vez, tanto como muchas personas se nos han gustado hacerlo. Nuestra intención era proporcionar un mecanismo limitado para los tipos de retorno de métodos de biblioteca en los que debía existir una forma clara de representar "ningún resultado", y el uso nullde este tipo era muy probable que causara errores.

Por ejemplo, probablemente nunca debería usarlo para algo que devuelva 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 valor de retorno para getters definitivamente sería un uso excesivo.

Opcional no tiene nada de malo que deba evitarse, simplemente no es lo que muchas personas desean que sea, y en consecuencia estábamos bastante preocupados por el riesgo de un celoso uso excesivo.

(Anuncio de servicio público: NUNCA llame a Optional.getmenos que pueda probar que nunca será nulo; en su lugar, use uno de los métodos seguros como orElseo ifPresent. En retrospectiva, deberíamos haber llamado getalgo como getOrElseThrowNoSuchElementExceptiono algo que dejó mucho más claro que este era un método altamente peligroso eso minó todo el propósito de Optionalla lección aprendida (ACTUALIZACIÓN: Java 10 tiene Optional.orElseThrow(), que es semánticamente equivalente a get(), pero cuyo nombre es más apropiado).

Brian Goetz
fuente
24
(Con respecto a la última parte) ... y cuando estamos seguros, que el valor nunca es lo nullque podríamos usar orElseThrow(AssertionError::new), ejem o orElseThrow(NullPointerException::new)...
Holger
25
¿Qué hubiera hecho de otra manera si su intención hubiera sido introducir un tipo de propósito general Tal vez o Algún tipo? ¿Hay alguna forma en que Opcional no se ajuste a la factura de uno, o es solo que la introducción de Opcionales en una nueva API lo haría no Java-ish?
David Moles
22
Por "tipo de propósito general", me refería a incorporarlo en el sistema de tipos del lenguaje, en lugar de proporcionar una clase de biblioteca que lo aproximara. (Algunos idiomas tienen tipos para T? (T o nulo) y T! (T no anulable)) Opcional es solo una clase; No podemos hacer conversiones implícitas entre Foo y Opcional <Foo> como podríamos tener con el soporte de idiomas.
Brian Goetz
11
Me he preguntado por un tiempo por qué el énfasis en el mundo de Java estaba en Opcional, y no en un mejor análisis estático. Opcional tiene algunas ventajas, pero la gran ventaja que nulltiene es la compatibilidad con versiones anteriores; Map::getdevuelve un nulo V, no un Optional<V>, y eso nunca va a cambiar. Sin embargo, podría haber sido anotado fácilmente @Nullable. Ahora tenemos dos formas de expresar la falta de valor, además de menos incentivos para que realmente se lleve a cabo un análisis estático, lo que parece una peor posición para estar.
yshavit
21
No tenía idea de que usarlo como valor de retorno para una propiedad no era lo que pretendían hasta que leí esta respuesta aquí en StackOverflow. De hecho, me hicieron creer que era exactamente lo que pensaban después de leer este artículo en el sitio web de Oracle: oracle.com/technetwork/articles/java/… Gracias por aclararlo
Jason Thompson,
73

Después de investigar un poco por mi cuenta, me he encontrado con varias cosas que podrían sugerir cuándo 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 no es reemplazar todas las referencias nulas . En cambio, 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 lo obliga a desenvolver activamente un Opcional para lidiar con la ausencia de un valor ". - ¿ Cansado de excepciones de puntero nulo? ¡Considere usar Java SE 8's 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)
  • en DTO (mismo motivo)
  • en parámetros de entrada de métodos
  • en los parámetros del constructor "

Lo que también parece plantear algunos puntos válidos.

No pude encontrar connotaciones negativas o banderas rojas para sugerir que Optionaldebería evitarse. Creo que la idea general es, si es útil o mejora la usabilidad de su API, úsela.

Justin
fuente
11
stackoverflow.com/questions/25693309 - parece que Jackson ya lo admite, por lo que "no serializable" ya no pasa como una razón válida :)
Vlasec
1
Sugiero que su respuesta responda por qué usar Optionalparámetros de entrada de métodos (más específicamente constructores) "no nos comprará nada". dolszewski.com/java/java-8-optional-use-cases contiene una buena explicación.
Gili
Descubrí que curry resultados opcionales que deben transformarse en otras API requiere un parámetro opcional. El resultado es una API bastante comprensible. Ver stackoverflow.com/a/31923105/105870
Karl the Pagan el
1
El enlace está roto. ¿Podría por favor actualizar la respuesta?
softarn
2
El problema es que a menudo no mejora la API, a pesar de las mejores intenciones del desarrollador. He sido una colección de ejemplos de malos usos de Opcional , todos tomados del código de producción, que puedes consultar.
MiguelMunoz
20

Yo diría que en general es una buena idea usar el tipo opcional para valores de retorno que pueden ser anulables. Sin embargo, supongo que reemplazar los getters clásicos por tipos opcionales causará muchos problemas al trabajar con frameworks (por ejemplo, Hibernate) que dependen de convenciones de codificación para getters y setters.

Claas Wilke
fuente
14
Este consejo es exactamente lo que quise decir con "nos preocupa el riesgo de un uso excesivo celoso" en stackoverflow.com/a/26328555/3553087 .
Brian Goetz
13

La razón que Optionalse agregó 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 admitir la programación funcional , que se agregó a Java al mismo tiempo. (El ejemplo viene por cortesía de un blog de Brian Goetz . Un mejor ejemplo podría usar el orElse()método, ya que este código arrojará una excepción de todos modos, pero se obtiene la imagen).

Pero ahora, las personas usan Opcional por una razón muy diferente. Lo están utilizando para abordar una falla en el diseño del lenguaje. La falla es esta: no hay forma de especificar cuál de los parámetros de una API y los valores de retorno pueden ser nulos. Puede mencionarse en los javadocs, pero la mayoría de los desarrolladores ni siquiera escriben javadocs para su código, y no muchos verifican los javadocs mientras escriben. Por lo tanto, esto conduce a una gran cantidad de código que siempre verifica los valores nulos antes de usarlos, a pesar de que a menudo no pueden ser nulos porque ya se validaron repetidamente nueve o diez veces en la pila de llamadas.

Creo que hubo 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. Por eso la gente hace preguntas como "¿deberían los getters devolver los opcionales?" No, probablemente no deberían, a menos que espere que se use el getter en la programación funcional, lo cual es muy poco probable. De hecho, si observa dónde se usa Opcional en la API de Java, es principalmente en las clases Stream, que son el núcleo de la programación funcional. (No lo he revisado a fondo, pero las clases Stream pueden ser el único lugar donde se usan).

Si planea usar un captador en un poco de código funcional, podría ser una buena idea tener un captador 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 falla de la API porque a) son muy detallados yb) Nunca tuvieron la intención de resolver ese problema en primer lugar.

Una solución mucho mejor a la falla de la API es el Comprobador de nulidad . Este es un procesador de anotaciones que le permite especificar qué parámetros y valores de retorno pueden ser nulos al anotarlos con @Nullable. De esta forma, el compilador puede escanear el código y determinar si un valor que realmente puede ser nulo se está pasando a un valor donde no está permitido nulo. Por defecto, se supone que nada está permitido a ser nulo a menos que esté anotado así. De esta manera, no tiene que preocuparse por los valores nulos. Pasar un valor nulo a un parámetro dará como resultado un error del compilador. Probar un objeto para nulo que no puede ser nulo 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 captadores, no use Opcional. Y trate de diseñar sus clases para que ninguno de los miembros pueda ser nulo. Y tal vez intente agregar el Comprobador de nulidad a su proyecto y declarar sus captadores y parámetros de establecimiento @Nullable si lo necesitan. Solo he hecho esto con nuevos proyectos. Probablemente produce muchas advertencias en proyectos existentes escritos con muchas pruebas superfluas para nulo, por lo que puede ser difícil adaptarlo. Pero también atrapará muchos errores. Me encanta. Mi código es mucho más limpio y más confiable debido a eso.

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

Anexo a la publicación original (versión 2)

Después de pensarlo mucho, de mala gana he llegado a la conclusión de que es aceptable devolver Opcional con una condición: que el valor recuperado en realidad podría ser nulo. He visto un montón de código en el que la gente devuelve rutinariamente Opcional de getters que posiblemente no pueden devolver nulo. 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 pueda ser realmente nulo, continúe y envuélvalo dentro de Opcional.

Tenga en cuenta que los métodos 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 usa Opcional. Por ejemplo, Optional.map()y Optional.flatMap()ambos toman referencias de funciones. El primero toma una referencia a un getter ordinario, y el segundo toma uno que devuelve Opcional. Entonces, no le está haciendo un favor a nadie al devolver un Opcional donde el valor no puede ser nulo.

Habiendo dicho todo eso, sigo viendo que el enfoque utilizado por Nullness Checker es la mejor manera de tratar con nulos, ya que convierten NullPointerExceptions de errores de tiempo de ejecución para compilar errores de tiempo.

Miguel Muñoz
fuente
2
Esta respuesta me parece más apropiada. Opcional solo se agregó en Java 8 cuando se agregaron secuencias. Y solo las funciones de transmisión devuelven opcionales por lo que he visto.
Archit
1
Creo que esta es una de las mejores respuestas. La gente sabe qué es opcional y cómo funciona. Pero la parte más confusa es / fue dónde usarlo y cómo usarlo. Al leer esto, despeja muchas dudas.
ParagFlume
3

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

  1. Si la capa de serialización (generalmente una base de datos) permite un nullvalor para una celda en la columna BARde la tabla FOO, entonces el captador Foo.getBar()puede regresar Optionalindicando al desarrollador que es razonable esperar que este valor sea nulo y deben manejarlo. Si el DB garantiza que el valor no será nulo, entonces el getter no debe envolver esto en un Optional.
  2. Foo.bardebería ser privatey no ser Optional. Realmente no hay razón para que sea Optionalasí private.
  3. El colocador Foo.setBar(String bar)debe tomar el tipo de bary no Optional . Si está bien usar un nullargumento, indíquelo en el comentario JavaDoc. Si no está bien usar nulluna IllegalArgumentExceptiono alguna lógica de negocios apropiada, en mi humilde opinión, es más apropiada.
  4. Los constructores no necesitan Optionalargumentos (por razones similares al punto 3). En general, solo incluyo argumentos en el constructor que no deben ser nulos en la base de datos de serialización.

Para hacer que lo anterior sea más eficiente, es posible que desee editar sus plantillas IDE para generar captadores y las plantillas correspondientes para toString(), equals(Obj o)etc. , o usar campos directamente para ellos (la mayoría de los generadores IDE ya se ocupan de nulos).

marca
fuente