¿Por qué Java ‘s Optional no debe usarse en argumentos?

He leído en muchos sitios web. Opcional debe usarse solo como un tipo de devolución, y no se usa en los argumentos del método. Estoy luchando por encontrar una razón lógica por la cual. Por ejemplo, tengo una pieza de lógica que tiene 2 parámetros opcionales. Por lo tanto, creo que tendría sentido escribir mi firma de método de esta manera (solución 1):

public int calculateSomething(Optional p1, Optional p2 { // my logic } 

Muchas páginas web especifican Opcional no se debe utilizar como argumentos de método. Con esto en mente, podría usar la siguiente firma de método y agregar un comentario claro de Javadoc para especificar que los argumentos pueden ser nulos, esperando que los mantenedores futuros lean el Javadoc y por lo tanto siempre lleven a cabo verificaciones nulas antes de usar los argumentos (solución 2) :

 public int calculateSomething(String p1, BigDecimal p2) { // my logic } 

Alternativamente, podría reemplazar mi método con cuatro métodos públicos para proporcionar una interfaz más agradable y hacer que sea más obvio que p1 y p2 son opcionales (solución 3):

 public int calculateSomething() { calculateSomething(null, null); } public int calculateSomething(String p1) { calculateSomething(p1, null); } public int calculateSomething(BigDecimal p2) { calculateSomething(null, p2); } public int calculateSomething(String p1, BigDecimal p2) { // my logic } 

Ahora bash escribir el código de la clase que invoca esta pieza de lógica para cada enfoque. Primero recupero los dos parámetros de entrada de otro objeto que devuelve Optional s y luego invoco calculateSomething . Por lo tanto, si se utiliza la solución 1, el código de llamada se vería así:

 Optional p1 = otherObject.getP1(); Optional p2 = otherObject.getP2(); int result = myObject.calculateSomething(p1, p2); 

si se utiliza la solución 2, el código de llamada se vería así:

 Optional p1 = otherObject.getP1(); Optional p2 = otherObject.getP2(); int result = myObject.calculateSomething(p1.orElse(null), p2.orElse(null)); 

si se aplica la solución 3, podría usar el código anterior o podría usar lo siguiente (pero es mucho más código):

 Optional p1 = otherObject.getP1(); Optional p2 = otherObject.getP2(); int result; if (p1.isPresent()) { if (p2.isPresent()) { result = myObject.calculateSomething(p1, p2); } else { result = myObject.calculateSomething(p1); } } else { if (p2.isPresent()) { result = myObject.calculateSomething(p2); } else { result = myObject.calculateSomething(); } } 

Entonces mi pregunta es: ¿Por qué se considera una mala práctica usar s Optional como argumentos de método (ver solución 1)? Parece la solución más legible para mí y hace que sea más obvio que los parámetros podrían estar vacíos / nulos para los futuros mantenedores. (Soy consciente de que los diseñadores de Optional pretendían que solo se usara como un tipo de devolución, pero no puedo encontrar razones lógicas para no usarlo en este escenario).

Oh, esos estilos de encoding deben tomarse con un poco de sal.

  1. (+) Pasar un resultado opcional a otro método, sin ningún análisis semántico; dejando eso al método, está bastante bien.
  2. (-) El uso de parámetros opcionales que causan una lógica condicional dentro de los métodos es literalmente contraproducente.
  3. (-) Necesitar empacar un argumento en un Opcional, es subóptimo para el comstackdor, y realiza un ajuste innecesario.
  4. (-) En comparación con los parámetros anulables Opcional es más costoso.

En general: Opcional unifica dos estados, que deben ser descifrados. Por lo tanto, es más adecuado para el resultado que para la entrada, para la complejidad del flujo de datos.

La mejor publicación que he visto sobre el tema fue escrita por Daniel Olszewski :

Aunque podría ser tentador considerar Opcional para parámetros de método no obligatorios, tal solución palidecerá en comparación con otras alternativas posibles. Para ilustrar el problema, examine la siguiente statement de constructor:

 public SystemMessage(String title, String content, Optional attachment) { // assigning field values } 

A primera vista, puede parecer una decisión de diseño correcta. Después de todo, marcamos explícitamente el parámetro de archivo adjunto como opcional. Sin embargo, en cuanto a llamar al constructor, el código del cliente puede volverse un poco torpe.

 SystemMessage withoutAttachment = new SystemMessage("title", "content", Optional.empty()); Attachment attachment = new Attachment(); SystemMessage withAttachment = new SystemMessage("title", "content", Optional.ofNullable(attachment)); 

En lugar de proporcionar claridad, los métodos de fábrica de la clase opcional solo distraen al lector. Tenga en cuenta que solo hay un parámetro opcional, pero imagine tener dos o tres. El tío Bob definitivamente no estaría orgulloso de ese código 😉

Cuando un método puede aceptar parámetros opcionales, es preferible adoptar el enfoque bien probado y diseñar dicho caso utilizando la sobrecarga de métodos. En el ejemplo de la clase SystemMessage, declarar dos constructores independientes es superior al uso de Opcional.

 public SystemMessage(String title, String content) { this(title, content, null); } public SystemMessage(String title, String content, Attachment attachment) { // assigning field values } 

Ese cambio hace que el código del cliente sea mucho más simple y fácil de leer.

 SystemMessage withoutAttachment = new SystemMessage("title", "content"); Attachment attachment = new Attachment(); SystemMessage withAttachment = new SystemMessage("title", "content", attachment); 

Casi no hay buenas razones para no usar Optional como parámetros. Los argumentos en contra de esto se basan en argumentos de la autoridad (vea a Brian Goetz – su argumento es que no podemos hacer cumplir las opciones no nulas) o que los argumentos Optional pueden ser nulos (esencialmente el mismo argumento). Por supuesto, cualquier referencia en Java puede ser nula, necesitamos alentar las reglas impuestas por el comstackdor, no por la memoria de los progtwigdores (que es problemática y no se escala).

Los lenguajes de progtwigción funcionales fomentan los parámetros Optional . Una de las mejores formas de usar esto es tener múltiples parámetros opcionales y usar liftM2 para usar una función suponiendo que los parámetros no están vacíos y devolviendo una opción (ver http://www.functionaljava.org/javadoc/4.4/functionaljava/fj /data/Option.html#liftM2-fj.F- ). Desafortunadamente, Java 8 implementó una biblioteca muy limitada que admite opcionalmente.

Como progtwigdores Java, solo deberíamos usar null para interactuar con bibliotecas heredadas.

Este consejo es una variante de la regla general “ser lo menos específico posible con respecto a las entradas y tan específico como sea posible con respecto a las salidas”.

Por lo general, si tiene un método que toma un valor plano no nulo, puede asignarlo sobre el Optional , por lo que la versión simple es estrictamente más inespecífica con respecto a las entradas. Sin embargo, hay un montón de posibles razones por las cuales desearía requerir un argumento Optional :

  • desea que su función se use junto con otra API que devuelve un Optional
  • Su función debería devolver algo que no sea un Optional vacío si el valor dado está vacío
  • Usted piensa que Optional es tan increíble que cualquiera que use su API debería ser requerido para aprender al respecto 😉

El patrón con Optional es para uno para evitar devolver null . Todavía es perfectamente posible pasar un método null .

Aunque todavía no son oficiales, puede usar anotaciones de estilo JSR-308 para indicar si acepta o no valores null en la función. Tenga en cuenta que debe tener las herramientas adecuadas para identificarlo realmente, y proporcionaría más de una verificación estática que una política de tiempo de ejecución aplicable, pero sería útil.

 public int calculateSomething(@NotNull final String p1, @NotNull final String p2) {} 

Esto me parece un poco tonto, pero la única razón por la que puedo pensar es que los argumentos del objeto en los parámetros del método ya son opcionales de alguna manera; pueden ser nulos. Por lo tanto, forzar a alguien a tomar un objeto existente y envolverlo de forma opcional es algo sin sentido.

Dicho esto, encadenar métodos juntos que tomen / devuelvan los opcionales es algo razonable que hacer, por ejemplo, quizás la mónada.

Mi opinión es que Opcional debería ser una Mónada y estas no son concebibles en Java.

En la progtwigción funcional, usted se ocupa de funciones puras y de orden superior que toman y componen sus argumentos solo en función de su “tipo de dominio comercial”. Las funciones de composición que se alimentan o cuyo cómputo debe informarse al mundo real (los denominados efectos secundarios) requieren la aplicación de funciones que se encargan de desempaquetar automáticamente los valores de las mónadas que representan el mundo exterior (Estado, Configuración, Futuros, Tal vez, O bien, Escritor, etc. …); esto se llama levantar. Puedes pensar que es una especie de separación de preocupaciones.

Mezclar estos dos niveles de abstracción no facilita la legibilidad, por lo que es mejor evitarlo.

Otra razón para ser cuidadoso cuando pasa un parámetro Optional es que un método debe hacer una cosa … Si pasa un parámetro Optional , puede favorecer hacer más de una cosa, podría ser similar a pasar un parámetro booleano.

 public void method(Optional param) { if(param.isPresent()) { //do something } else { //do some other } } 

Creo que es porque normalmente escribes tus funciones para manipular datos, y luego lo levantas a Optional usando el map y funciones similares. Esto agrega el comportamiento Optional predeterminado a él. Por supuesto, puede haber casos en los que sea necesario escribir su propia función auxiliar que funcione en Optional .

Creo que lo importante de ser es que primero debes comprobar si Optional es nulo o no y luego intentar evaluar el valor que envuelve. Demasiadas validaciones innecesarias

Las opciones no están diseñadas para este propósito, como Brian Goetz explicó muy bien.

Siempre puede usar @Nullable para indicar que un argumento de método puede ser nulo. Usar un opcional no te permite escribir la lógica de tu método de forma más clara.

La aceptación de parámetros opcionales provoca un ajuste innecesario en el nivel de la persona que llama.

Por ejemplo en el caso de:

 public int calculateSomething(Optional p1, Optional p2 {} 

Supongamos que tiene dos cadenas no nulas (es decir, devuelta por algún otro método):

 String p1 = "p1"; String p2 = "p2"; 

Estás obligado a envolverlos en Opcional incluso si sabes que no están Vacíos.

Esto empeora cuando tienes que componer con otras estructuras “mapeables”, es decir. Eithers :

 Either value = compute().right().map((s) -> calculateSomething( < here you have to wrap the parameter in a Optional even if you know it's a string >)); 

árbitro:

los métodos no deben esperar la Opción como parámetros, esto es casi siempre un olor de código que indica una fuga de flujo de control de la persona que llama al destinatario, debe ser responsabilidad de la persona que llama verificar el contenido de una Opción

árbitro. https://github.com/teamdigitale/digital-citizenship-functions/pull/148#discussion_r170862749

El uso de argumentos Optional como método también necesita una verificación null en sí misma. Lógica duplicada innecesaria. Por ejemplo:-

 void doSomething(Optional isoOpt, Optional prodOpt){ if(isoOpt != null){ isoOpt.orElse(ISOCode.UNKNOWN); } if(prodOpt != null){ prodOpt.orElseThrow(NullPointerException::new); } //more instructions } 

Imagine si aumenta el número de parámetros Optional . Si olvida la verificación nula, posiblemente se lanzará NPE en el tiempo de ejecución.

Además, si su método es público, el cliente se verá obligado a ajustar sus parámetros en Opcionales, lo que supondrá una carga, especialmente si aumenta el número de argumentos.

Un enfoque más, lo que puedes hacer es

 // get your optionals first Optional p1 = otherObject.getP1(); Optional p2 = otherObject.getP2(); // bind values to a function Supplier calculatedValueSupplier = () -> { // your logic here using both optional as state} 

Una vez que haya creado una función (proveedor en este caso), podrá pasarla como cualquier otra variable y podrá llamarla usando

 calculatedValueSupplier.apply(); 

La idea aquí es si tiene o no valor opcional será el detalle interno de su función y no estará en el parámetro. Las funciones de pensamiento cuando pienso en un parámetro opcional es en realidad una técnica muy útil que he encontrado.

En cuanto a su pregunta, si realmente debería hacerlo o no, se basa en su preferencia, pero como otros dijeron, hace que su API sea fea por decir lo menos.

Al principio, también preferí pasar Opcionales como parámetro, pero si cambia de una perspectiva API-Designer a una perspectiva de Usuario API, verá las desventajas.

Para su ejemplo, donde cada parámetro es opcional, sugeriría cambiar el método de cálculo en una clase propia de la siguiente manera:

 Optional p1 = otherObject.getP1(); Optional p2 = otherObject.getP2(); MyCalculator mc = new MyCalculator(); p1.map(mc::setP1); p2.map(mc::setP2); int result = mc.calculate(); 

Sé que esta pregunta tiene más que ver con la opinión que con hechos concretos. Pero recientemente pasé de ser un desarrollador de .net a uno de Java, así que recién me he unido a la fiesta opcional. Además, prefiero decir esto como un comentario, pero dado que mi nivel no me permite comentar, me veo obligado a poner esto como una respuesta en su lugar.

Lo que he estado haciendo, que me ha servido bien como regla general. Es usar Opcionales para los tipos de devolución, y solo usar Opcionales como parámetros, si requiero tanto el valor del Opcional, como el tiempo o no, el Opcional tenía un valor dentro del método.

Si solo me importa el valor, compruebo que estoy presente antes de llamar al método, si tengo algún tipo de registro o lógica diferente dentro del método que depende de si el valor existe, entonces con gusto pasaré el Opcional.