El libro "Java moderno. Expresiones Lambda, flujos y programación funcional "

imagen Hola habrozhiteli! La ventaja de las aplicaciones modernas está en las soluciones avanzadas, que incluyen microservicios, arquitecturas reactivas y transmisión de datos. Las expresiones Lambda, los flujos de datos y el tan esperado sistema de módulos de la plataforma Java simplifican enormemente su implementación.

El libro lo ayudará a aprender nuevas funciones de complementos modernos, como Streams API y el sistema de módulos de la plataforma Java. Descubra nuevos enfoques de competitividad y aprenda cómo los conceptos de funcionalidad mejoran el trabajo con código.

En este libro: • Nuevas características de Java • Transmisión de datos y programación reactiva • Sistema de módulo de plataforma Java.

Extracto Capítulo 11. Clase opcional como la mejor alternativa a nulo


Levante la mano si durante su carrera como desarrollador de Java alguna vez recibió una NullPointerException. Déjelo en alto si esta excepción es la más común que ha encontrado. Desafortunadamente, no te vemos ahora, pero es muy probable que levantes tu mano. También sospechamos que puede estar pensando algo como: “Sí, estoy de acuerdo. Excepciones NullPointerException es un dolor de cabeza para cualquier desarrollador de Java, ya sea un principiante o un experto. Pero de todos modos no se puede hacer nada con ellos, este es el precio que pagamos por usar un diseño tan conveniente y probablemente inevitable como enlaces vacíos ". Esta es una opinión común en el mundo de la programación (imperativa); sin embargo, quizás esta no sea toda la verdad, sino más bien un prejuicio profundamente arraigado.

El especialista en informática británico Tony Hoare, que creó enlaces nulos en 1965, mientras desarrollaba el lenguaje ALGOL W, uno de los primeros lenguajes de programación mecanografiados con registros para los que se asignó memoria en el montón, admitió más tarde que hizo esto " solo por la facilidad de implementación ". Aunque quería garantizar "la seguridad total de usar una excepción para enlaces vacíos, pensó que esta era la forma más conveniente de simular la ausencia de un valor". Muchos años después, lamentó esta decisión, calificándola como "mi error de mil millones de dólares". Todos vimos los resultados de esta decisión. Por ejemplo, podemos verificar el campo de un objeto para determinar si representa uno de los dos valores posibles, solo para descubrir que no estamos verificando un objeto, sino un puntero nulo, e inmediatamente obtenemos esta molesta NullPointerException.

De hecho, Hoar podría haber subestimado el costo total de arreglar millones de desarrolladores de errores causados ​​por enlaces vacíos en los últimos 50 años. De hecho, la gran mayoría de los lenguajes de programación1 creados en las últimas décadas, incluido Java, se basan en la misma decisión de diseño, quizás por razones de compatibilidad con lenguajes más antiguos o (más probablemente), como dijo Hoar, "simplemente por la facilidad de implementación ". Comenzamos demostrando un ejemplo simple de los problemas encontrados cuando se usa nulo.

11.1 Cómo simular una falta de valor


Imagine que tiene la estructura de objetos anidados en el Listado 11.1 para el propietario de un automóvil que compró un seguro de automóvil.

Listado 11.1. Modelo de datos de persona / automóvil / seguro

public class Person { private Car car; public Car getCar() { return car; } } public class Car { private Insurance insurance; public Insurance getInsurance() { return insurance; } } public class Insurance { private String name; public String getName() { return name; } } 

¿Cuál crees que es el problema con el siguiente código?

 public String getCarInsuranceName(Person person) { return person.getCar().getInsurance().getName(); } 

Este código parece bastante razonable, pero muchas personas no tienen automóviles, entonces, ¿cuál es el resultado de llamar al método getCar en este caso? A menudo (y en vano) devuelven un enlace vacío para indicar la ausencia de un valor (en este caso, para indicar la ausencia de una máquina). Como resultado, llamar al método getInsurance devolverá un seguro de enlace en blanco, lo que provocará que se produzca una NullPointerException en tiempo de ejecución y el programa se detenga. Pero eso no es todo. Pero, ¿qué pasa si el objeto persona era nulo? ¿Qué pasa si getInsurance también devuelve nulo?

11.1.1. Reduzca NullPointerException con comprobación de seguridad


¿Cómo evitar una inesperada excepción NullPointerException? Por lo general, puede agregar comprobaciones nulas donde lo necesite (y, a veces, exceder los requisitos de programación segura y donde no lo necesite), y a menudo en diferentes estilos. Nuestro primer intento de escribir un método para evitar la generación de una NullPointerException se muestra en el Listado 11.2.

imagen

Este método busca nulos cada vez que se desreferencia una variable, devolviendo el valor de cadena "Desconocido" si al menos una de las variables encontradas en esta cadena de desreferenciación es un valor vacío. La única excepción a esta regla es que no verificamos que el nombre de la compañía de seguros sea nulo, porque sabemos que (como cualquier otra compañía) debe tener un nombre. Tenga en cuenta que logramos evitar esta última verificación solo debido al conocimiento del área temática, pero este hecho no se refleja en las clases de Java que modelan nuestros datos.

Describimos el método en el Listado 11.2 como "dudas profundas", porque se nota un patrón repetitivo: cada vez que tiene dudas, la variable no es nula, debe agregar otro bloque anidado, lo que aumenta el nivel de sangría del código. Obviamente, esta técnica no escala bien y reduce la legibilidad, por lo que es mejor probar una solución diferente. Para evitar este problema, tomemos una ruta diferente, como se muestra en el Listado 11.3.

En el segundo intento, tratamos de evitar el anidamiento profundo de bloques if utilizando una estrategia diferente: cada vez que nos topamos con una variable nula, devolvemos el valor de la cadena "Desconocido". Pero esta solución también está lejos de ser ideal; Ahora el método tiene cuatro puntos de salida diferentes, lo que complica enormemente su mantenimiento. Además, el valor predeterminado que se devuelve en el caso de nulo, la cadena "Desconocido", se repite en tres lugares y (esperamos) no contiene errores ortográficos. Para evitar esto, puede, por supuesto, mover la cadena de repetición a una constante.

imagen

Además, este proceso es propenso a errores. ¿Qué sucede si olvida verificar si una de las propiedades es nula? En este capítulo, argumentamos que usar nulo para representar una falta de valor es un enfoque fundamentalmente incorrecto. Se requiere una mejor manera de modelar la ausencia y la presencia de valor.

11.1.2. Problemas encontrados con nulo


Para resumir lo anterior, el uso de enlaces vacíos en Java conduce a los siguientes problemas, tanto teóricos como prácticos.

  • Sirve como fuente de errores. NullPointerException es la excepción más común (por un amplio margen) en Java.
  • "Infla" el código. La legibilidad empeora debido a la necesidad de llenar el código con comprobaciones nulas, a menudo profundamente anidadas.
  • No tiene sentido Carece de significado semántico, en particular, tal enfoque para modelar la ausencia de significado en un lenguaje con tipificación estática es fundamentalmente incorrecto.
  • Viola la ideología del lenguaje Java. Java siempre oculta los punteros de los desarrolladores, con una excepción: un puntero nulo.
  • Crea un espacio en el sistema de tipos. nulo no incluye ningún tipo u otra información, por lo que puede asignarse a cualquier tipo de enlace. Esto puede generar problemas al transferir nulo a otra parte del sistema donde no hay información sobre cuál debería ser este nulo inicialmente.

Como base para otras soluciones factibles en la siguiente subsección, examinamos brevemente las posibilidades que ofrecen otros lenguajes de programación.

11.1.3. Alternativas a nulo en otros lenguajes de programación


En los últimos años, lenguajes como Groovy han logrado sortear este problema mediante la introducción de un operador de llamadas seguro (?.), Diseñado para trabajar de manera segura con posibles valores nulos. Para comprender cómo se implementa este proceso en la práctica, considere el siguiente código Groovy. Recupera el nombre de la compañía de seguros en la que la persona especificada aseguró el automóvil:

def carInsuranceName = person?.car?.insurance?.name

Debe quedar claro lo que hace este código. Una persona puede no tener un automóvil, para cuya simulación asignamos nulo al enlace del automóvil del objeto de la persona. Del mismo modo, una máquina puede no tener seguro. El operador de llamadas seguras de Groovy le permite trabajar de forma segura con enlaces potencialmente vacíos sin lanzar una excepción NullPointerException, pasar el enlace vacío por la cadena de llamadas y devolver nulo si algún valor de la cadena es nulo.

Se propuso implementar una característica similar en Java 7, pero luego se decidió no hacerlo. Sin embargo, por extraño que parezca, el operador de llamada segura no es realmente necesario en Java. La primera idea de cualquier desarrollador de Java cuando encuentra una NullPointerException es solucionar rápidamente el problema agregando una instrucción if para verificar si es nulo antes de llamar a su método. Resolver el problema de manera similar, sin considerar si nulo es aceptable en esta situación particular para su algoritmo o modelo de datos, no conduce a una corrección, sino a ocultar el error. Y posteriormente para el próximo desarrollador (probablemente usted mismo en una semana o un mes) será mucho más difícil encontrar y corregir este error. De hecho, solo barres la basura debajo de la alfombra. El operador de desreferenciación segura nula de Groovy es solo una escoba más grande y poderosa, con la que puede hacer tales tonterías sin preocuparse realmente por las consecuencias.

Otros lenguajes de programación funcionales, como Haskell y Scala, consideran este problema de manera diferente. Haskell tiene un tipo Quizás, que esencialmente encapsula un valor opcional. Un objeto de tipo Quizás puede contener un valor del tipo especificado o no contener nada. Haskell carece del concepto de un enlace vacío. En Scala, para encapsular la presencia o ausencia de un valor de tipo T, se proporciona una estructura lógica similar Opción [T], que discutiremos en el capítulo 20. En este caso, debe verificar explícitamente si un valor está presente utilizando operaciones de tipo Opción, que proporciona "verificaciones nulas" . Ahora ya no es posible "olvidar verificar nulo", ya que el sistema de tipos en sí mismo requiere verificación.

Bien, estamos un poco fuera de tema, y ​​todo suena bastante abstracto. Probablemente se esté preguntando qué ofrece Java 8 en este sentido. Inspirado por la idea de un valor opcional, los creadores de Java 8 introdujeron una nueva clase, java.util.Optional! En este capítulo, mostramos cuál es la ventaja de usarlo para modelar valores potencialmente perdidos en lugar de asignarles una referencia vacía. También explicaremos por qué tal transición de nulo a opcional requiere que el programador revise la ideología de trabajar con valores opcionales en el modelo de dominio. Finalmente, exploraremos las posibilidades de esta nueva clase Opcional y proporcionaremos algunos ejemplos prácticos de su uso efectivo. Como resultado, aprenderá a diseñar API mejoradas, en las que el usuario ya comprende, a partir de la firma del método, si aquí es posible un valor opcional.

11.2 Introduciendo la clase opcional


En Java 8, bajo la influencia de los lenguajes Haskell y Scala, una nueva clase java.util.Optional ha aparecido para encapsular un valor opcional. Por ejemplo, si sabe que una persona puede o no tener un automóvil, no debe declarar el automóvil variable en la clase Persona con el tipo Automóvil y asignarle una referencia vacía si la persona no tiene un automóvil; en cambio, su tipo debería ser Opcional, como se muestra en la fig. 11.1

imagen

Dado un valor, la clase Opcional sirve como un adaptador para ello. Por el contrario, la ausencia de un valor se modela utilizando el opcional vacío devuelto por el método Opcional.empty. Este método de fábrica estático devuelve una instancia única especial de la clase Opcional. Tal vez se pregunte cuál es la diferencia entre un enlace vacío y Opcional.empty (). Semánticamente, pueden considerarse uno y lo mismo, pero en la práctica existen enormes diferencias entre ellos. Un intento de desreferenciar nulo inevitablemente conduce a una NullPointerException, y Opcional.empty () es un objeto válido y viable del tipo Opcional, al que se puede acceder cómodamente. Pronto verás cómo exactamente.

Una importante diferencia semántica práctica en el uso de objetos opcionales en lugar de nulos: declarar una variable de tipo Opcional dice claramente que se permite un valor vacío en este punto. Y viceversa, siempre usando el tipo Car y, posiblemente, a veces asignando referencias vacías a variables de este tipo, quiere decir que solo confía en su conocimiento de dominio para comprender si nulo pertenece al dominio de definición de una variable dada.

Con esto en mente, puede rehacer el modelo original del Listado 11.1. Usamos la clase Opcional, como se muestra en el Listado 11.4.

Tenga en cuenta que el uso de la clase Opcional enriquece la semántica del modelo. Un campo de tipo Opcional en la clase Persona y un campo de tipo Opcional en la clase Automóvil reflejan el hecho de que una persona puede o no tener un automóvil, así como una máquina puede o no estar asegurada.

Al mismo tiempo, declarar el nombre de la compañía de seguros como String, en lugar de Opcional, indica claramente que la compañía de seguros debe tener un nombre. Por lo tanto, sabe con certeza que obtendrá una excepción NullPointerException al desreferenciar el nombre de la compañía de seguros; No es necesario agregar una comprobación nula, ya que solo ocultaría el problema. La compañía de seguros debe tener un nombre, por lo que si se encuentra con una compañía sin nombre, debe averiguar qué está mal con los datos y no agregar un código para ocultar este hecho.

imagen

El uso constante de valores opcionales crea una distinción clara entre un valor que puede estar ausente y un valor que falta debido a un error en el algoritmo o un problema en los datos. Es importante tener en cuenta que la clase Opcional no pretende reemplazar todos los enlaces vacíos por uno solo. Su tarea es ayudar a diseñar API más comprensibles, de modo que mediante la firma del método se pueda entender si se puede encontrar un valor opcional allí. El sistema de tipos Java fuerza una opción de desempaquetado para manejar la ausencia de un valor.

Sobre los autores


Raul-Gabriel Urma es el CEO y cofundador de Cambridge Spark (Reino Unido), una comunidad educativa líder para investigadores y desarrolladores de datos. Raúl fue elegido uno de los participantes en el programa Java Champions en 2017. Ha trabajado para Google, eBay, Oracle y Goldman Sachs. Defendió su tesis en ingeniería informática en la Universidad de Cambridge. Además, posee una maestría en ingeniería del Imperial College de Londres, se graduó con honores y recibió varios premios por propuestas de racionalización. Raúl ha presentado más de 100 informes técnicos en conferencias internacionales.

Mario Fusco es ingeniero de software senior en Red Hat, involucrado en el desarrollo del núcleo Drools, el motor de reglas JBoss. Tiene una rica experiencia en el desarrollo de Java, participó (a menudo como desarrollador líder) en muchos proyectos corporativos en diversas industrias, desde los medios de comunicación hasta el sector financiero. Entre sus intereses están la programación funcional y los lenguajes específicos de dominio. Basado en estos dos pasatiempos, creó la biblioteca lambdaj de código abierto, queriendo desarrollar un DSL Java interno para trabajar con colecciones y hacer posible el uso de algunos elementos de programación funcional en Java.

Alan Mycroft es profesor en el Departamento de Informática de la Universidad de Cambridge, donde ha enseñado desde 1984. También es empleado de Robinson College, uno de los fundadores de la Asociación Europea de Lenguajes y Sistemas de Programación, y uno de los fundadores y administradores de la Fundación Raspberry Pi. Tiene títulos en matemáticas (Cambridge) e informática (Edimburgo). Alan es autor de más de 100 artículos científicos. Fue el supervisor de más de 20 candidatos a doctorado. Su investigación se ocupa principalmente del campo de los lenguajes de programación y su semántica, optimización e implementación. Durante un tiempo, trabajó en AT&T Laboratories y el departamento de investigación de Intel, y también cofundó Codemist Ltd., que lanzó el compilador de lenguaje C para la arquitectura ARM, llamado Norcroft.

»Se puede encontrar más información sobre el libro en el sitio web del editor
» Contenidos
» Extracto

Cupón de 25% de descuento para vendedores ambulantes - Java

Tras el pago de la versión en papel del libro, se envía un libro electrónico por correo electrónico.

Source: https://habr.com/ru/post/470598/


All Articles