Translate

Mostrando las entradas con la etiqueta Java. Mostrar todas las entradas
Mostrando las entradas con la etiqueta Java. Mostrar todas las entradas

miércoles, 1 de abril de 2026

Rendimiento de Consola: Linux vs Windows (¿realmente uno es más rápido?)


Hace poco me encontré con un comportamiento curioso ejecutando el siguiente código en Java:


var lista = new LinkedList<Long>();

for (var i = 0L; i < Long.MAX_VALUE; i++) {

    System.out.println(i);

    lista.add(i);

}


En Linux corría muchísimo más rápido que en Windows.


La primera reacción fue: ¿La JVM es más rápida en Linux?

Pero la respuesta es: no necesariamente.


El problema no está en Java ni en la lógica del programa. Está en: System.out.println(i);


Cada println implica:

  • Sincronización interna (thread-safe)
  • Conversión a string
  • Llamada al sistema operativo (I/O)
  • Escritura en la consola


Windows (cmd / PowerShell)

  • Consola históricamente más lenta
  • Mayor overhead en escritura
  • Menor eficiencia en buffering
  • Renderizado de texto menos optimizado


🐧 Linux (terminal)

  • Mejor manejo de buffers
  • I/O más eficiente
  • Consolas más livianas (bash, zsh, tty)
  • Mejor throughput de escritura


Si sacamos el println anda muy rápido :


var lista = new LinkedList<Long>();

for (var i = 0L; i < 10_000_000; i++) {

    lista.add(i);

}


El cuello de botella era la consola, no el sistema operativo.


Como lecciones importantes tengo :

  1. Nunca midas performance con println
  2. El I/O domina el rendimiento
  3. No culpes al lenguaje o al OS sin aislar variables
  4. Linux suele tener mejor rendimiento de consola



sábado, 28 de marzo de 2026

GraalOS ¿qué es y en qué estado está?


Si venís siguiendo GraalVM, probablemente escuchaste hablar de GraalOS.


La idea suena ambiciosa. Un sistema operativo optimizado para correr aplicaciones compiladas con GraalVM.

Pero… ¿es algo real hoy? ¿o sigue siendo experimental?


GraalOS es un concepto/proyecto que busca ejecutar aplicaciones native image directamente sobre un sistema mínimo.


Sin necesidad de:

  • JVM tradicional
  • Sistema operativo generalista


En esencia:

  • Llevar la idea de GraalVM al extremo:
  • una app = un runtime + un OS mínimo

GraalOS está muy relacionado con el concepto de Unikernel


Un unikernel:

  • Incluye solo lo necesario para una app
  • Corre directamente sobre el hypervisor
  • Reduce overhead y superficie de ataque


Pero, pero, GraalOS no es un producto maduro ni ampliamente disponible


En los últimos años:

  • No tuvo adopción masiva
  • No hay releases estables mainstream
  • No forma parte del flujo típico de GraalVM


En la práctica es más un concepto exploratorio / investigación que una herramienta de uso diario.

Mientras GraalVM avanzó mucho, GraalOS perdió protagonismo :(


En lugar de GraalOS, crecieron:

  • Containers (Docker)
  • Orquestación (Kubernetes)
  • Serverless

La industria resolvió el problema con otra abstracción


Hoy usarías antes:

  • Un container liviano
  • Un binary con GraalVM Native Image
  • Kubernetes o serverless


Antes que meterte en algo como GraalOS. GraalOS es más una idea interesante que una herramienta práctica.

¿En que anda GraalVM?


GraalVM es uno de los proyectos más interesantes del ecosistema Java. Permite ejecutar múltiples lenguajes (Java, JavaScript, Python, etc.) y, sobre todo, compilar aplicaciones a binarios nativos mediante Native Image.

En los últimos años, el proyecto evolucionó bastante — no tanto en hype, sino en madurez real. Además, hubo cambios importantes en cómo se distribuye y licencia.


Uno de los cambios más importantes: Gran parte de GraalVM se integró más profundamente con OpenJDK

  • El compilador Graal JIT ya no es algo “externo raro”
  • Se volvió más estándar dentro del ecosistema Java
  • Mejor compatibilidad con versiones recientes del JDK (17, 21, 23)


En otras palabras:

  • Antes GraalVM era “algo aparte”.
  • Hoy está mucho más alineado con el stack oficial de Java.


La feature estrella sigue siendo:

  • Ahead-of-Time compilation (AOT)
  • Genera binarios nativos sin JVM
  • Arranque ultra rápido
  • Bajo consumo de memoria


En estos años mejoró muchísimo:

  • Mejor soporte para frameworks (Spring, Micronaut, Quarkus)
  • Menos configuración manual
  • Mejor manejo de reflection
  • Debugging menos doloroso


Especialmente con:

  • Spring Boot + AOT
  • Quarkus (que sigue siendo el más “native-first”)


Cambio clave: licencias y distribución. Acá está el punto más importante del post 

Antes existían dos ediciones:

  •   Community Edition (open source)
  •   Enterprise Edition (mejoras pagas)


Ahora (últimos años) oracle reorganizó todo:

GraalVM Community

  • Basado en OpenJDK
  • Licencia: GPL (con classpath exception)
  • Sigue siendo gratis y open source


GraalVM Oracle (antes Enterprise)

  • Parte del ecosistema comercial de Oracle
  • Incluye optimizaciones avanzadas
  • Integrado con Oracle JDK


Y esto está más alineado con el modelo de licencias de Oracle Java.


Bueno pero, ¿GraalVM ahora es pago?”

La respuesta corta:

  • No, la versión Community sigue siendo gratis
  • Pero algunas features avanzadas están en el ecosistema comercial


Y algo que no me gusta es que Polyglot tiene menos hype y es más de nicho. 

GraalVM nació con la idea de: “Un runtime para múltiples lenguajes”


Soporta:

  • Java
  • JavaScript
  • Python
  • Ruby
  • WebAssembly


Pero, no se usa mucho, creo que es una tendencia general en la industria. 


En estos años se vio algo interesante que hubo menos marketing, más uso concreto


Casos típicos:

  • Microservicios con arranque rápido
  • Serverless
  • Apps en contenedores (menos RAM)


La pregunta del millon ¿Vale la pena hoy? Sí, si:

  • Querés arranque rápido
  • Buscás bajo consumo de memoria
  • Usás frameworks compatibles
  • Estás en cloud / Kubernetes


No tanto si:

  • Usás mucha reflexión dinámica
  • Dependés de librerías complejas
  • Querés “zero config”

Mi opinión es que GraalVM es un producto real, que todos tendríamos que conocer y usar. Si tenés una aplicación en Java y en el cloud, no cuesta nada probar migrar a GraalVM y medir su performance, ya sea usando Graal Jit o binarios nativos. 

sábado, 27 de diciembre de 2025

Implementando nuestra propia mónada en Java


En lenguajes funcionales (y en C++23 con std::expected), existe un tipo que representa éxito o error sin excepciones.

En Java podemos implementarlo fácilmente y hacerlo monádico.

Result<T, E> encapsula dos posibles estados:

  • Ok(T) → el cálculo fue exitoso
  • Err(E) → ocurrió un error


Así evitamos lanzar excepciones, y podemos encadenar operaciones de manera declarativa.


public sealed interface Result<T, E> permits Ok, Err {

    boolean isOk();

    boolean isErr();


    <U> Result<U, E> map(Function<? super T, ? extends U> f);

    <U> Result<U, E> flatMap(Function<? super T, Result<U, E>> f);

    T orElse(T fallback);

}


public record Ok<T, E>(T value) implements Result<T, E> {

    public boolean isOk() { return true; }

    public boolean isErr() { return false; }

    public <U> Result<U, E> map(Function<? super T, ? extends U> f) {

        return new Ok<>(f.apply(value));

    }

    public <U> Result<U, E> flatMap(Function<? super T, Result<U, E>> f) {

        return f.apply(value);

    }

    public T orElse(T fallback) { return value; }

}


public record Err<T, E>(E error) implements Result<T, E> {

    public boolean isOk() { return false; }

    public boolean isErr() { return true; }

    public <U> Result<U, E> map(Function<? super T, ? extends U> f) {

        return new Err<>(error);

    }

    public <U> Result<U, E> flatMap(Function<? super T, Result<U, E>> f) {

        return new Err<>(error);

    }

    public T orElse(T fallback) { return fallback; }

}


Podemos usarlo, de esta manera: 


Result<Integer, String> parseInt(String s) {

    try {

        return new Ok<>(Integer.parseInt(s));

    } catch (NumberFormatException e) {

        return new Err<>("Not a number");

    }

}


Result<Integer, String> divideByTwo(int n) {

    return (n % 2 == 0)

        ? new Ok<>(n / 2)

        : new Err<>("Odd number");

}


var result = parseInt("42")

    .flatMap(this::divideByTwo)

    .map(x -> x * 3)

    .orElse(0);


System.out.println(result); // 63


Si en cualquier paso se produce un error (Err), las transformaciones se detienen automáticamente.

No hay try/catch, ni comprobaciones manuales de estado.


Ventajas del enfoque monádico

  • Evita excepciones y null.
  • Encadena operaciones de manera natural.
  • Explicita el flujo de éxito/error.
  • Es fácilmente testeable y composable.


En Java no hace falta un lenguaje funcional completo para pensar funcionalmente.

Con un poco de sintaxis moderna (record, sealed interface, lambdas) podés crear tus propios tipos monádicos.


miércoles, 24 de diciembre de 2025

Programación funcional con Optional, Stream y CompletableFuture en Java

 


Aunque Java no hable directamente de mónadas, muchas de sus clases modernas (desde Java 8) implementan comportamiento monádico: permiten encadenar operaciones que transforman o combinan valores sin salir del contexto (por ejemplo: puede ser nulo, puede fallar, puede no haber terminado aún).


Una mónada encapsula un valor junto con un contexto.

Ejemplos de contextos:

  • “puede no haber valor” → Optional<T>
  • “flujo de valores” → Stream<T>
  • “resultado asíncrono” → CompletableFuture<T>


Una mónada define operaciones para:

  • aplicar funciones al valor (map, flatMap)
  • propagar la ausencia o el error automáticamente
  • encadenar transformaciones sin condicionales explícitos


Optional<T> — ausencia de valor


El caso más simple: representa un valor o nada.


Optional<Integer> x = Optional.of(5);


var y = x.map(n -> n * 2)

          .flatMap(n -> Optional.of(n + 3))

          .orElse(0); // (5 * 2) + 3 = 13



Métodos monádicos principales

  • map(f): Aplica f si el valor existe
  • flatMap(f): Aplica f que devuelve otro Optional
  • orElse(v): Valor por defecto si está vacío
  • orElseGet(supplier): Valor por defecto perezoso
  • or(fallback) (desde Java 9): Usa otro Optional si está vacío


Stream<T> — secuencias monádicas

Stream también es una mónada: un contexto que contiene una secuencia de valores y permite transformaciones encadenadas.


Stream.of(1, 2, 3)

    .map(x -> x * 2)

    .flatMap(x -> Stream.of(x, x + 10))

    .forEach(System.out::println);


  • map transforma cada elemento
  • flatMap expande (aplana) los resultados
  • filter, reduce, etc. siguen el mismo espíritu


En términos monádicos: Stream es la mónada de la lista.


CompletableFuture<T> — mónada asíncrona


CompletableFuture encapsula un valor que aún no está disponible.


CompletableFuture.supplyAsync(() -> 5)

    .thenApply(x -> x * 2)           // map

    .thenCompose(x -> asyncAdd(x))   // flatMap

    .exceptionally(e -> 0)           // orElse

    .thenAccept(System.out::println);


  • thenApply ≈ map
  • thenCompose ≈ flatMap
  • exceptionally ≈ orElse


Los métodos monádicos permiten escribir código sin condicionales explícitos, sin null checks y sin bloqueos innecesarios.


domingo, 2 de noviembre de 2025

Estado del Octoverso

Github publicó el informe anual Octoverse, en el cual se pueden ver datos interesantes de los proyectos. El informe es grande y tiene muchos detalles.

En especial me intereso los lenguajes más utilizados:


Pero a partir de 2025, la curva de crecimiento de Python comenzó a seguir una trayectoria casi idéntica en paralelo con JavaScript y TypeScript, lo que sugiere que la adopción de la IA está influyendo en la elección del lenguaje en estos ecosistemas.




Dejo link: https://octoverse.github.com/

viernes, 17 de octubre de 2025

Queue y Deque en Java


En el Java Collections Framework, las interfaces Queue<E> y Deque<E> representan estructuras de datos diseñadas para manejar elementos de forma ordenada y disciplinada, siguiendo patrones clásicos como FIFO (First-In, First-Out) o LIFO (Last-In, First-Out).

Aunque en muchos casos usamos List<E> para mantener una secuencia, las colas ofrecen operaciones especializadas que reflejan su propósito: procesar elementos en un orden determinado.

Queue<E>: la cola clásica

Una Queue (cola) sigue la política FIFO: el primer elemento en entrar es el primero en salir.

Entre los métodos principales tenemos:

  • add(E e): Inserta un elemento, lanza excepción si no hay espacio. 
  • offer(E e): Inserta un elemento, devuelve false si falla (no lanza excepción). 
  • remove(): Elimina y devuelve el primer elemento, lanza excepción si está vacía.
  • poll(): Elimina y devuelve el primer elemento, devuelve null si está vacía.
  • element(): Devuelve el primer elemento sin eliminarlo, lanza excepción si está vacía.
  • peek(): Devuelve el primer elemento sin eliminarlo, o null si está vacía.  

Usá offer, poll y peek cuando no quieras manejar excepciones, y add, remove, element si esperás que siempre haya elementos.

Veamos un ejemplo con LinkedList: LinkedList implementa Queue, así que se puede usar como una cola:


import java.util.*;


public class EjemploQueue {

    public static void main(String[] args) {

        Queue<String> cola = new LinkedList<>();


        cola.offer("A");

        cola.offer("B");

        cola.offer("C");


        System.out.println("Frente de la cola: " + cola.peek());


        while (!cola.isEmpty()) {

            System.out.println("Atendiendo: " + cola.poll());

        }

    }

}


Salida:

Frente de la cola: A

Atendiendo: A

Atendiendo: B

Atendiendo: C



Aquí los elementos se procesan en el mismo orden en que fueron agregados.

Implementaciones comunes de Queue:

  • LinkedList: Cola basada en lista enlazada (también implementa Deque). 
  • PriorityQueue: Cola que ordena sus elementos según su prioridad o un comparador.
  • ArrayDeque: Implementación eficiente basada en array.
  • ConcurrentLinkedQueue: Versión no bloqueante, segura para hilos.
  • BlockingQueue: Interfaz extendida con operaciones que bloquean el hilo (en java.util.concurrent).


Deque<E>: doble cola


Deque (Double-Ended Queue) es una cola doble, permite insertar y eliminar elementos tanto por el frente como por el final.


Veamos un ejemplo con ArrayDeque:


import java.util.*;


public class EjemploDeque {

    public static void main(String[] args) {

        Deque<String> deque = new ArrayDeque<>();


        deque.addFirst("A");

        deque.addLast("B");

        deque.addLast("C");

        deque.addFirst("Inicio");


        System.out.println("Primero: " + deque.peekFirst());

        System.out.println("Último: " + deque.peekLast());


        while (!deque.isEmpty()) {

            System.out.println("Procesando: " + deque.pollFirst());

        }

    }

}


Salida:

Primero: Inicio

Último: C

Procesando: Inicio

Procesando: A

Procesando: B

Procesando: C


Antes de Java 1.6 se usaba la clase Stack, pero hoy se recomienda usar Deque como pila:


Deque<Integer> pila = new ArrayDeque<>();


pila.push(10); // addFirst

pila.push(20);

pila.push(30);


while (!pila.isEmpty()) {

    System.out.println("Sacando: " + pila.pop());

}


Salida:

Sacando: 30

Sacando: 20

Sacando: 10


Aquí la Deque actúa como una pila LIFO (Last-In, First-Out).


Las interfaces Queue<E> y Deque<E> amplían el poder del Java Collections Framework al modelar estructuras de datos de procesamiento secuencial, ideales para tareas como:

  • Procesamiento en colas de mensajes
  • Modelos productor-consumidor
  • Pilas o buffers circulares
  • Algoritmos de recorrido (como BFS)


jueves, 9 de octubre de 2025

Hablemos de ArrayList


La clase ArrayList en Java es una de las colecciones más utilizadas del paquete java.util.

Está implementada internamente sobre un arreglo dinámico, y su código fuente se encuentra en java.util.ArrayList dentro del JDK.

Veamos cómo funciona en detalle:

Internamente, ArrayList mantiene un array (arreglo) de tipo Object[] llamado elementData donde guarda los elementos:


public class ArrayList<E> extends AbstractList<E>

        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {


    private static final int DEFAULT_CAPACITY = 10;


    private static final Object[] EMPTY_ELEMENTDATA = {};


    private transient Object[] elementData;


    private int size;

}


  • elementData: el arreglo donde se almacenan los elementos.
  • size: la cantidad actual de elementos.
  • DEFAULT_CAPACITY: capacidad inicial (10).
  • EMPTY_ELEMENTDATA: usado cuando el ArrayList está vacío.


Cuando creás un ArrayList sin indicar capacidad:


List<String> list = new ArrayList<>();


internamente no se reserva espacio aún. Se crea con elementData = EMPTY_ELEMENTDATA.

Recién al agregar el primer elemento, se inicializa con una capacidad por defecto (10).


Cuando llamás a:


list.add("Hola");


Java hace esto internamente:


public boolean add(E e) {

    ensureCapacityInternal(size + 1);

    elementData[size++] = e;

    return true;

}


Y ensureCapacityInternal verifica si el arreglo tiene espacio suficiente:


private void ensureCapacityInternal(int minCapacity) {

    if (elementData == EMPTY_ELEMENTDATA) {

        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);

    }

    ensureExplicitCapacity(minCapacity);

}



Si no hay espacio, llama a grow():


private void grow(int minCapacity) {

    int oldCapacity = elementData.length;

    int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5 veces más

    if (newCapacity - minCapacity < 0)

        newCapacity = minCapacity;

    elementData = Arrays.copyOf(elementData, newCapacity);

}



Cuando se queda sin espacio, duplica la capacidad en un 50% (crecimiento amortizado).


Acceder es O(1) porque es un arreglo:


public E get(int index) {

    rangeCheck(index);

    return (E) elementData[index];

}


Cuando quitás un elemento:


list.remove(2);


se mueve el resto de los elementos para no dejar “huecos”:


public E remove(int index) {

    rangeCheck(index);


    E oldValue = elementData(index);


    int numMoved = size - index - 1;

    if (numMoved > 0)

        System.arraycopy(elementData, index + 1, elementData, index, numMoved);


    elementData[--size] = null; // libera la referencia

    return oldValue;

}

Esto hace que remove sea O(n) en el peor caso (por el corrimiento de elementos).


Veamos ventajas y desventajas

Ventajas:

  • Acceso aleatorio rápido (O(1)).
  • Memoria contigua, lo que mejora la localidad de referencia.
  • Crece automáticamente.


Desventajas:

  • Insertar o eliminar en el medio es costoso (O(n)).
  • Aumentar la capacidad implica copiar el arreglo completo.


ArrayList es básicamente una versión moderna y no sincronizada de Vector.

Si necesitás sincronización, podés envolverlo así:

List<String> syncList = Collections.synchronizedList(new ArrayList<>());


lunes, 22 de septiembre de 2025

Records en Java: Clases Inmutables de Forma Sencilla



A partir de Java 14 (como preview) y de forma estable en Java 16, se introdujeron los records.

Un record es una forma concisa de declarar clases inmutables que sirven principalmente para transportar datos (clases DTO, value objects, etc.).

La sintaxis básica es:

public record Persona(String nombre, int edad) {}


Esto genera automáticamente:

  • Los campos privados y finales nombre y edad.
  • Un constructor que inicializa esos campos.
  • Los métodos getters nombre() y edad().
  • Una implementación de toString().
  • Métodos equals() y hashCode() basados en los campos.


Veamos un ejemplo de uso: 


public class Main {

    public static void main(String[] args) {

        Persona p = new Persona("Ana", 30);


        System.out.println(p.nombre()); // "Ana"

        System.out.println(p.edad());   // 30

        System.out.println(p);          // Persona[nombre=Ana, edad=30]

    }

}


Notá que no se usan getNombre() ni getEdad(), sino directamente nombre() y edad().

Los campos de un record son inmutables (implícitamente final).

No existe p.setEdad(31) → en su lugar, deberías crear un nuevo objeto Persona.


Aunque el record genera mucho código automáticamente, también podés definir métodos adicionales:


public record Persona(String nombre, int edad) {

    public boolean esMayorDeEdad() {

        return edad >= 18;

    }

}


Persona juan = new Persona("Juan", 17);

System.out.println(juan.esMayorDeEdad()); // false


Podés agregar lógica en el constructor, pero siempre respetando la inicialización de todos los campos:


public record Persona(String nombre, int edad) {

    public Persona {

        if (edad < 0) {

            throw new IllegalArgumentException("La edad no puede ser negativa");

        }

    }

}


Este es el constructor compacto: no es necesario repetir la asignación de campos, Java lo hace automáticamente.


Los records funcionan muy bien como DTOs, por ejemplo en listas o streams:


List<Persona> personas = List.of(

    new Persona("Ana", 30),

    new Persona("Luis", 25)

);


personas.stream()

    .filter(p -> p.edad() > 26)

    .forEach(System.out::println);


En Resumen: 

  • Los records simplifican la creación de clases para transportar datos.
  • Generan automáticamente: constructor, getters, equals, hashCode y toString.
  • Son inmutables por diseño.
  • Se pueden añadir métodos y validaciones de construcción.


Son ideales para DTOs, value objects y cualquier situación en la que antes usabas una clase con solo atributos y getters.

domingo, 14 de septiembre de 2025

WebSocket Scope en Spring



Spring Framework nos ofrece diferentes scopes para manejar el ciclo de vida de los beans: singleton, prototype, request, session, etc.

Pero cuando trabajamos con WebSockets, entramos en un contexto distinto al clásico HTTP. Aquí, cada cliente mantiene una conexión persistente, y necesitamos un scope que nos permita almacenar estado por sesión WebSocket.

Para eso existe el WebSocket Scope.

El WebSocket Scope es un alcance especial que Spring habilita cuando se trabaja con @EnableWebSocket o @EnableWebSocketMessageBroker.

Permite que un bean viva mientras dure la conexión WebSocket de un cliente específico.

Es decir:

  • Cada cliente WebSocket obtiene su propia instancia del bean.
  • Cuando la conexión WebSocket se cierra, ese bean se destruye automáticamente.

Spring Boot ya trae soporte para WebSockets. Para usar el WebSocket Scope hay que agregar la anotación @Scope("websocket") sobre el bean.


import org.springframework.context.annotation.Scope;

import org.springframework.stereotype.Component;


@Component

@Scope("websocket")

public class WebSocketSessionBean {


    private int counter = 0;


    public int incrementAndGet() {

        counter++;

        return counter;

    }

}


En este ejemplo, cada cliente WebSocket tiene su propio contador independiente.

Podemos inyectar el bean con scope websocket en un controlador:


import org.springframework.messaging.handler.annotation.MessageMapping;

import org.springframework.stereotype.Controller;


@Controller

public class ChatController {


    private final WebSocketSessionBean sessionBean;


    public ChatController(WebSocketSessionBean sessionBean) {

        this.sessionBean = sessionBean;

    }


    @MessageMapping("/message")

    public String handleMessage(String message) {

        int count = sessionBean.incrementAndGet();

        return "Mensaje #" + count + ": " + message;

    }

}


Aquí:

  • Cada vez que el mismo cliente envía un mensaje, se incrementa su contador privado.
  • Otro cliente tiene su propio contador independiente.


Ciclo de vida

  • Inicio: el bean se crea cuando el cliente establece la conexión WebSocket.
  • Destrucción: se elimina automáticamente cuando la conexión se cierra.

Esto lo hace ideal para manejar estado ligado a una sesión WebSocket, sin necesidad de usar mapas estáticos o manejar manualmente IDs de sesión.


El WebSocket Scope en Spring nos permite manejar estado de forma segura y aislada por conexión.

Es útil para casos como:

  • Chats con información por usuario.
  • Juegos multijugador donde cada sesión mantiene su progreso.
  • Aplicaciones colaborativas en tiempo real.


Con esta herramienta, Spring hace más simple mantener datos por cliente en arquitecturas basadas en WebSockets.


Dejo link:  

https://docs.spring.io/spring-framework/reference/web/websocket/stomp/scope.html

martes, 19 de agosto de 2025

Inner Classes en Java vs Nested Classes en C#


En muchos lenguajes podemos anidar clases dentro de otras, pero no siempre significan lo mismo ni tienen las mismas capacidades. Veamos la comparación entre Java y C#.

Java tiene Inner Classes. Cuando declaramos una clase dentro de otra, tenemos dos opciones principales:

Static nested class: funciona como una clase estática, no tiene referencia a la instancia externa.

Inner class (no estática): mantiene una referencia implícita a la instancia de la clase que la contiene.

Esto permite escribir cosas como:


class Contenedora {

    private int valor = 42;


    class Inner {

        void mostrar() {

            // Acceso implícito a la instancia externa

            System.out.println("Valor: " + Contenedora.this.valor);

        }

    }


    public static void main(String[] args) {

        Contenedora c = new Contenedora();

        Contenedora.Inner i = c.new Inner();

        i.mostrar(); // Valor: 42

    }

}


Aquí, Contenedora.this es una referencia implícita que permite acceder directamente a la instancia de la clase externa.


En C# tenemos Nested Classes. En C#, todas las clases anidadas son conceptualmente clases estáticas (aunque no las declares static).

Esto significa que no existe referencia implícita a la instancia externa.


Ejemplo equivalente en C#:


public class Contenedora

{

    private int valor = 42;


    public class Nested

    {

        // No existe "Contenedora.this"

        public void Mostrar(Contenedora externa)

        {

            Console.WriteLine($"Valor: {externa.valor}");

        }

    }


    public static void Main()

    {

        var c = new Contenedora();

        var n = new Nested();

        n.Mostrar(c); // Valor: 42

    }

}


Si querés acceder a los atributos de la clase externa, tenés que pasar explícitamente la referencia (Contenedora externa en este caso).

Entonces, Java Inner Class:

  • Tiene un puntero implícito a la instancia externa.
  • Podés usar Outer.this.atributo.
  • Muy útil en callbacks o cuando la clase interna depende fuertemente de la externa.


C# Nested Class:

  • No tiene referencia implícita.
  • No existe Outer.this.
  • Son más “independientes”, como una clase estática que solo vive en el scope de otra.


¿Por qué esta diferencia?

  • Java diseñó las inner classes como un mecanismo para trabajar con GUI y callbacks (ejemplo clásico: listeners en Swing). Por eso necesitan la referencia a la instancia externa.
  • C# tomó otro camino: sus delegates y lambdas con closures resuelven ese escenario sin necesidad de que las nested classes tengan referencia implícita.



sábado, 9 de agosto de 2025

is, as de C# y el viejo amigo instanceof de Java


En C#, trabajar con tipos en tiempo de ejecución es bastante cómodo gracias a dos operadores clave: is y as.

is nos dice si un objeto es de un tipo determinado, y desde C# 7 nos deja incluso declarar la variable directamente en la comprobación.


if (obj is string texto)

{

    Console.WriteLine(texto.ToUpper());

}


De esta forma, la verificación y el cast se hacen en una sola línea, clara y segura.


El otro operador, as, va un paso más allá: intenta convertir el objeto y, si no puede, devuelve null en lugar de lanzar una excepción. Ideal para esos casos donde la conversión no es obligatoria, pero sí útil si ocurre:


var texto = obj as string;

if (texto != null)

{

    Console.WriteLine(texto.Length);

}


Con estos dos, en C# rara vez tenemos que hacer casts inseguros, y eso se agradece.


Ahora, en el mundo Java siempre hemos tenido a instanceof, que durante años fue… digamos… más básico: verificaba el tipo, pero luego había que castear manualmente:


if (obj instanceof String) {

    String texto = (String) obj;

    System.out.println(texto.toUpperCase());

}


Esto cambió en Java 14, cuando instanceof adoptó pattern matching. Ahora podemos escribir:


if (obj instanceof String texto) {

    System.out.println(texto.toUpperCase());

}


Sí, muy similar a lo que C# ya hacía.



Y con Java 21 llegó la verdadera vuelta de tuerca: pattern matching dentro de switch. Esto no solo ahorra código, sino que hace más expresivas estructuras de control que antes eran un desfile de if/else.


switch (obj) {

    case String s -> System.out.println("Cadena: " + s.toUpperCase());

    case Integer i -> System.out.println("Entero: " + (i * 2));

    default -> System.out.println("Otro tipo");

}


Así que hoy, si trabajás en C#, tenés en is y as dos herramientas muy potentes para escribir código seguro y legible. Y si venís de Java, sabé que instanceof ya no es el operador limitado de antes: está alcanzando la versatilidad de C#, y con switch incluso ofrece caminos nuevos para organizar la lógica.

En ambos lenguajes, dominar estas técnicas significa escribir código más limpio, menos propenso a errores y, sobre todo, más agradable de mantener.


domingo, 13 de julio de 2025

Mapeo de Herencia en JPA


JPA ofrece 3 estrategias principales para mapear la herencia :


@Inheritance(strategy = SINGLE_TABLE) : Todo se guarda en una sola tabla con una columna discriminadora. 

@Inheritance(strategy = JOINED): Se usa una tabla por clase, relacionadas por claves foráneas.        

@Inheritance(strategy = TABLE_PER_CLASS): Una tabla para cada clase, sin relaciones.                           


Veamos un ejemplo usando la estrategia: SINGLE_TABLE.



package com.ejemplo.demo.entidad;

import jakarta.persistence.*;


@Entity

@Inheritance(strategy = InheritanceType.SINGLE_TABLE)

@DiscriminatorColumn(name = "tipo_empleado", discriminatorType = DiscriminatorType.STRING)

public abstract class Empleado {


    @Id

    @GeneratedValue(strategy = GenerationType.IDENTITY)

    private Long id;


    private String nombre;

    // Getters y setters

}


@Inheritance(...): Define la estrategia de herencia.

@DiscriminatorColumn(...):  Crea una columna adicional (tipo_empleado) que almacena el tipo real de cada instancia.


📄 Gerente.java

package com.ejemplo.demo.entidad;


import jakarta.persistence.*;


@Entity

@DiscriminatorValue("GERENTE")

public class Gerente extends Empleado {


    private String departamento;

    // Getters y setters

}


📄 Desarrollador.java


package com.ejemplo.demo.entidad;


import jakarta.persistence.*;


@Entity

@DiscriminatorValue("DESARROLLADOR")

public class Desarrollador extends Empleado {


    private String lenguajeFavorito;

    // Getters y setters

}


Se crea una única tabla empleado, con las columnas:

id, nombre, departamento, lenguajeFavorito y tipo_empleado (con valores como GERENTE, DESARROLLADOR)


Ventajas de SINGLE_TABLE

    • Más simple y rápida para consultas.

    • Ideal si la mayoría de los campos son comunes.


Desventajas

    • Muchos campos nulos en columnas que sólo usa una subclase.


Otras estrategias: 

    • JOINED: separa en tablas, y junta usando claves foráneas. Útil si los datos son muy distintos y no querés campos nulos.

    • TABLE_PER_CLASS: evita JOINs, y es util cuando no necesitas hacer consultas a nivel de clase padre. 


En conclusión SINGLE_TABLE lo usamos cuando tenemos herencia la cual las clases hijas no agregan muchos o ningun dato. JOINED cuando queremos hacer consultas a nivel clase padre y tenemos muchos datos diferentes y TABLE_PER_CLASS cuando no necesitamos hacer query a nivel clase padre. 

lunes, 7 de julio de 2025

Mapeo de Herencia en JPA


JPA ofrece 3 estrategias para mapear herencia:

  • @Inheritance(strategy = SINGLE_TABLE) : Todo se guarda en una sola tabla con una columna discriminadora. 
  • @Inheritance(strategy = JOINED): Se usa una tabla por clase, relacionadas por claves foráneas.        
  • @Inheritance(strategy = TABLE_PER_CLASS): Una tabla para cada clase, sin relaciones.                           

Veamos un ejemplo: Clase base y subclases

Vamos a usar la estrategia: SINGLE_TABLE.


package com.ejemplo.demo.entidad;

import jakarta.persistence.*;


@Entity

@Inheritance(strategy = InheritanceType.SINGLE_TABLE)

@DiscriminatorColumn(name = "tipo_empleado", discriminatorType = DiscriminatorType.STRING)

public abstract class Empleado {


    @Id

    @GeneratedValue(strategy = GenerationType.IDENTITY)

    private Long id;

    private String nombre;


    // Getters y setters

}


@Inheritance(...): Define la estrategia de herencia.

@DiscriminatorColumn(...):  Crea una columna adicional (tipo_empleado) que almacena el tipo real de cada instancia.


📄 Gerente.java


package com.ejemplo.demo.entidad;


import jakarta.persistence.*;


@Entity

@DiscriminatorValue("GERENTE")

public class Gerente extends Empleado {


    private String departamento;


    // Getters y setters

}


📄 Desarrollador.java


package com.ejemplo.demo.entidad;


import jakarta.persistence.*;


@Entity

@DiscriminatorValue("DESARROLLADOR")

public class Desarrollador extends Empleado {


    private String lenguajeFavorito;


    // Getters y setters

}


Resultado en la base de datos


Se crea una única tabla empleado, con las columnas:

id, nombre, departamento, lenguajeFavorito y tipo_empleado (con valores como GERENTE, DESARROLLADOR)


Ventajas de SINGLE_TABLE

    • Más simple y rápida para consultas.

    • Ideal si la mayoría de los campos son comunes.

Desventajas

    • Muchos campos nulos en columnas que sólo usa una subclase.


Otras estrategias: 

    • JOINED: separa en tablas, y junta usando claves foráneas. Útil si los datos son muy distintos y no querés campos nulos.

    • TABLE_PER_CLASS: evita JOINs, pero no es tan usada. No permite relaciones polimórficas fácilmente.


domingo, 6 de julio de 2025

Relaciones entre Entidades con JPA


Aprender a modelar relaciones entre tablas en Java usando JPA, como por ejemplo:

  • @OneToMany (uno a muchos)

  • @ManyToOne (muchos a uno)

  • @OneToOne (uno a uno)

  • @ManyToMany (muchos a muchos)


En este ejemplo vamos a modelar una relación simple: Una Persona puede tener varias Direcciones.


package com.ejemplo.demo.entidad;


import jakarta.persistence.*;

import java.util.List;


@Entity

public class Persona {


@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

private Long id;

private String nombre;

@OneToMany(mappedBy = "persona", cascade = CascadeType.ALL, orphanRemoval = true)

private List<Direccion> direcciones;


// Constructores, getters y setters

}


Anotaciones clave:

@OneToMany(mappedBy = "persona"): Declara una relación de uno a muchos. Una persona puede tener varias direcciones.

  • mappedBy = "persona": indica que el atributo que mantiene la relación está del otro lado (Direccion.persona).
  • cascade = CascadeType.ALL: al guardar o eliminar una persona, se aplican los cambios también a sus direcciones.
  • orphanRemoval = true: elimina direcciones si se quitan de la lista.



Entidad Direccion:



package com.ejemplo.demo.entidad;


import jakarta.persistence.*;


@Entity

public class Direccion {


@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

private Long id;

private String calle;

private String ciudad;


@ManyToOne

@JoinColumn(name = "persona_id")

private Persona persona;


// Constructores, getters y setters

}



Anotaciones clave:

@ManyToOne: Cada dirección pertenece a una sola persona.

@JoinColumn(name = "persona_id"): Crea una columna en la tabla direccion llamada persona_id que actúa como clave foránea a la tabla persona.


Las listas deben ser inicializadas como new ArrayList<>() si se van a usar directamente.

Si no usás orphanRemoval, las direcciones quedan "huérfanas" si quitás una de la lista.


Un pequeño ejemplo para que vayamos incrementando nuestro conocimiento en JPA. 

miércoles, 2 de julio de 2025

¿Qué es Spring Data JPA? Parte 2

 


Veamos un ejemplo de entidad de jpa: 

📄 Persona.java


package com.ejemplo.demo.entidad;

import jakarta.persistence.*;


@Entity

public class Persona {

    @Id

    @GeneratedValue(strategy = GenerationType.IDENTITY)

    private Long id;

    private String nombre;

    private String email;


    // Constructores

    public Persona() {}

    public Persona(String nombre, String email) {

        this.nombre = nombre;

        this.email = email;

    }


    // Getters y setters

    public Long getId() { return id; }

    public String getNombre() { return nombre; }

    public void setNombre(String nombre) { this.nombre = nombre; }

    public String getEmail() { return email; }

    public void setEmail(String email) { this.email = email; }

}


@Entity: Marca esta clase como una entidad JPA, lo que significa que será persistida en una tabla de la base de datos. El nombre de la tabla por defecto será el mismo que el nombre de la clase (persona en minúsculas).

@Id: Indica que el atributo id es la clave primaria de la entidad. Cada fila tendrá un valor único en esta columna.

@GeneratedValue(strategy = GenerationType.IDENTITY):  Especifica que el valor del campo id será generado automáticamente por la base de datos (usualmente como autoincremental).  Existen otras estrategias (AUTO, SEQUENCE, TABLE), pero IDENTITY es simple y efectiva para bases H2 o MySQL.


Repositorio

📄 PersonaRepository.java


package com.ejemplo.demo.repositorio;

import com.ejemplo.demo.entidad.Persona;

import org.springframework.data.jpa.repository.JpaRepository;


public interface PersonaRepository extends JpaRepository<Persona, Long> {

}


JpaRepository<Persona, Long>:  Es una interfaz de Spring Data que nos proporciona métodos CRUD listos para usar: findAll(), save(), deleteById(), etc.

Persona: el tipo de entidad

Long: el tipo de la clave primaria (id)

No se necesita ninguna anotación porque Spring escanea esta interfaz y genera una implementación automática.


Servicio

📄 PersonaService.java


@Service

public class PersonaService {

    // ...

}


@Service:  Marca esta clase como un componente de servicio, lo que permite que Spring la detecte y gestione como parte del contexto de la aplicación (inyección de dependencias, ciclo de vida, etc.).

Controlador

📄 PersonaController.java


@RestController

@RequestMapping("/personas")

public class PersonaController {

    // ...

}


    • @RestController:  Marca la clase como un controlador REST. Es equivalente a usar @Controller + @ResponseBody para cada método.

    • @RequestMapping("/personas"):  Define la ruta base de la API. Todos los métodos se expondrán bajo /personas.

    • @GetMapping, @PostMapping, @DeleteMapping: Indican qué método HTTP debe usarse para cada operación.

    • @RequestBody:  Indica que los datos del cuerpo del request (formato JSON) deben ser convertidos a un objeto Java.

    • @PathVariable: Extrae una variable de la URL, por ejemplo /personas/1 extrae el id = 1.


Archivo application.properties

spring.datasource.url=jdbc:h2:mem:testdb

spring.jpa.hibernate.ddl-auto=update

spring.h2.console.enabled=true


spring.jpa.hibernate.ddl-auto=update:  Le indica a JPA que genere automáticamente las tablas según nuestras entidades. Si la tabla no existe, la crea.


martes, 1 de julio de 2025

¿Qué es Spring Data JPA?


JPA (Java Persistence API) es una especificación estándar de Java para el mapeo objeto-relacional (ORM), que permite interactuar con bases de datos relacionales utilizando objetos Java.


Con JPA se pueden:

    • Definir clases Java como entidades persistentes (@Entity).

    • Especificar relaciones entre entidades (uno a muchos, muchos a muchos, etc.).

    • Realizar operaciones como insertar, actualizar, eliminar y consultar datos sin escribir SQL explícito.


JPA no es una implementación, sino una interfaz estándar. Las implementaciones más comunes son:

    • Hibernate (la más usada)

    • EclipseLink

    • OpenJPA


Spring Data JPA es un módulo de Spring que simplifica el uso de JPA:

    • Permite definir interfaces de repositorio sin necesidad de implementar los métodos CRUD.

    • Integra la capa de persistencia con el resto del ecosistema de Spring (inyección de dependencias, control transaccional, validaciones, etc.).

    • Facilita la creación de consultas con nombres de método (findByNombre, findByEmailContaining, etc.).

    • Soporta JPQL, SQL nativo y consultas dinámicas.


Spring Data JPA es una abstracción sobre JPA que reduce drásticamente el código necesario para acceder a la base de datos.


Ventajas de usar JPA con Spring

    • Menos código repetitivo: no es necesario implementar manualmente métodos CRUD.

    • Integración total con Spring: todo se gestiona mediante inyección de dependencias y configuración automática.

    • Consultas declarativas: se pueden crear consultas complejas usando el nombre del método.

    • Abstracción del SQL: permite trabajar con objetos sin escribir sentencias SQL directamente.

    • Gestión automática del contexto de persistencia: Spring gestiona transacciones, apertura y cierre de conexiones.

    • Compatible con múltiples bases de datos: MySQL, PostgreSQL, H2, Oracle, SQL Server, etc.


En el siguiente post vamos con algo más práctico. 


jueves, 19 de junio de 2025

Java 25 integra Compact Object Headers (JEP 519)

 


Java 25, la próxima versión LTS, incorpora de forma nativa el soporte para Compact Object Headers mediante la JEP 519. Esta característica, que era experimental en Java 24, ahora se promueve como una funcionalidad oficial del VM, lo que implica grandes beneficios sin necesidad de modificar el código existente.

¿Qué son los Compact Object Headers?

En HotSpot (el JVM de referencia), cada objeto en memoria tiene un cabecera (header) que contiene meta información:

  • Un mark word (incluye bits para GC, monitoreo y hashcode)
  • Un class word (referencia comprimida a la clase del objeto)

Hasta ahora, este header ocupa 96 bits (12 bytes), lo cual significa un sobrecoste significativo —sobre todo en aplicaciones con muchos objetos de pequeño tamaño.


¿Qué cambia con Compact Object Headers?


Reducción del header a 64 bits (8 bytes), manteniendo todo lo necesario:

  • Mark word
  • Class pointer (comprimido a 22 bits)
  • Campos reservados para GC y futuros proyectos como Valhalla 

El ahorro en memoria puede llegar al 30 % de CPU y 20 % menos uso de heap en aplicaciones que crean muchos objetos pequeños. Benchmarks como SPECjbb2015 muestran mejoras de 22 % en heap y 8 % en CPU, además de menor frecuencia de GC.

Amazon ya lo ha probado en centenas de servicios, validando caídas de hasta 30 % en consumo CPU y 15 % menos GC, especialmente en colectores G1 y Parallel.


¿Cómo habilitarlo?


En Java 25 basta con agregar el siguiente flag al iniciar tu aplicación:


java -XX:+UseCompactObjectHeaders -jar mi-app.jar


Sin necesidad del flag -XX:+UnlockExperimentalVMOptions, ni cambiar una sola línea de código 


¿Por qué es relevante?

  • Menor consumo de memoria: ideal para microservicios, Kubernetes, contenedores o entornos edge.
  • Mejor rendimiento: los objetos más pequeños mejoran la localidad de caché y reducen cargas de GC.
  • Implementación sencilla: es transparente y compatible con código existente, sin riesgo ni esfuerzo extra.


Compact Headers es parte de Project Lilliput, cuyo objetivo es reducir drásticamente el overhead de los headers de objetos:

  • JDK 22 introdujo estructura para monitores de objetos
  • JEP 450 en Java 24 activó Compact Headers en modo experimental
  • Ahora, JEP 519 en Java 25 los integra como funcionalidad oficial 


Esto también adelanta compatibilidad con futuras mejoras como Valhalla (clases de valor) y Project Panama.


Recomendaciones prácticas

  • Activá en entornos de desarrollo o staging y monitorizá el uso de memoria y CPU.
  • En entornos de producción, el flag puede reducir costes operativos y mejorar densidad en la nube.
  • Revisá que no actives `-XX:-UseCompressedClassPointers` o locking legado, ya obsoletos en Java 25.
  • Esperá mejoras similares en plataformas x64 y AArch64; ZGC en x64 aún está en progreso


Java 25 (LTS) ofrece ahora una forma sencilla de optimizar memoria y rendimiento con un solo parámetro. Compact Object Headers representan una mejora tangible para cualquier proyecto moderno que genere muchos objetos. Es una mejora que conviene activar y medir desde ya.

Dejo link: 

https://www.infoq.com/news/2025/06/java-25-compact-object-headers/


jueves, 8 de mayo de 2025

Beans Singleton en Spring: ¿Son un riesgo en entornos concurrentes?


Cuando trabajamos con Spring Framework, una de las primeras cosas que aprendemos es que los beans por defecto son singleton. Es decir, Spring crea una única instancia de cada bean y la reutiliza a lo largo de toda la aplicación. Ojo aca, no es igual que el patron singleton, porque el patron singleton es una instancia por clase. Spring realiza una instancia por bean, pero varios beans pueden ser de la misma clase. 

Pero de igual manera nos podemos preguntar ¿No podría causar problemas si múltiples hilos usan el mismo objeto?

Veamos por qué no, y en qué casos sí.

Cuando marcamos una clase con @Component, @Service, @Repository, etc., sin indicar un @Scope, Spring crea una única instancia de esa clase cuando arranca el contexto, y luego la reutiliza para inyectarla donde haga falta.


@Service

public class MiServicio {

    public void hacerAlgo() {

        // lógica

    }

}

Este bean se compartirá entre todas las partes de la aplicación que lo necesiten.

¿Y si lo usan varios hilos?

Acá es donde entra el concepto de thread safety. Si el bean no guarda estado mutable (es stateless), no hay ningún problema. Puede ser accedido por múltiples hilos al mismo tiempo sin consecuencias.

Pero si el bean mantiene estado mutable (por ejemplo, una variable de instancia que se modifica en cada método), entonces puede haber condiciones de carrera, errores y comportamiento impredecible.

Veamos un ejemplo peligroso:


@Component

public class ContadorCompartido {

    private int contador = 0;


    public void incrementar() {

        contador++;

    }


    public int getContador() {

        return contador;

    }

}


Este bean no es thread-safe: si lo usan múltiples hilos al mismo tiempo, el contador puede tener resultados inesperados.

Otra pregunta puede ser ¿Spring crea múltiples instancias si hay mucha carga?

No. Este es un mito común. Spring no crea múltiples instancias de un bean singleton automáticamente bajo carga. La única forma en que podrías tener más de una instancia es:

  • Si usamos un @Scope("prototype"), @RequestScope, etc.
  • Si instanciás manualmente objetos sin pasar por el contenedor de Spring (lo cual rompe la inyección de dependencias).
  • O si usás proxies o mecanismos especiales para aislar contexto por request, como ocurre con algunos beans en entornos web.


Podemos concluir que : 

  • Si tu bean necesita guardar estado por usuario o por request, no lo hagas singleton. Usá un scope adecuado.
  • Si tu bean puede ser stateless, mantenelo así. Es más simple, más eficiente y seguro en entornos multihilo.
  • Si el bean debe tener estado compartido, usá sincronización o mecanismos thread-safe como AtomicInteger, ConcurrentHashMap, etc.

En Spring, los beans singleton son una gran herramienta para reutilización de lógica y eficiencia de memoria. Pero es tu responsabilidad asegurarte de que no mantengan estado mutable que pueda generar conflictos en concurrencia.