GUIA JAVA ORACLE ONE
1. Clases. Objetos. Metodos. Funciones.
Clases Anonimas
2. Modificadores de acceso (public, privet) ya sea accessible para paquetes o
solo para las clases de funciones. Igualmente, clases Privadas o publicas.
3. Void & Return:
La vuelta de una función ocurre cuando se ha ejecutado la última sentencia
de la función y, conceptualmente, se encuentra la llave }, del final de la
función.
Todas las funciones, excepto aquellas de tipo void, devuelven un valor.
Este valor se especifica explícitamente en la sentencia return. Si no la
sentencia return, el valor devuelto por la función queda técnicamente
indefinido. En otras palabras mientras una función no se declare como void
4. Herencias. Una ClaseB depende de ClaseA. O bien, se podría decir que
ClaseB es hijo (extendes) de ClaseA. Lo cual quiere decir que la Clase A, va a
utilizar las funciones o métodos de la clase B para funcionar.
En el código anterior, la clase Cuenta es la superclase y la clase
CuentaAhorro es la subclase. La clase CuentaAhorro hereda los atributos y métodos
de la clase Cuenta, y agrega un nuevo atributo tasaDeIntereses y un nuevo método
calcularIntereses. Aunque los atributos son heredados, como se declararon como
private en la superclase, no pueden ser accedidos directamente en la subclase, por
lo que se deben utilizar los métodos getters/setters, que son públicos.
También observe en el código anterior que la subclase sobrescribe el método
sacar para descontar la tarifa de retiro, además de usar la palabra clave super para
llamar al método de la superclase, evitando duplicar un código ya existente. Esta es
la gran ventaja de la herencia: la reutilización de código con la flexibilidad para
sobrescribir comportamientos.
5. Interfaces e Implements.
En el polimorfismo de interfaces, múltiples clases implementan la misma
interfaz y proporcionan su propia implementación de los métodos definidos
en la interfaz. Esto permite tratar objetos de diferentes clases de manera
intercambiable. Aquí tienes un ejemplo:
En estos ejemplos, el polimorfismo permite que objetos de diferentes clases sean
tratados de manera uniforme, lo que promueve la flexibilidad y la reutilización del
código.
Las Interfaces funcionales, pueden solo contener UN SOLO METODO de tipo
abstracto, a menos que se declaren métodos de tipo: Static void, el cual se
considera como una parte funciónal de la interfaz y no se considera como un
metodo abstracto, default void contienen un implemento por defecto en la
interfaz y no se considera como un método abstracto
6. Consumo de API. Record. Utilizar Postman y bajar el Gson y Jar de JSON
para que el programa pueda leer (transformar la informacion) que ya esta en
JSON a Java. Record es una clase básica que ya contiene Constructores,
Getters, Setter, y demás contenido primordial para solo mostrarse en
pantalla cuando sea utilizado. Es importante recordar que Record NO hace
ninguna acción. Solo muestra información.
7. Override (ToString) para sobreescribir el valor que puede devolver un
Hash, o sea, devuelve la dirección hexadecimal de la información. Para evitar
esto, se usa el ToString, mostrándolos ya sea con Setter o Getters.
8. Parse o ValueOf . Convierte un valor, por ejemplo, de tipo String, a un tipo
entero. Lo cual quiere decir que transforma los datos ya existentes, al tipo de
datos que se solicita.
9. Framework Spring Maven:
@JsonAlias y @JsonProperty son anotaciones de Jackson, una biblioteca Java
para procesar JSON, que ayudan a mapear propiedades de clase a campos
JSON.
Sin embargo, tienen funciones diferentes. ¿Vamos a conocer mejor cada una
de estas anotaciones?
@JsonProperty
Esta anotación se utiliza para definir el nombre de la propiedad JSON
asociada al campo Java.
Cuando el JSON se serializa, es decir, se convierte de objetos Java a JSON, el
nombre especificado en @JsonProperty se utilizará como la clave para el
campo en el JSON de salida. De manera similar, cuando el JSON se deserializa
(se convierte de JSON a objetos Java), la biblioteca busca el nombre
especificado en @JsonProperty para mapear el valor JSON al campo Java.
Entonces, si deseas usar un nombre diferente en el JSON, puedes utilizar
@JsonProperty("nombreEnJson"). Esto es útil, por ejemplo, cuando necesitas
trabajar con un JSON que sigue una convención de nomenclatura diferente a
la de tu clase Java.
Por ejemplo, si tienes una clase Java con la propiedad "nombreCompleto" y
deseas que se mapee al JSON como "nombre", puedes usar
@JsonProperty("nombre") para especificar el nombre correcto en el JSON:
@JsonAlias
@JsonAlias se utiliza para definir uno o más alias para el nombre de la
propiedad JSON asociada al campo Java.
Al deserializar, @JsonAlias permite que la biblioteca encuentre el valor JSON
correspondiente, incluso si el nombre de la propiedad en el JSON no coincide
exactamente con el nombre del campo Java.
Esto es útil cuando trabajas con diferentes versiones de un JSON o cuando
deseas permitir que una propiedad sea referenciada por nombres diferentes.
Por ejemplo, si tienes una clase Java con la propiedad "nombreCompleto" y el
JSON usa "nombre" en su lugar, puedes usar @JsonAlias("nombre") para
mapear correctamente la propiedad. De esta manera, tanto
"nombreCompleto" como "nombre" serán aceptados al realizar el mapeo:
Documentación Jackson: [Link]
10. Generics: En Java, los genéricos permiten crear clases, interfaces y
métodos que pueden trabajar con tipos desconocidos o parámetros
genéricos. Proporcionan una forma de escribir código flexible y reutilizable,
haciéndolo independiente de tipos específicos y permitiendo que funcione
con diferentes tipos de datos.
Para crear una clase o método genérico, necesitas usar parámetros de tipo
(tipos genéricos) que se representan entre corchetes angulares <>. Por lo
general, usamos letras mayúsculas únicas para representar los tipos
genéricos, pero puedes usar cualquier identificador válido en Java. Aquí
tienes un ejemplo de una clase genérica llamada Caja, que almacena un
valor de un tipo desconocido:
En el ejemplo anterior, podemos crear un objeto del tipo Caja y almacenar
cualquier tipo de valor en él. Aquí tienes un ejemplo:
Observa que podemos utilizar la clase Caja para incluir valores de diferentes
tipos. Para la variable cajaDeTexto, el compilador garantizará que solo se
puedan almacenar valores del tipo String. Para la variable cajaDeEdad, el
compilador garantizará que solo se puedan almacenar valores del tipo
Integer, y así sucesivamente.
Método Genérico
Para crear un método genérico, puedes usar la misma sintaxis con
parámetros de tipo entre corchetes angulares. Aquí tienes un ejemplo de un
método genérico, que pertenece a la clase Caja y que debería sumar el valor
pasado por parámetro al contenido de la caja:
La finalidad del método anterior es realizar la suma entre el contenido actual
de la caja ([Link]) y el valor pasado como parámetro (valor). El
método es genérico y puede usarse para diferentes tipos de contenido que
pueden sumarse, como Integer, Double y String.
Veamos paso a paso lo que sucede:
public <T> T sumaContenidoEnCaja(T valor) {
El método es genérico y recibe un parámetro valor del tipo genérico T, que es
el mismo tipo que se devolverá como resultado de la suma.
Luego, el método comienza con una serie de condicionales if que verifican el
tipo del contenido actual de la caja ([Link]) y el tipo del valor pasado
como parámetro (valor).
La verificación se realiza utilizando los operadores instanceof y los
operadores de coincidencia de patrones (instanceof con variables de patrón)
disponibles a partir de Java 16.
Si el contenido actual ([Link]) y el valor (valor) son ambos del mismo
tipo, se realiza la suma o la concatenación, como en el caso de String. Si el
tipo de la variable valor es diferente al tipo del contenido, devolvemos el
valor anterior del contenido. Veamos cómo quedaría en nuestra clase
TesteaCaja:
Al ejecutar el código anterior, obtendremos la siguiente salida en nuestro
terminal:
Guardando texto en mi caja!
Otra línea
56
501.0
null
Observa que en la última línea del código, al intentar incluir un String "texto"
en nuestra cajaDeValor, al ejecutar este código obtuvimos un retorno nulo, ya
que solo realizamos la suma si ambos tipos eran iguales.
InstanceOf: [Link]
instanceof-en-java-MCGKP3Z2V77RD4ZKVJYG3ABC5CB4?language=es
Contenido Spring
11. Colecciones.
Las colecciones son estructuras de datos fundamentales en Java que nos permiten
almacenar y manipular conjuntos de elementos de manera eficiente. Están
implementadas a través de la API de colecciones de Java, que forma parte del
paquete [Link]. Las colecciones proporcionan una variedad de clases e interfaces
para almacenar y organizar datos de diferentes maneras, atendiendo a diversas
necesidades y escenarios.
La API de colecciones de Java incluye interfaces, clases abstractas y clases
concretas para representar diferentes tipos de colecciones. Algunas de las
interfaces principales de colecciones son:
List: Una colección ordenada que permite elementos duplicados. Los
elementos se acceden mediante índices.
Set: Una colección que no permite elementos duplicados y normalmente no
tiene un orden definido.
Queue: Una colección que representa una cola, donde los elementos se
agregan al final y se eliminan del principio.
Map: Una colección de pares clave-valor, donde cada clave es única y
mapeada a un valor correspondiente.
Además de estas interfaces, la API de colecciones también incluye varias clases
concretas que implementan estas interfaces, como ArrayList, LinkedList, HashSet,
TreeSet, HashMap, TreeMap, entre otras.
Una de las interfaces más utilizadas es List. Como se mencionó anteriormente, List
es una interfaz que define una secuencia ordenada de elementos, donde cada
elemento tiene una posición específica. Al declarar una variable de este tipo,
indicamos que trabajaremos con una colección que mantiene el orden de los
elementos.
Una de las principales razones para usar List es la flexibilidad que ofrece. Podemos
agregar, eliminar y acceder a elementos de manera fácil e intuitiva. Además,
también permite la duplicación de elementos, es decir, podemos tener elementos
repetidos en la colección. Otra ventaja es la posibilidad de recorrer los elementos de
manera secuencial, utilizando bucles de repetición como el for-each, lo que facilita
la manipulación de los elementos y la realización de operaciones en masa.
Aquí tienes un ejemplo práctico del uso de List en un código Java:
Además, Java también tiene otras colecciones como Set y Map. Set es una colección
que no permite elementos duplicados, mientras que Map es una colección que
asocia claves a valores, permitiendo la recuperación rápida de un elemento a través
de su clave.
Las colecciones de Java son extremadamente útiles en diversas situaciones, como
almacenar datos en memoria, realizar operaciones de búsqueda, ordenación y
filtrado. Nos ayudan a organizar y manipular grandes cantidades de datos de
manera eficiente y elegante.
12. Constantes
Al declarar una constante, es importante seguir algunas buenas prácticas. La
nomenclatura de las constantes debe ser clara y descriptiva, utilizando letras
mayúsculas y separando las palabras con guiones bajos (_), siguiendo el
patrón conocido como "snake_case". Esto hace que el código sea más legible
y comprensible para otros desarrolladores.
En Java, podemos declarar una constante utilizando la palabra clave final. Por
ejemplo:
En este ejemplo, AÑO_ACTUAL y NOMBRE_EMPRESA son constantes que
almacenan un valor entero y una cadena, respectivamente. El uso de la
palabra clave final indica que estas variables no pueden cambiar su valor
después de la asignación inicial.
Además, es una buena práctica declarar las constantes como static si
pertenecen a una clase y se comparten entre varios objetos. De esta manera,
las constantes pueden accederse directamente a través del nombre de la
clase, sin la necesidad de instanciar un objeto.
Java
En el ejemplo anterior, las constantes ANHO_ATUAL y NOMBRE_EMPRESA se
declaran como static, lo que permite que se acceda directamente a ellas a
través de la clase EjemploConstantes. Por ejemplo, así es como podríamos
acceder a ellas desde nuestro método main.
Java
El uso de constantes aporta beneficios como facilitar el mantenimiento del
código, evitar errores de escritura y hacer que el código sea más legible.
Además, ayudan a evitar la repetición de valores en diferentes partes del
código, promoviendo la consistencia y la reutilización.
Recuerda que las constantes deben utilizarse para valores que no deben
modificarse durante la ejecución del programa.
13. Lambdas
Las funciones lambda son un recurso muy útil en el lenguaje de
programación Java. Pueden ayudar a simplificar nuestro código y hacerlo más
fácil de leer y entender. Pero antes de avanzar, comencemos desde el
principio.
¿Qué son las funciones Lambda?
Las funciones Lambda, también llamadas funciones anónimas, son una forma
de definir funciones que se utilizan con frecuencia solo una vez, directamente
en el lugar donde se van a utilizar.
En la programación convencional, normalmente definimos una función en
algún lugar de nuestro código y luego la llamamos por su nombre en otro
lugar, siempre que necesitemos usarla.
Sin embargo, a veces necesitamos una función que se utilice solo en un lugar
de nuestro código. Para estos casos, puede ser más simple y directo definir
esta función directamente en el lugar donde se utilizará, sin darle un nombre,
es decir, utilizando una función Lambda.
La principal ventaja del uso de funciones lambda es la simplificación del
código y la mejora en la legibilidad, ya que la función se define justo donde
se usará.
¿Cómo usamos las funciones Lambda en Java?
En Java, una función lambda se define de la siguiente manera:
Java
Por ejemplo, podemos definir una función lambda que sume dos números de
la siguiente manera:
java
Aquí, a y b son los argumentos de nuestra función. El cuerpo de la función,
que es el código que se ejecutará cuando se llame a la función, está entre
llaves {}. En este caso, el cuerpo de la función es solo una línea que
devuelve la suma de a y b.
En Java, las funciones lambda se utilizan generalmente con interfaces
funcionales. Una interfaz funcional es una interfaz que contiene solo un único
método. La función lambda luego proporciona la implementación de ese
único método.
Ejemplos de uso de funciones Lambda
Vamos a un ejemplo concreto para entenderlo mejor. Supongamos que
tenemos una lista de números y queremos imprimir solo los números pares
de esa lista. Sin el uso de funciones lambda, podríamos hacer algo así:
Pero con el uso de funciones lambda, podemos simplificar este código:
En el código anterior, creamos un stream de nuestra lista, filtramos ese
stream para incluir solo los números pares (esto se hace con la función lambda i -> i
% 2 == 0), y finalmente usamos el método forEach para imprimir cada elemento
del stream filtrado.
Ejercicio de bucles dentro de bucles
Ahora, imaginemos que necesitamos buscar todas las temporadas de una
determinada serie de la API. Considerando que ya hemos buscado los datos
de la serie y los hemos alimentado en la variable datosSerie, ¿cómo
implementaríamos este recurso?
14. Streams
Los streams son una nueva forma de trabajar con colecciones de datos en
Java, introducidas a partir de Java 8. Permiten realizar operaciones de manera
más eficiente y concisa, utilizando un enfoque funcional.
Un stream es una secuencia de elementos que puede procesarse en paralelo
o en serie. Puede crearse a partir de una colección, un array, un archivo,
entre otros. A partir de ahí, podemos realizar diversas operaciones en ese
stream, como filtrar, mapear, ordenar, entre otras.
Las operaciones intermedias son aquellas que pueden aplicarse en un stream
y retornan un nuevo stream como resultado. Estas operaciones no se
ejecutan inmediatamente, sino solo cuando se llama a una operación final.
Veamos algunos ejemplos de operaciones intermedias:
Sorted. Ordenara de Menor a mayor y/o alfabeticamente
Filter: permite filtrar los elementos del stream basándose en una condición.
Por ejemplo, podemos filtrar una lista de números para devolver solo los
números pares.
Map: permite transformar cada elemento del stream en otro tipo de dato. Por
ejemplo, podemos transformar una lista de cadenas en una lista de sus
respectivas longitudes.
Las operaciones finales son aquellas que cierran el stream y devuelven un
resultado concreto. Algunas operaciones finales comunes son forEach, collect
y count.
FlatMap. Ayuda a acceder a datos anidados de arreglos. Es decir, mientras
MAP, tan solo entrega una salida en base a una entrada, FlatMap entrega
varias salidas en una entrada.
Por ejemplo, en el ejercicio de consumo OMD (Series), para realizar una lista
tipo Stream, Map solo devolvería una lista con las Temporadas, y se debería
poner mas código para invocar los capítulos. Sin embargo con FlatMap se
puede concatenar ambas informaciones.
Fuente extra: [Link]
toList. Funciona en conjunto al Stream, y solo realiza una lista Inamovible.
Es decir, una vez creada, no es posible agregarle ni quitarle información.
Collect(CollectorToList). Al contrario de toList, estas listas si se pueden
modificar
Veamos algunos ejemplos de operaciones finales:
ForEach: permite ejecutar una acción en cada elemento de la stream. Por
ejemplo, podemos imprimir cada elemento de la lista.
java
// Salida:
// Hola, Juan!
// Hola, Maria!
// Hola, Pedro!
// Hola, Ana!
Collect: permite recopilar los elementos de la stream en una colección o en
otro tipo de dato. Por ejemplo, podemos recopilar los números pares en un
conjunto.
En resumen, los streams y sus operaciones intermedias y finales son una
forma poderosa de manipular colecciones de datos en Java de manera más
eficiente y concisa. Permiten escribir código más legible y expresivo,
facilitando el procesamiento y la transformación de datos.
Además de las operaciones intermedias y finales mencionadas, hay muchas
otras disponibles, como distinct (que elimina elementos duplicados), limit
(que limita el número de elementos de la stream), skip (que salta los
primeros elementos del stream), reduce (que combina los elementos de la
stream en un único resultado) y muchas más.
Los streams en Java son una herramienta poderosa para la manipulación de
colecciones de datos. Las operaciones intermedias permiten filtrar, mapear y
ordenar los elementos del stream, mientras que las operaciones finales
cierran el stream y devuelven un resultado concreto. Combinando estas
operaciones, podemos realizar transformaciones y acciones de manera
concisa y eficiente.
15. Constructores Personalizados.
Vamos a explorar un tema muy interesante y muy útil en Java: los
Constructores Personalizados. Estos son como un comando especial que
usamos para crear e inicializar objetos de una clase. El constructor
personalizado nos permite definir cómo queremos crear nuestros objetos,
permitiéndonos inicializar las propiedades de la clase con los valores que
especificamos. En última instancia, esto nos da más control sobre la forma en
que se crean nuestros objetos.
¿Cuándo son útiles los Constructores Personalizados?
Consideremos un ejemplo simple. Supongamos que estás creando una clase
de Carro en Java, donde cada carro tiene una marca, un modelo y un año.
Sin un constructor personalizado, tendrías que crear un carro de esta
manera:
Java
Ahora, usando un constructor personalizado:
Ahora puedes crear un nuevo Carro de manera mucho más fácil y segura:
El uso de constructores personalizados asegura que todos los carros que
creamos tengan todas las propiedades necesarias definidas.
Posibles problemas al usar Constructores Personalizados A pesar de ser
útiles, los constructores personalizados deben usarse correctamente para
evitar problemas.
Un problema común es la sobrecarga de constructores, donde tienes muchos
constructores en la misma clase. Esto puede hacer que tu código sea confuso
y difícil de mantener.
Otro problema puede surgir al utilizar la herencia. Si la clase padre tiene un
constructor personalizado, la clase hija debe llamar explícitamente a ese
constructor, o el compilador devolverá un error.
Además, si no defines un constructor por defecto (un constructor sin
argumentos), Java no creará uno por ti si tienes algún constructor
personalizado. Esto puede ser un problema si necesitas crear un objeto sin
inicializar ninguna propiedad.
En resumen, los constructores personalizados en Java son una herramienta
muy útil en la creación de objetos, asegurando que todas las propiedades
necesarias se inicialicen. Sin embargo, deben usarse con cuidado para evitar
problemas de sobrecarga y herencia. Como siempre, la mejor estrategia es
usar la herramienta adecuada para el trabajo correcto y mantener tu código
simple y limpio.
La inmutabilidad de los datos es un concepto fundamental en
programación que se refiere a la característica de que un objeto o valor no
puede ser alterado después de ser creado. Esto aporta varios beneficios,
como la seguridad y confiabilidad del código, ya que los datos inmutables no
pueden ser modificados o corrompidos accidentalmente.
Además, la inmutabilidad promueve la concurrencia segura en entornos de
múltiples hilos, ya que cada hilo puede trabajar con copias de los datos
inmutables sin interferir en los demás. La inmutabilidad también hace que el
código sea más legible y facilita el mantenimiento, ya que el flujo de
información es más claro y predecible.
En Java, podemos crear objetos inmutables utilizando clases finales, atributos
finales y métodos que no modifican el estado interno del objeto. Esta práctica
es esencial para desarrollar programas más robustos y confiables.
En el video de la lección anterior, vimos que al utilizar streams, una de las
operaciones finales nos permite colocar los datos generados en otra lista.
También vimos que si utilizamos directamente toList() para este propósito, se
genera una lista inmutable. Por lo tanto, cuando necesitamos que la nueva
lista pueda modificarse, tenemos que utilizar collect([Link]()).
En Java, algunas clases nativas ya son inmutables. Algunos ejemplos
incluyen:
String: La clase [Link] es inmutable. Una vez creada, una
instancia de String no puede ser modificada.
Clases Wrapper: Las clases que representan los tipos primitivos,
como Integer, Double, Character, etc., también son inmutables.
Enum: Las enumeraciones (enum) en Java son inmutables. Las
instancias de un enum se crean solo una vez y no pueden ser
modificadas.
Además de las clases nativas, la API de colecciones de Java también
ofrece algunas clases inmutables en la clase utilitaria Collections. Estas
clases se utilizan para crear colecciones inmutables, como listas,
conjuntos y mapas. Algunos ejemplos incluyen:
[Link]: Crea una lista inmutable a partir de
una lista existente.
[Link]: Crea un conjunto inmutable a partir
de un conjunto existente.
[Link]: Crea un mapa inmutable a partir de
un mapa existente.
Estas clases te permiten crear versiones inmutables de colecciones
existentes, haciéndolas seguras para compartir y evitando la
modificación accidental.
Aquí tienes un ejemplo de código:
Trabajas desarrollando aplicaciones en un banco digital y una de tus tareas es
gestionar y analizar de manera eficiente una enorme cantidad de datos
relacionados con las inversiones y saldos de los clientes, con el fin de
ofrecerles líneas de crédito adecuadas al saldo que tienen en cuenta.
En este contexto, debes filtrar a los clientes con cuenta corriente y ordenarlos
considerando el saldo, en orden descendente, y luego mostrar los cinco
clientes con el mayor saldo en cuenta.
Dado que la variable datosClientes ya está poblada con la lista completa de
clientes y que los métodos para obtener el tipo de cuenta y el saldo son
getTipoCuenta() y getSaldo(), respectivamente, ¿cuál de los siguientes
códigos realizará la funcionalidad correctamente?
EJERCICIO.
Necesitas mostrar una lista de las 10 mejores películas lanzadas en un año
determinado. Para ello, tendrás en cuenta el total de visualizaciones, que
debe ser mayor o igual a 10 mil, y la calificación de los clientes, que debe ser
mayor o igual a 9.4. Considera la clase Pelicula a continuación:
Dado que List<Pelicula> peliculas ya está poblada con todas las películas y
deseas conocer las mejores películas de 2022, ¿cómo realizarías el código
para llevar a cabo esta tarea?
Fechas.
Para trabajar con fechas, podemos importar el paquete que contiene varias
clases de fecha y hora, el [Link]. Esta API de fechas fue introducida en
Java 8 con la idea de resolver los problemas de las antiguas clases y facilitar
el uso de fechas en Java. ¿Vamos a conocer algunas funcionalidades?
Patrones
Los formatos de fecha y hora se especifican mediante cadenas estándar de
fecha y hora. La siguiente tabla muestra la sintaxis estándar que debe
utilizarse para el patrón de formato:
Ejemplo.
Este código producirá:
sáb., 19 nov. 2022 02:58:14 BRT:
Local Date Time
Hora y fecha actuales
LocalDateTime representa una fecha con la hora, visto como año-mes-día-hora-
minuto-segundo. El tiempo se representa como [Link].
Resultado:
2022-11-19T04:11:17.108565200
Date Time Formatter
Debes haber notado que los resultados están en formatos con los que no estamos
acostumbrados a trabajar. ¡Entonces, podemos trabajar con varios formateadores
de fechas existentes!
Ejemplos de formatos de fecha y hora:
Formato Ejemplo
MM/dd/yyyy 19/11/2022
dd-M-yyyy hh:mm:ss 19-11-22 12:07:23
dd MMMM yyyy 19 de noviembre de 2022
dd MMMM yyyy zzzz 19 de noviembre de 2022, Hora estándar de Brasilia
E, dd MMM yyyy HH:mm:ss z Sáb, 19 de noviembre de 2022, 12:07:23 BRT
Además, también podemos cambiar la forma de mostrar el mes. Si usamos letras en
cantidades diferentes, el resultado también será consecuentemente diferente.
Veamos:
Tipo Por defecto Ejemplo
Mes completo MMMM Septiembre
Mes abreviado MMM Set.
Mes numérico MM 09
Para formatear, utilizaremos DateTimeFormatter. Hay varios ya preparados, pero
aún tienes la alternativa de crear tu propio formato en el patrón ya conocido de
dd/MM/yyyy hh:mm:ss.
Para hacer esto, simplemente utiliza el método ofPattern. Como vimos en las tablas
anteriores, podemos elegir cómo se imprimirá.
Ahora, podemos, a partir de nuestra fecha, en este caso hoy, llamar al método
format y pasarle este formateador:
Ahora sí, al ejecutar, obtenemos el resultado:
19/11/2022 04:38:11
16. Peek
La función "peek" es una operación intermedia de una stream. Una operación
intermedia es aquella que procesa los datos del stream pero no la finaliza, es
decir, permite que se realicen más operaciones después de ella. La función
peek se utiliza para "espiar" los elementos del stream sin alterarlos, lo que
puede ser muy útil para la depuración. Esta función recibirá cada elemento
del stream y realizará alguna operación con efecto secundario (como
imprimir el elemento), pero devolverá el mismo elemento sin cambiar nada.
¿Por qué debería usar esto?
El uso de Streams y la función peek puede ayudarte a escribir códigos más
elegantes, fáciles de leer y reducir la posibilidad de errores. Además, pueden
hacer que tu código se ejecute más rápido en ciertos casos, ya que los
Streams permiten la paralelización de las operaciones, es decir, ejecutar
varias operaciones al mismo tiempo.
Ejemplos de código
Ahora veamos un ejemplo simple de cómo podemos usar la función peek con
streams. Imagina que tenemos una lista de números y queremos multiplicar
cada uno de ellos por 2 y luego sumar los resultados.
En este código, creamos una stream con la lista de números, espiamos cada
número para imprimirlo (con la función .peek), multiplicamos el valor por dos,
volvemos a espiar para verificar el valor de la multiplicación y luego
sumamos los resultados.
Así es como aparece en la terminal después de ejecutar la aplicación:
Elemento: 1
Contenido después del map: 2
Elemento: 2
Contenido después del map: 4
Elemento: 3
Contenido después del map: 6
Elemento: 4
Contenido después del map: 8
Elemento: 5
Contenido después del map: 10
La suma de los números es: 30
Esto nos ayuda a asegurarnos de que el proceso se esté realizando
correctamente. Aquí podemos ver que la suma de los números 2 + 4 + 6 + 8
+ 10 se calculó correctamente, dando como resultado 30. Es decir, la función
lambda en nuestro método reduce está haciendo exactamente lo que se
planeó. Y pudimos verificar esto con el peek.
Este es solo un ejemplo básico, pero recuerda que la función peek se puede
usar en cualquier lugar dentro de tu stream, siempre que desees ver qué
elementos están pasando por ese punto.
EJERCICIO.
Estás trabajando como persona desarrolladora en Gatito Petshop. El proyecto
actual implica el uso de Spring Boot API y Java Streams. Durante tu actividad,
recuerdas que la función 'peek' puede ser muy útil para seguir el
procesamiento de los streams y facilitar el proceso de depuración. Decides
aplicar esta función, pero necesitas recordar cómo funciona.
¿Cómo aplicarías la función 'peek' en una lista de objetos Pet (animales) para
imprimir cada elemento mientras se procesa?
Creemos un método que puede devolver un valor null. Sin Optional, podría
causar errores no deseados, pero con Optional, es más seguro:
Aunque Optional es un aliado útil, hay algunas cosas que deben tenerse en
cuenta para usarlo de manera efectiva:
Prefiera el retorno de Optional en lugar de devolver null: Esto hace que
sus intenciones sean claras y evita errores.
No use [Link]() sin [Link](): [Link]() lanzará un
error si el valor no está presente. Por lo tanto, es mejor verificar antes
si el valor está presente.
No use Optional para campos de la clase o parámetros del método:
Optional debe usarse principalmente para retornos de métodos que
pueden no tener valor.
Comprender y usar Optional correctamente puede llevar a un código
más limpio y menos propenso a errores, así que vale la pena invertir
tiempo para aprenderlo.
Trabajar en actividades paralelas es una práctica esencial en programación,
ya que permite ejecutar varias tareas simultáneamente, aumentando la
eficiencia del código. Una de las formas de lograr la programación paralela en
Java es mediante el uso de threads.
Las threads son unidades independientes de ejecución que pueden crearse
dentro de un programa. Permiten que diferentes partes del código se
ejecuten al mismo tiempo, mejorando el rendimiento y la capacidad de
respuesta del programa.
findAny
Una operación útil para trabajar con threads es el método findAny en
colecciones de datos. Este método se utiliza para encontrar cualquier
elemento que cumpla con una condición específica en una colección.
Devuelve un Optional, que puede contener el elemento encontrado o estar
vacío si ningún elemento cumple con la condición.
Al utilizar findAny en una colección con threads, cada thread puede buscar un
elemento de la colección de manera paralela, haciendo que la búsqueda sea
más rápida y eficiente. Es importante destacar que findAny no garantiza que
siempre se devolverá el mismo elemento, ya que el orden de búsqueda
puede variar entre las threads.
Para utilizar findAny con threads, puedes crear una implementación de
Predicate que defina la condición que los elementos deben cumplir. Luego,
puedes utilizar el método parallelStream en una colección para crear un
stream paralelo y llamar a findAny, pasando el Predicate como argumento.
Veamos un ejemplo:
En este ejemplo, tenemos una lista de números del 1 al 100. Utilizando
parallelStream, filtramos los números que son múltiplos de 10 y luego utilizamos
findAny para encontrar cualquier elemento que cumpla con esta condición.
Dado que la búsqueda se realiza en paralelo, el resultado puede variar en cada
ejecución del programa, ya que diferentes threads pueden encontrar elementos
diferentes del stream. La salida en la consola podría ser, por ejemplo:
Encontrado: 70
o
Encontrado: 50
o cualquier otro número que sea múltiplo de 10 dentro de la lista.
Cabe destacar que, para algunos tipos de streams, como streams de listas o arrays,
se mantiene el orden de los elementos. Sin embargo, en un stream paralelo, la
búsqueda se realizará de manera concurrente y el resultado puede ser cualquier
elemento que cumpla con la condición de filtrado. Por lo tanto, en streams
paralelos, el método findAny puede ser más útil para obtener cualquier elemento
sin garantía de un resultado específico.
EJERCICIO
Imagina que eres un analista de datos en Bytebank Banco Digital y se te ha
encomendado monitorear el flujo de transacciones bancarias realizadas. El
banco utiliza Java Stream API para procesar estos datos. Durante el día,
ocurren una gran cantidad de operaciones, y todas se registran en tiempo
real.
Utilizando Java Stream API y el concepto de Maps, peek, findFirst y otros
recursos, ¿cómo crearías un flujo que filtre las transacciones con valores
superiores a 5000, imprima un tipo de registro con peek, busque la primera
de estas transacciones encontradas y recolecte los resultados en un Set?