Translate

lunes, 17 de febrero de 2025

Pattern matching para instanceof en Java


Pattern matching para instanceof es una característica introducida en Java 14 como una mejora para escribir código más conciso y seguro al realizar comprobaciones de tipo y conversiones.

Antes de Java 14, verificar si un objeto era de cierto tipo y luego convertirlo requería código repetitivo:


if (obj instanceof String) {

    String str = (String) obj;

    System.out.println("Longitud: " + str.length());

}


La coincidencia de patrones para `instanceof` elimina la necesidad del casting explícito, reduciendo la repetición:


if (obj instanceof String str) {

    System.out.println("Longitud: " + str.length());

}


La coincidencia de patrones se puede usar en estructuras más complejas como `else if`:


public void imprimir(Object obj) {

    if (obj instanceof String str) {

        System.out.println("Es un String: " + str);

    } else if (obj instanceof Integer num) {

        System.out.println("Es un Integer: " + num);

    } else {

        System.out.println("Tipo desconocido");

    }

}


La variable resultante solo está disponible dentro del bloque if, lo que evita posibles accesos indebidos:


if (obj instanceof String str) {

    System.out.println(str.length()); // Válido

}

System.out.println(str); // Error: No está definido fuera del if


La coincidencia de patrones para instanceof simplifica la escritura de código en Java, haciéndolo más expresivo y seguro. Esta funcionalidad es el primer paso hacia mejoras más avanzadas en la manipulación de tipos en el lenguaje.


Lombok @Builder: Simplificando la Creación de Objetos en Java


En el blog nunca he hablado de lombok, porque no me gusta que exista un framework que haga lo que tendria que tener el lenguaje que programamos. Pero esta funcionalidad de lombok no la conocia y me esta buena. 

La creación de objetos con múltiples atributos puede volverse tediosa y propensa a errores si se usa el enfoque tradicional de constructores o setters. Lombok ofrece la anotación @Builder, que es una solución elegante que permite generar automáticamente un patrón de construcción de objetos de manera fluida y legible.

Lombok es una librería que reduce el código repetitivo en Java mediante anotaciones que generan automáticamente métodos como getter, setter, equals, hashCode y toString. Una de sus anotaciones más útiles es @Builder, que facilita la creación de objetos sin necesidad de escribir constructores manualmente.

Para agregar Lombok, si usas Maven, agrega la siguiente dependencia:


<dependency>

    <groupId>org.projectlombok</groupId>

    <artifactId>lombok</artifactId>

    <version>1.18.26</version>

    <scope>provided</scope>

</dependency>


Si usas Gradle:


dependencies {

    compileOnly 'org.projectlombok:lombok:1.18.26'

    annotationProcessor 'org.projectlombok:lombok:1.18.26'

}


Veamos un ejemplo de una clase que tiene un builder: 


import lombok.Builder;

import lombok.Getter;

import lombok.ToString;


@Getter

@ToString

@Builder

public class Usuario {

    private String nombre;

    private String email;

    private int edad;

}

public class Main {

    public static void main(String[] args) {

        Usuario usuario = Usuario.builder()

                .nombre("Juan Pérez")

                .email("juan.perez@example.com")

                .edad(30)

                .build();

        

        System.out.println(usuario);

    }

}


Se puede personalizar el constructor y el nombre del método de construcción con @Builder, veamos un ejemplo :


@Builder(builderMethodName = "nuevoUsuario")

public class Usuario {

    private String nombre;

    private String email;

    private int edad;

}


Uso:


Usuario usuario = Usuario.nuevoUsuario()

        .nombre("Ana López")

        .email("ana.lopez@example.com")

        .edad(28)

        .build();


El uso de @Builder en Lombok simplifica la creación de objetos en Java, haciendo que el código sea más conciso, flexible y fácil de mantener. Esta anotación es especialmente útil cuando se manejan objetos con múltiples atributos opcionales, evitando la necesidad de escribir múltiples constructores sobrecargados.


Dejo link:

https://www.baeldung.com/lombok-builder

Concurrencia en Erlang parte 13


Soy una persona un poco desorganizada. Con suerte, todavía necesitas recordatorios de lo que tienes que hacer, porque vamos a escribir una de estas aplicaciones de recordatorio de eventos que te incitan a hacer cosas y te recuerdan las citas.

El primer paso es saber qué diablos estamos haciendo. "Una aplicación de recordatorios", dices. "Por supuesto", digo yo. Pero hay más. ¿Cómo planeamos interactuar con el software? ¿Qué queremos que haga por nosotros? ¿Cómo representamos el programa con procesos? ¿Cómo sabemos qué mensajes enviar?

Como dice la cita, "Caminar sobre el agua y desarrollar software a partir de una especificación son fáciles si ambos están congelados". Así que obtengamos una especificación y apeguémonos a ella. Nuestro pequeño software nos permitirá hacer lo siguiente:

  • Agregar un evento. Los eventos contienen una fecha límite (el momento en el que se debe advertir), un nombre de evento y una descripción.
  • Mostrar una advertencia cuando haya llegado el momento.
  • Cancelar un evento por nombre.

Sin almacenamiento en disco persistente. No es necesario para mostrar los conceptos arquitectónicos que veremos. Sería un fastidio para una aplicación real, pero en su lugar solo mostraré dónde se podría insertar si quisiera hacerlo y también señalaré algunas funciones útiles. Dado que no tenemos almacenamiento persistente, tenemos que poder actualizar el código mientras se está ejecutando.

La interacción con el software se realizará a través de la línea de comandos, pero debería ser posible ampliarla más adelante para que se puedan utilizar otros medios (por ejemplo, una GUI, acceso a una página web, software de mensajería instantánea, correo electrónico, etc.)

Esta es la estructura del programa que elegí para hacerlo:

Hay 5 componentes: Un cliente (1) que puede comunicarse con un servidor de eventos (2) y 3 pequeños círculos etiquetados como 'x', 'y' y 'z'. Los tres están vinculados al servidor de eventos.

Donde el cliente, el servidor de eventos y x, y y z son todos procesos. Esto es lo que cada uno de ellos puede hacer:

Servidor de eventos

  • Acepta suscripciones de clientes
  • Reenvía notificaciones de procesos de eventos a cada uno de los suscriptores
  • Acepta mensajes para agregar eventos (e iniciar los procesos x, y, z necesarios)
  • Puede aceptar mensajes para cancelar un evento y, posteriormente, matar los procesos de eventos
  • Puede ser finalizado por un cliente
  • Puede hacer que su código se vuelva a cargar a través del shell.

Cliente

Se suscribe al servidor de eventos y recibe notificaciones como mensajes. Por lo tanto, debería ser fácil diseñar un grupo de clientes que se suscriban al servidor de eventos. Cada uno de ellos podría ser potencialmente una puerta de entrada a los diferentes puntos de interacción mencionados anteriormente (GUI, página web, software de mensajería instantánea, correo electrónico, etc.)

  • Pide al servidor que agregue un evento con todos sus detalles
  • Pide al servidor que cancele un evento
  • Monitorea el servidor (para saber si se cae)
  • Apaga el servidor de eventos si es necesario

x, y y z:

  • Representan una notificación que espera ser activada (básicamente son solo temporizadores vinculados al servidor de eventos)
  • Envían un mensaje al servidor de eventos cuando se acaba el tiempo
  • Reciben un mensaje de cancelación y mueren

Tenga en cuenta que todos los clientes (mensajería instantánea, correo, etc. que no están implementados en este libro) reciben notificaciones sobre todos los eventos, y una cancelación no es algo sobre lo que advertir a los clientes. Aquí el software está escrito para usted y para mí, y se supone que solo un usuario lo ejecutará.

Esto representa cada proceso que tendremos. Al dibujar todas las flechas allí y decir que son mensajes, hemos escrito un protocolo de alto nivel, o al menos su esqueleto.

Se debe tener en cuenta que usar un proceso por evento para recordar probablemente sea excesivo y difícil de escalar en una aplicación del mundo real. Sin embargo, para una aplicación de la que será el único usuario, esto es suficiente. Un enfoque diferente podría ser usar funciones como timer:send_after/2-3 para evitar generar demasiados procesos.

sábado, 15 de febrero de 2025

Record en Java


Con la introducción de los records en Java 14 como una feature en vista previa y su estandarización en Java 16, ahora es más fácil crear clases inmutables de manera concisa y sin la sobrecarga de escribir código repetitivo.

Un record es una clase especial diseñada para almacenar datos de manera inmutable. Se encarga automáticamente de generar los métodos equals(), hashCode(), toString() y los accesores (`getters`) sin necesidad de escribir código adicional.

Veamos un ejemplo: 


public record Usuario(String nombre, String email, int edad) {}


La declaración anterior equivale a escribir lo siguiente en una clase tradicional:


public final class Usuario {

    private final String nombre;

    private final String email;

    private final int edad;

    

    public Usuario(String nombre, String email, int edad) {

        this.nombre = nombre;

        this.email = email;

        this.edad = edad;

    }

    

    public String nombre() { return nombre; }

    public String email() { return email; }

    public int edad() { return edad; }

    

    @Override

    public boolean equals(Object o) { /* Implementación automática */ }

    

    @Override

    public int hashCode() { /* Implementación automática */ }

    

    @Override

    public String toString() { /* Implementación automática */ }

}


Veamos como usar un record: 


public class Main {

    public static void main(String[] args) {

        Usuario usuario = new Usuario("Juan Pérez", "juan.perez@example.com", 30);       

        System.out.println(usuario);

    }

}


Salida:

Usuario[nombre=Juan Pérez, email=juan.perez@example.com, edad=30]


Se puede agregar métodos adicionales si es necesario:


public record Usuario(String nombre, String email, int edad) {

    public boolean esMayorDeEdad() {

        return edad >= 18;

    }

}


Los record en Java son una herramienta poderosa para definir estructuras de datos inmutables de forma concisa y eficiente. Gracias a ellos, el código es más limpio, menos propenso a errores y más fácil de mantener.


jueves, 13 de febrero de 2025

AssertJ


En el mundo de las pruebas unitarias en Java, AssertJ se ha consolidado como una de las herramientas más poderosas y expresivas para realizar aserciones. Este framework proporciona una API fluida y altamente legible que facilita la validación de resultados en las pruebas, mejorando la mantenibilidad y claridad del código.

AssertJ es una librería de aserciones para Java con una API expresiva y encadenable. Entre sus principales características se encuentran:

  • Sintaxis fluida y encadenada.
  • Mejor manejo de colecciones y excepciones.
  • Comparaciones avanzadas con objetos y fechas.
  • Integración con JUnit y TestNG.

Para utilizar AssertJ en un proyecto Maven, agrega la siguiente dependencia:


<dependency>

    <groupId>org.assertj</groupId>

    <artifactId>assertj-core</artifactId>

    <version>3.24.2</version>

    <scope>test</scope>

</dependency>



Si usas Gradle:

testImplementation 'org.assertj:assertj-core:3.24.2'


Veamos  un ejemplo de cómo realizar aserciones básicas con AssertJ:



import static org.assertj.core.api.Assertions.*;

import org.junit.jupiter.api.Test;


class AssertJExampleTest {

    @Test

    void testBasicAssertions() {

        String mensaje = "Hola AssertJ";

        assertThat(mensaje)

            .isNotNull()

            .startsWith("Hola")

            .endsWith("AssertJ")

            .contains("AssertJ");

    }

}


Otro ejemplo: 

@Test

void testNumbers() {

    int resultado = 10;

    assertThat(resultado)

        .isPositive()

        .isGreaterThan(5)

        .isLessThanOrEqualTo(10);

}


@Test

void testListAssertions() {

    List<String> nombres = List.of("Juan", "Maria", "Carlos");

    assertThat(nombres)

        .hasSize(3)

        .contains("Maria")

        .doesNotContain("Pedro")

        .containsExactly("Juan", "Maria", "Carlos");

}


Veamos Aserciones con Excepciones:


@Test

void testException() {

    assertThatThrownBy(() -> {

        throw new IllegalArgumentException("Error de prueba");

    }).isInstanceOf(IllegalArgumentException.class)

      .hasMessageContaining("Error");

}


AssertJ es una excelente opción para mejorar las pruebas unitarias en Java, ofreciendo una API intuitiva y potente que facilita la validación de resultados. Su uso ayuda a escribir pruebas más claras y mantenibles, mejorando la calidad del código.


Dejo link: https://www.baeldung.com/introduction-to-assertj

martes, 11 de febrero de 2025

Concurrencia en Erlang parte 12


Una vez entendidos los enlaces y los monitores, queda otro problema por resolver. Utilicemos las siguientes funciones del módulo linkmon.erl:


start_critic() ->

    spawn(?MODULE, critic, []).


judge(Pid, Band, Album) ->

    Pid ! {self(), {Band, Album}},

    receive

        {Pid, Criticism} -> Criticism

    after 2000 ->

        timeout

    end.


critic() ->

    receive

        {From, {"Rage Against the Turing Machine", "Unit Testify"}} ->

            From ! {self(), "They are great!"};

        {From, {"System of a Downtime", "Memoize"}} ->

            From ! {self(), "They're not Johnny Crash but they're good."};

        {From, {"Johnny Crash", "The Token Ring of Fire"}} ->

            From ! {self(), "Simply incredible."};

        {From, {_Band, _Album}} ->

            From ! {self(), "They are terrible!"}

    end,

    critic().


Ahora vamos a fingir que vamos a las tiendas a comprar música. Hay algunos álbumes que suenan interesantes, pero nunca estamos del todo seguros. Decides llamar a tu amigo, el crítico.


1> c(linkmon).                         

{ok,linkmon}

2> Critic = linkmon:start_critic().

<0.47.0>

3> linkmon:judge(Critic, "Genesis", "The Lambda Lies Down on Broadway").

"They are terrible!"


Debido a una tormenta solar (estoy tratando de encontrar algo realista aquí), la conexión se interrumpe:


4> exit(Critic, solar_storm).

true

5> linkmon:judge(Critic, "Genesis", "A trick of the Tail Recursion").

timeout


Es molesto. Ya no podemos recibir críticas por los álbumes. Para mantener viva la crítica, escribiremos un proceso básico de "supervisión" cuya única función es reiniciarlo cuando deje de funcionar:


start_critic2() ->

    spawn(?MODULE, restarter, []).


restarter() ->

    process_flag(trap_exit, true),

    Pid = spawn_link(?MODULE, critic, []),

    receive

        {'EXIT', Pid, normal} -> % not a crash

            ok;

        {'EXIT', Pid, shutdown} -> % manual termination, not a crash

            ok;

        {'EXIT', Pid, _} ->

            restarter()

    end.


Aquí, el reiniciador será su propio proceso. A su vez, iniciará el proceso del crítico y, si alguna vez muere por una causa anormal, restarter/0 se repetirá y creará un nuevo crítico. Tenga en cuenta que agregué una cláusula para {'EXIT', Pid, ​​shutoff} como una forma de matar manualmente al crítico si alguna vez lo necesitamos.

El problema con nuestro enfoque es que no hay forma de encontrar el Pid del crítico y, por lo tanto, no podemos llamarlo para conocer su opinión. Una de las soluciones que Erlang tiene para resolver esto es dar nombres a los procesos.

El acto de dar un nombre a un proceso le permite reemplazar el pid impredecible por un átomo. Este átomo puede usarse exactamente como un Pid al enviar mensajes. Para darle un nombre a un proceso, se usa la función erlang:register/2. Si el proceso muere, perderá automáticamente su nombre o también puede usar unregister/1 para hacerlo manualmente. Puede obtener una lista de todos los procesos registrados con register/0 o una lista más detallada con el comando de shell regs(). Aquí podemos reescribir la función restarter/0 de la siguiente manera:

restarter() ->

    process_flag(trap_exit, true),

    Pid = spawn_link(?MODULE, critic, []),

    register(critic, Pid),

    receive

        {'EXIT', Pid, normal} -> % not a crash

            ok;

        {'EXIT', Pid, shutdown} -> % manual termination, not a crash

            ok;

        {'EXIT', Pid, _} ->

            restarter()

    end. 

Como puede ver, register/2 siempre le dará a nuestro crítico el nombre 'critic', sin importar cuál sea el Pid. Lo que debemos hacer es eliminar la necesidad de pasar un Pid desde las funciones de abstracción. Probemos esto:


judge2(Band, Album) ->

    critic ! {self(), {Band, Album}},

    Pid = whereis(critic),

    receive

        {Pid, Criticism} -> Criticism

    after 2000 ->

        timeout

    end.


Aquí, la línea Pid = whereis(critic) se utiliza para encontrar el identificador de proceso del crítico con el fin de realizar una comparación de patrones con él en la expresión de recepción. Queremos hacer una comparación con este pid, porque nos asegura que encontraremos el mensaje correcto (¡podría haber 500 de ellos en el buzón mientras hablamos!). Sin embargo, esto puede ser la fuente de un problema. El código anterior supone que el pid del crítico seguirá siendo el mismo entre las dos primeras líneas de la función. Sin embargo, es completamente plausible que suceda lo siguiente:


  1. critic ! Message

                        2. critic receives

                        3. critic replies

                        4. critic dies

  5. whereis fails

                        6. critic is restarted

  7. code crashes


O bien, también es una posibilidad:


  1. critic ! Message

                           2. critic receives

                           3. critic replies

                           4. critic dies

                           5. critic is restarted

  6. whereis picks up

     wrong pid

  7. message never matches


La posibilidad de que las cosas salgan mal en un proceso diferente puede hacer que salga mal otro si no hacemos las cosas bien. En este caso, el valor del átomo crítico se puede ver desde varios procesos. Esto se conoce como estado compartido. El problema aquí es que el valor del átomo crítico puede ser accedido y modificado por diferentes procesos prácticamente al mismo tiempo, lo que da como resultado información inconsistente y errores de software. El término común para este tipo de cosas es condición de carrera. Las condiciones de carrera son particularmente peligrosas porque dependen del momento en que ocurren los eventos. En casi todos los lenguajes concurrentes y paralelos que existen, este momento depende de factores impredecibles, como qué tan ocupado está el procesador, a dónde van los procesos y qué datos está procesando su programa.

Es posible que haya escuchado que Erlang generalmente no tiene condiciones de carrera ni interbloqueos y hace que el código paralelo sea seguro. Esto es cierto en muchas circunstancias, pero nunca suponga que su código es realmente tan seguro. Los procesos nombrados son solo un ejemplo de las múltiples formas en que el código paralelo puede salir mal.

Otros ejemplos incluyen el acceso a archivos en la computadora (para modificarlos), la actualización de los mismos registros de la base de datos desde muchos procesos diferentes, etc.

Afortunadamente para nosotros, es relativamente fácil corregir el código anterior si no asumimos que el proceso nombrado sigue siendo el mismo. En su lugar, utilizaremos referencias (creadas con make_ref()) como valores únicos para identificar mensajes. Necesitaremos reescribir la función critic/0 en critic2/0 y judge/3 en judge2/2:


judge2(Band, Album) ->

    Ref = make_ref(),

    critic ! {self(), Ref, {Band, Album}},

    receive

        {Ref, Criticism} -> Criticism

    after 2000 ->

        timeout

    end.


critic2() ->

    receive

        {From, Ref, {"Rage Against the Turing Machine", "Unit Testify"}} ->

            From ! {Ref, "They are great!"};

        {From, Ref, {"System of a Downtime", "Memoize"}} ->

            From ! {Ref, "They're not Johnny Crash but they're good."};

        {From, Ref, {"Johnny Crash", "The Token Ring of Fire"}} ->

            From ! {Ref, "Simply incredible."};

        {From, Ref, {_Band, _Album}} ->

            From ! {Ref, "They are terrible!"}

    end,

    critic2().


Y luego cambia restarter/0 para que se ajuste haciendo que genere critic2/0 en lugar de critic/0. Ahora las otras funciones deberían seguir funcionando bien. El usuario no notará ninguna diferencia. Bueno, la notará porque cambiamos el nombre de las funciones y cambiamos la cantidad de parámetros, pero no sabrá qué detalles de implementación se cambiaron y por qué fue importante. Todo lo que verá es que su código se simplificó y ya no necesita enviar un PID en las llamadas a funciones:


6> c(linkmon).

{ok,linkmon}

7> linkmon:start_critic2().

<0.55.0>

8> linkmon:judge2("The Doors", "Light my Firewall").

"They are terrible!"

9> exit(whereis(critic), kill).

true

10> linkmon:judge2("Rage Against the Turing Machine", "Unit Testify").     

"They are great!"


Y ahora, aunque eliminamos al crítico, uno nuevo volvió instantáneamente para resolver nuestros problemas. Esa es la utilidad de los procesos nombrados. Si hubiera intentado llamar a linkmon:judge/2 sin un proceso registrado, el operador ! dentro de la función habría arrojado un error de argumento incorrecto, lo que garantizaría que los procesos que dependen de los nombrados no puedan ejecutarse sin ellos.

Los átomos se pueden usar en una cantidad limitada (aunque alta). Nunca debería crear átomos dinámicos. Esto significa que los procesos nombrados deben reservarse para servicios importantes exclusivos de una instancia de la máquina virtual y procesos que deberían estar allí durante todo el tiempo que se ejecuta su aplicación.

Si necesita procesos nombrados pero son transitorios o no hay ninguno que pueda ser exclusivo de la máquina virtual, puede significar que deben representarse como un grupo. Vincularlos y reiniciarlos juntos si fallan puede ser la opción más sensata, en lugar de intentar usar nombres dinámicos.




lunes, 10 de febrero de 2025

Bloques de Texto en Java


Con la introducción de los Bloques de Texto en Java 13 y su estandarización en Java 15, la manipulación de cadenas multilínea se ha vuelto mucho más sencilla y legible. Esta característica permite definir textos sin necesidad de escapar caracteres especiales o concatenar múltiples líneas, lo que mejora la claridad del código.

Los bloques de texto son literales de cadena que pueden abarcar múltiples líneas, definidos usando tres comillas dobles ("""). Se utilizan para escribir fragmentos de texto extensos sin necesidad de concatenaciones o caracteres de escape innecesarios.

Antes de los bloques de texto, una cadena multilínea en Java debía escribirse así:

String json = "{\n" +

              "    \"nombre\": \"Juan\",\n" +

              "    \"edad\": 25\n" +

              "}";

Con los bloques de texto, el mismo código se simplifica de la siguiente manera:


String json = """

    {

        "nombre": "Juan",

        "edad": 25

    }

    """;


Los bloques de texto son especialmente útiles en consultas SQL y plantillas HTML. Por ejemplo:


String query = """

    SELECT * FROM usuarios

    WHERE edad > 18

    ORDER BY nombre;

    """;


Otro ejemplo con HTML:


String html = """

    <html>

        <body>

            <h1>Bienvenido</h1>

        </body>

    </html>

    """;

Java mantiene la indentación de los bloques de texto, pero puedes usar `stripIndent()` para eliminar espacios innecesarios.

Puedes utilizar formatted() para reemplazo de valores dinámicos dentro del bloque.

Los bloques de texto en Java ofrecen una solución elegante para manejar cadenas multilínea de forma clara y eficiente. Su uso simplifica la lectura y escritura de código, mejorando la productividad de los desarrolladores.


domingo, 9 de febrero de 2025

Interceptores en C#


Con la llegada de C# 12, una de las características más interesantes introducidas es la capacidad de interceptar llamadas a métodos mediante Interceptores. Esta funcionalidad permite modificar o analizar la ejecución de un método antes o después de su invocación, lo que resulta útil para aspectos como logging, validación, caching y manejo de excepciones.

Los interceptores son una característica que permite redirigir llamadas a métodos a una lógica personalizada en tiempo de compilación. Esto brinda un alto nivel de control sin necesidad de modificar el código fuente original.

Para implementar interceptores en C#, se usa el atributo [InterceptsLocation], que indica que un método debe reemplazar otro en una ubicación específica del código.

Supongamos que tenemos un método que realiza una operación matemática simple:


public static class MathOperations

{

    public static int Add(int a, int b) => a + b;

}


Podemos definir un interceptor que intercepte esta llamada y agregue una funcionalidad adicional, como logging:



using System.Runtime.CompilerServices;


public static class MathInterceptor

{

    [InterceptsLocation("MathOperations.cs", line: 5, column: 5)]

    public static int Add(int a, int b)

    {

        Console.WriteLine($"Interceptando llamada a Add({a}, {b})");

        return a + b;

    }

}


Para tener en cuenta: 

  • Los interceptores funcionan en tiempo de compilación y no en ejecución.
  • Solo pueden ser usados en métodos con ubicaciones explícitas dentro del código fuente.
  • Requieren compatibilidad con la infraestructura de compilación adecuada.


En mi opinión es una forma de programación por aspecto o se podria implementar con esta nueva feature. 


martes, 4 de febrero de 2025

Haciendo fácil el calculo de hashing en archivoa con java.security.MessageDigest


Java 12 introdujo una API que facilita el cálculo de resúmenes de archivos (hashing) de manera eficiente y sencilla mediante la clase java.security.MessageDigest. Esta API permite generar hashes de archivos utilizando algoritmos como SHA-256 o MD5 sin necesidad de manejar manualmente la lectura de bytes y el procesamiento del hash.

Un hash de archivo es un valor único derivado de su contenido, generado por una función hash criptográfica. Es ampliamente utilizado para:

  • Verificar la integridad de archivos.
  • Comparar grandes volúmenes de datos de manera eficiente.
  • Validar la autenticidad de descargas.

Java 12 simplificó el proceso de generación de hash de archivos mediante el uso de `MessageDigest` junto con `Files.newInputStream`.

Veamos un ejemplo de uso con SHA-256:


import java.io.IOException;

import java.nio.file.Files;

import java.nio.file.Path;

import java.security.DigestInputStream;

import java.security.MessageDigest;

import java.security.NoSuchAlgorithmException;

import java.util.HexFormat;


public class FileHashingExample {

    public static void main(String[] args) throws IOException, NoSuchAlgorithmException {

        Path filePath = Path.of("archivo.txt");

        

        String hash = calculateFileHash(filePath, "SHA-256");

        System.out.println("Hash SHA-256: " + hash);

    }

    

    public static String calculateFileHash(Path path, String algorithm) throws IOException, NoSuchAlgorithmException {

        MessageDigest digest = MessageDigest.getInstance(algorithm);

        

        try (DigestInputStream dis = new DigestInputStream(Files.newInputStream(path), digest)) {

            while (dis.read() != -1) { } // Leer completamente el archivo

        }

        

        byte[] hashBytes = digest.digest();

        return HexFormat.of().formatHex(hashBytes);

    }

}


Otra mejora en Java 12 es el método Files.mismatch, que permite comparar dos archivos y determinar la primera posición donde difieren. Esto es útil para verificaciones de integridad.


import java.nio.file.Files;

import java.nio.file.Path;

import java.io.IOException;


public class FileComparisonExample {

    public static void main(String[] args) throws IOException {

        Path file1 = Path.of("archivo1.txt");

        Path file2 = Path.of("archivo2.txt");

        

        long mismatch = Files.mismatch(file1, file2);

        

        if (mismatch == -1) {

            System.out.println("Los archivos son idénticos.");

        } else {

            System.out.println("Los archivos difieren en la posición: " + mismatch);

        }

    }

}


Esta API facilita el cálculo de hashes y la comparación de archivos, mejorando la eficiencia y seguridad del procesamiento de datos. Estas mejoras hacen que Java sea una opción aún más atractiva para tareas de integridad de archivos y validación criptográfica.


lunes, 3 de febrero de 2025

Concurrencia en Erlang parte 11


Los monitores son un tipo especial de enlace con dos diferencias:

  • son unidireccionales;
  • se pueden apilar.

Los monitores son lo que necesitas cuando un proceso quiere saber qué está pasando con un segundo proceso, pero ninguno de ellos es realmente vital para el otro.

Otra razón, como se mencionó anteriormente, es apilar las referencias. Ahora bien, esto puede parecer inútil a primera vista, pero es genial para escribir bibliotecas que necesitan saber qué está pasando con otros procesos.

Verás, los enlaces son más una construcción organizacional. Cuando diseñas la arquitectura de tu aplicación, determinas qué proceso hará qué trabajos y qué dependerá de qué. Algunos procesos supervisarán a otros, otros no podrían vivir sin un proceso gemelo, etc. Esta estructura suele ser algo fijo, conocido de antemano. Los enlaces son útiles para eso y no necesariamente deberían usarse fuera de ella.

Pero, ¿qué sucede si tienes 2 o 3 bibliotecas diferentes a las que llamas y todas necesitan saber si un proceso está activo o no? Si usaras enlaces para esto, rápidamente te encontrarías con un problema cada vez que necesitaras desvincular un proceso. Ahora bien, los enlaces no son apilables, por lo que en el momento en que desvinculas uno, los desvinculas a todos y arruinas todas las suposiciones realizadas por las otras bibliotecas. Eso es bastante malo. Por lo tanto, necesitas enlaces apilables, y los monitores son tu solución. Se pueden eliminar individualmente. Además, ser unidireccional es útil en las bibliotecas porque otros procesos no deberían tener que estar al tanto de dichas bibliotecas.

Entonces, ¿cómo se ve un monitor? Bastante fácil, configuremos uno. La función es erlang:monitor/2, donde el primer argumento es el proceso atom y el segundo es el pid:


1> erlang:monitor(process, spawn(fun() -> timer:sleep(500) end)).

#Ref<0.0.0.77>

2> flush().

Shell got {'DOWN',#Ref<0.0.0.77>,process,<0.63.0>,normal}

ok


Cada vez que un proceso que monitorizas deja de funcionar, recibirás un mensaje como este. El mensaje es {'DOWN', MonitorReference, process, Pid, ​​Reason}. La referencia está ahí para permitirte demostrar el proceso. Recuerda, los monitores son apilables, por lo que es posible dejar fuera de servicio más de uno. Las referencias te permiten rastrear cada uno de ellos de una manera única. También ten en cuenta que, al igual que con los enlaces, hay una función atómica para generar un proceso mientras lo monitorizas, spawn_monitor/1-3:


3> {Pid, Ref} = spawn_monitor(fun() -> receive _ -> exit(boom) end end).

{<0.73.0>,#Ref<0.0.0.100>}

4> erlang:demonitor(Ref).

true

5> Pid ! die.

die

6> flush().

ok


En este caso, demostramos el otro proceso antes de que se bloqueara y, por lo tanto, no teníamos rastros de que se hubiera detenido. La función demonitor/2 también existe y brinda un poco más de información. El segundo parámetro puede ser una lista de opciones. Solo existen dos, info y flush:


7> f().

ok

8> {Pid, Ref} = spawn_monitor(fun() -> receive _ -> exit(boom) end end). 

{<0.35.0>,#Ref<0.0.0.35>}

9> Pid ! die.

die

10> erlang:demonitor(Ref, [flush, info]).

false

11> flush().

ok


La opción info le indica si existía o no un monitor cuando intentó eliminarlo. Por eso la expresión 10 devolvió falso. El uso de flush como opción eliminará el mensaje DOWN del buzón si existía, lo que hará que flush() no encuentre nada en el buzón del proceso actual.




domingo, 2 de febrero de 2025

Procesamiento simultáneo de datos con Streams gracias a colectores de Teeing en Java


Java 12 introdujo una nueva funcionalidad en la API de Streams: el colector teeing, el cual permite combinar dos colectores en una sola operación y fusionar sus resultados en un solo valor. Esta característica proporciona una forma elegante y eficiente de realizar dos operaciones de recolección en paralelo sobre un mismo flujo de datos.

El colector teeing se encuentra en la clase Collectors y permite procesar un Stream<T> en dos colectores distintos. Luego, combina los resultados mediante una función de fusión.

El metodo sería el siguiente: 


public static <T, R1, R2, R> Collector<T, ?, R> teeing(

    Collector<? super T, ?, R1> downstream1,

    Collector<? super T, ?, R2> downstream2,

    BiFunction<? super R1, ? super R2, R> merger)


Los parametros son: 

- downstream1: Primer colector que procesará el flujo de datos.

- downstream2: Segundo colector que operará sobre el mismo flujo.

- merger: Función que combina los resultados de ambos colectores en un solo valor.


Supongamos que queremos calcular simultáneamente el promedio y el mínimo de una lista de números.


import java.util.List;

import java.util.stream.Collectors;


public class TeeingExample {

    public static void main(String[] args) {

        List<Integer> numbers = List.of(3, 5, 7, 2, 8, 10);

        

        var result = numbers.stream().collect(

            Collectors.teeing(

                Collectors.averagingDouble(i -> i),

                Collectors.minBy(Integer::compareTo),

                (average, min) -> "Promedio: " + average + ", Mínimo: " + min.orElseThrow()

            )

        );

        

        System.out.println(result);

    }

}

Y la salida sería: 

Promedio: 5.833333333333333, Mínimo: 2


El colector teeing en Java 12 proporciona una forma eficiente de combinar dos colectores en una sola operación de Stream, evitando iteraciones adicionales y haciendo el código más legible. Su versatilidad lo convierte en una herramienta valiosa para el procesamiento de datos en Java moderno.

sábado, 1 de febrero de 2025

Concurrencia en Erlang parte 10


Ahora volvamos a los links y procesos que mueren. La propagación de errores entre procesos se realiza a través de un proceso similar al paso de mensajes, pero con un tipo especial de mensaje llamado señales o signals. Las señales de salida son mensajes "secretos" que actúan automáticamente sobre los procesos, matándolos en la acción.

Ya he mencionado muchas veces que para ser confiable, una aplicación necesita poder matar y reiniciar un proceso rápidamente. En este momento, los enlaces están bien para hacer la parte de matar. Lo que falta es el reinicio.

Para reiniciar un proceso, necesitamos una forma de saber primero que murió. Esto se puede hacer agregando una capa sobre los enlaces con un concepto llamado procesos del sistema. Los procesos del sistema son básicamente procesos normales, excepto que pueden convertir señales de salida en mensajes normales. Esto se hace llamando a process_flag(trap_exit, true) en un proceso en ejecución. Nada dice tanto como un ejemplo, así que lo usaremos. Simplemente volveré a hacer el ejemplo de la cadena con un proceso del sistema al principio:


1> process_flag(trap_exit, true).

true

2> spawn_link(fun() -> linkmon:chain(3) end).

<0.49.0>

3> receive X -> X end.

{'EXIT',<0.49.0>,"chain dies here"}


Ahora las cosas se ponen interesantes. Volviendo a nuestros dibujos, lo que sucede ahora es más bien así:


[shell] == [3] == [2] == [1] == [0]

[shell] == [3] == [2] == [1] == *dead*

[shell] == [3] == [2] == *dead*

[shell] == [3] == *dead*

[shell] <-- {'EXIT,Pid,"chain dies here"} -- *dead*

[shell] <-- still alive!


Y este es el mecanismo que permite reiniciar rápidamente los procesos. Al escribir programas que utilizan procesos del sistema, es fácil crear un proceso cuyo único papel sea comprobar si algo muere y luego reiniciarlo cuando falle.

Por ahora, quiero volver a las funciones de excepción que vimos anteriormente y mostrar cómo se comportan en torno a los procesos que atrapan salidas. Primero, establezcamos las bases para experimentar sin un proceso del sistema. Mostraré sucesivamente los resultados de lanzamientos, errores y salidas no atrapados en procesos vecinos:

Exception source: spawn_link(fun() -> ok end)
Untrapped Result: - nothing -
Trapped Result{'EXIT', <0.61.0>, normal}
The process exited normally, without a problem. Note that this looks a bit like the result of catch exit(normal), except a PID is added to the tuple to know what processed failed.
Exception source: spawn_link(fun() -> exit(reason) end)
Untrapped Result** exception exit: reason
Trapped Result{'EXIT', <0.55.0>, reason}
The process has terminated for a custom reason. In this case, if there is no trapped exit, the process crashes. Otherwise, you get the above message.
Exception source: spawn_link(fun() -> exit(normal) end)
Untrapped Result: - nothing -
Trapped Result{'EXIT', <0.58.0>, normal}
This successfully emulates a process terminating normally. In some cases, you might want to kill a process as part of the normal flow of a program, without anything exceptional going on. This is the way to do it.
Exception source: spawn_link(fun() -> 1/0 end)
Untrapped ResultError in process <0.44.0> with exit value: {badarith, [{erlang, '/', [1,0]}]}
Trapped Result{'EXIT', <0.52.0>, {badarith, [{erlang, '/', [1,0]}]}}
The error ({badarith, Reason}) is never caught by a try ... catch block and bubbles up into an 'EXIT'. At this point, it behaves exactly the same as exit(reason) did, but with a stack trace giving more details about what happened.
Exception source: spawn_link(fun() -> erlang:error(reason) end)
Untrapped ResultError in process <0.47.0> with exit value: {reason, [{erlang, apply, 2}]}
Trapped Result{'EXIT', <0.74.0>, {reason, [{erlang, apply, 2}]}}
Pretty much the same as with 1/0. That's normal, erlang:error/1 is meant to allow you to do just that.
Exception source: spawn_link(fun() -> throw(rocks) end)
Untrapped ResultError in process <0.51.0> with exit value: {{nocatch, rocks}, [{erlang, apply, 2}]}
Trapped Result{'EXIT', <0.79.0>, {{nocatch, rocks}, [{erlang, apply, 2}]}}
Because the throw is never caught by a try ... catch, it bubbles up into an error, which in turn bubbles up into an EXIT. Without trapping exit, the process fails. Otherwise it deals with it fine.


Y eso es todo en lo que respecta a las excepciones habituales. Las cosas son normales: todo va bien. Suceden cosas excepcionales: los procesos mueren, se envían diferentes señales.

Luego está exit/2. Este es el equivalente a un arma en el proceso Erlang. Permite que un proceso mate a otro a distancia, de forma segura. Estas son algunas de las posibles llamadas:


Exception source: exit(self(), normal)
Untrapped Result** exception exit: normal
Trapped Result{'EXIT', <0.31.0>, normal}
When not trapping exits, exit(self(), normal) acts the same as exit(normal). Otherwise, you receive a message with the same format you would have had by listening to links from foreign processes dying.
Exception source: exit(spawn_link(fun() -> timer:sleep(50000) end), normal)
Untrapped Result: - nothing -
Trapped Result: - nothing -
This basically is a call to exit(Pid, normal). This command doesn't do anything useful, because a process can not be remotely killed with the reason normal as an argument.
Exception source: exit(spawn_link(fun() -> timer:sleep(50000) end), reason)
Untrapped Result** exception exit: reason
Trapped Result{'EXIT', <0.52.0>, reason}
This is the foreign process terminating for reason itself. Looks the same as if the foreign process called exit(reason) on itself.
Exception source: exit(spawn_link(fun() -> timer:sleep(50000) end), kill)
Untrapped Result** exception exit: killed
Trapped Result{'EXIT', <0.58.0>, killed}
Surprisingly, the message gets changed from the dying process to the spawner. The spawner now receives killed instead of kill. That's because kill is a special exit signal. More details on this later.
Exception source: exit(self(), kill)
Untrapped Result** exception exit: killed
Trapped Result** exception exit: killed
Oops, look at that. It seems like this one is actually impossible to trap. Let's check something.
Exception source: spawn_link(fun() -> exit(kill) end)
Untrapped Result** exception exit: killed
Trapped Result{'EXIT', <0.67.0>, kill}
Now that's getting confusing. When another process kills itself with exit(kill) and we don't trap exits, our own process dies with the reason killed. However, when we trap exits, things don't happen that way.


Aunque se pueden atrapar la mayoría de las razones de salida, hay situaciones en las que se puede querer asesinar brutalmente un proceso: tal vez uno de ellos esté atrapando salidas pero también está atascado en un bucle infinito, sin leer ningún mensaje. La razón de eliminación actúa como una señal especial que no se puede atrapar. Esto garantiza que cualquier proceso que se termine con ella realmente estará muerto. Por lo general, matar es un último recurso, cuando todo lo demás ha fallado.

Como la razón de eliminación nunca se puede atrapar, se debe cambiar a matar cuando otros procesos reciben el mensaje. Si no se cambiara de esa manera, todos los demás procesos vinculados a ella morirían a su vez por la misma razón de eliminación y, a su vez, matarían a sus vecinos, y así sucesivamente. Se produciría una cascada de muertes.

Esto también explica por qué exit(kill) parece muerto cuando se recibe de otro proceso vinculado (la señal se modifica para que no se produzca una cascada), pero sigue pareciendo muerto cuando se atrapa localmente.

Si todo esto le parece confuso, no se preocupe. Muchos programadores sienten lo mismo. Las señales de salida son un poco curiosas. Afortunadamente, no hay muchos más casos especiales que los descritos anteriormente. Una vez que los comprenda, podrá comprender la mayor parte de la gestión de errores concurrentes de Erlang sin problemas.


miércoles, 29 de enero de 2025

Expresiones Switch en Java


Con Java 12, Oracle introdujo una nueva funcionalidad en el lenguaje: las expresiones switch, como parte de una característica en fase de vista previa. Esta adición busca mejorar la legibilidad y reducir la verbosidad del código al trabajar con estructuras switch.

En versiones anteriores de Java, la estructura switch era exclusivamente una sentencia, lo que significa que no devolvía un valor. Con la introducción de las expresiones switch, ahora puedes usar switch como una expresión que devuelve un valor, simplificando significativamente códigos comunes y eliminando la necesidad de manejar variables auxiliares.


Vea,mos un ejemplo básico de expresión switch


En versiones anteriores de Java:


int day = 3;

String dayName;

switch (day) {

    case 1:

        dayName = "Lunes";

        break;

    case 2:

        dayName = "Martes";

        break;

    case 3:

        dayName = "Miércoles";

        break;

    default:

        dayName = "Día inválido";

        break;

}

System.out.println(dayName);


Con expresiones switch en Java 12:


int day = 3;

String dayName = switch (day) {

    case 1 -> "Lunes";

    case 2 -> "Martes";

    case 3 -> "Miércoles";

    default -> "Día inválido";

};

System.out.println(dayName);


La nueva sintaxis también admite bloques de código más complejos, usando llaves, veamos un ejemplo:


int number = 5;

String parity = switch (number % 2) {

    case 0 -> {

        System.out.println("Es un número par.");

        yield "Par";

    }

    case 1 -> {

        System.out.println("Es un número impar.");

        yield "Impar";

    }

    default -> throw new IllegalStateException("Valor inesperado: " + number % 2);

};

System.out.println("El número es " + parity);


Las expresiones switch introducidas en Java 12 y estabilizada en Java 14, representan un paso importante hacia la modernización del lenguaje, ofreciendo una sintaxis más concisa y fácil de usar. Si bien inicialmente estaban en fase de vista previa, su adopción completa en versiones posteriores las convierte en una herramienta esencial para los desarrolladores Java modernos.


Concurrencia en Erlang parte 9


Un enlace es un tipo específico de relación que se puede crear entre dos procesos. Cuando se establece esa relación y uno de los procesos muere debido a un error o salida inesperados, el otro proceso vinculado también muere.

Este es un concepto útil desde la perspectiva de no detener los errores lo antes posible: si el proceso que tiene un error se bloquea pero los que dependen de él no, entonces todos estos procesos dependientes tienen que lidiar con la desaparición de una dependencia. Dejar que mueran y luego reiniciar todo el grupo suele ser una alternativa aceptable. Los enlaces nos permiten hacer exactamente esto.

Para establecer un enlace entre dos procesos, Erlang tiene la función primitiva link/1, que toma un Pid como argumento. Cuando se llama, la función creará un enlace entre el proceso actual y el identificado por Pid. Para deshacerse de un enlace, usamos unlink/1. Cuando uno de los procesos vinculados se bloquea, se envía un tipo especial de mensaje, con información relativa a lo que sucedió. No se envía dicho mensaje si el proceso muere por causas naturales (léase: termina de ejecutar sus funciones). Primero veamos esta nueva función como parte de linkmon.erl:


myproc() ->

    timer:sleep(5000),

    exit(reason).


Si ejecutamos las siguiente función (y espera 5 segundos entre cada comando de generación), debería ver que el shell se bloquea por "razón" solo cuando se haya establecido un vínculo entre los dos procesos.


1> c(linkmon).

{ok,linkmon}

2> spawn(fun linkmon:myproc/0).

<0.52.0>

3> link(spawn(fun linkmon:myproc/0)).

true

** exception error: reason


No se puede capturar con un try... catch el mensaje de error como de costumbre. Se deben utilizar otros mecanismos para hacer esto. 

Es importante señalar que los enlaces se utilizan para establecer grupos más grandes de procesos que deberían morir todos juntos:


chain(0) ->

    receive

        _ -> ok

    after 2000 ->

        exit("chain dies here")

    end;

chain(N) ->

    Pid = spawn(fun() -> chain(N-1) end),

    link(Pid),

    receive

        _ -> ok

    end.


Esta función tomará un entero N, iniciará N procesos vinculados entre sí. Para poder pasar el argumento N-1 al siguiente proceso de "cadena" (que llama a spawn/1), envuelvo la llamada dentro de una función anónima para que ya no necesite argumentos. Llamar a spawn(?MODULE, chain, [N-1]) habría hecho un trabajo similar.

Aquí, tendremos muchos procesos vinculados entre sí, que morirán cuando cada uno de sus sucesores salga:


4> c(linkmon).               

{ok,linkmon}

5> link(spawn(linkmon, chain, [3])).

true

** exception error: "chain dies here"


Y como puedes ver, el shell recibe la señal de muerte de algún otro proceso. Aquí hay una representación dibujada de los procesos generados y los enlaces que se caen:


[shell] == [3] == [2] == [1] == [0]

[shell] == [3] == [2] == [1] == *dead*

[shell] == [3] == [2] == *dead*

[shell] == [3] == *dead*

[shell] == *dead*

*dead, error message shown*

[shell] <-- restarted


Después de que el proceso que ejecuta linkmon:chain(0) muere, el error se propaga a lo largo de la cadena de enlaces hasta que el proceso de shell muere por ello. El fallo podría haber ocurrido en cualquiera de los procesos enlazados; como los enlaces son bidireccionales, solo es necesario que uno de ellos muera para que los demás sigan su ejemplo.

Si deseamos matar otro proceso desde el shell, podemos utilizar la función exit/2, que se llama de esta manera: exit(Pid, Reason). 

Los enlaces no se pueden apilar. Si llama a link/1 15 veces para los mismos dos procesos, solo seguirá existiendo un enlace entre ellos y una sola llamada a unlink/1 será suficiente para borrarlo.

Es importante tener en cuenta que link(spawn(Function)) o link(spawn(M,F,A)) ocurren en más de un paso. En algunos casos, es posible que un proceso muera antes de que se haya establecido el enlace y luego provoque un comportamiento inesperado. Por este motivo, se ha añadido al lenguaje la función spawn_link/1-3. Esta función toma los mismos argumentos que spawn/1-3, crea un proceso y lo vincula como si link/1 hubiera estado allí, excepto que todo se realiza como una operación atómica (las operaciones se combinan como una sola, que puede fallar o tener éxito, pero nada más). Esto generalmente se considera más seguro y también ahorra un conjunto de paréntesis.