Translate

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

martes, 21 de abril de 2026

Records vs Clases vs Lombok vs Kotlin vs Scala


¿Cuál es la mejor forma de modelar datos? Desde los Struct de c++ nos venimos preguntando esto. Vamos a ver algunas opciones modernas que nos provee la plataforma java. 

Cuando trabajamos con objetos que representan datos (DTOs, Value Objects, etc.), distintos lenguajes ofrecen soluciones para evitar el boilerplate.


En este post comparamos:

  • Records en Java
  • Clases tradicionales
  • Lombok
  • Data classes en Kotlin
  • Case classes en Scala


1. Clase tradicional (Java)


public class Persona {

    private final String nombre;

    private final int edad;


    public Persona(String nombre, int edad) {

        this.nombre = nombre;

        this.edad = edad;

    }


    public String getNombre() { return nombre; }

    public int getEdad() { return edad; }


    @Override public boolean equals(Object o) { ... }

    @Override public int hashCode() { ... }

    @Override public String toString() { ... }

}

Ventajas

  • Total control
  • Compatible con todo (JPA, frameworks)


Desventajas

  • Mucho boilerplate
  • Propenso a errores


2. Records (Java)


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


Ventajas

  • Ultra conciso
  • Inmutabilidad garantizada
  • Sin dependencias


Desventajas

  • Menos flexible
  • No sirve bien con JPA
  • No herencia


3. Lombok (@Data)


import lombok.Data;


@Data

public class Persona {

    private String nombre;

    private int edad;

}


Ventajas

  • Reduce mucho código
  • Mutable o inmutable (configurable)


Desventajas

  • Dependencia externa
  • "Magia" en compilación (puede confundir)
  •  Problemas en tooling/debug


4. Data Classes (Kotlin)


data class Persona(val nombre: String, val edad: Int)


Ventajas

  • Muy conciso
  • Inmutable por defecto
  • copy() incluido
  • Destructuring


val (nombre, edad) = persona


Desventajas

  • Requiere usar Kotlin
  • Interoperabilidad Java no siempre perfecta


5. Case Classes (Scala)


case class Persona(nombre: String, edad: Int)


Ventajas

  • Inmutables
  • Pattern matching nativo
  • copy() automático
  • Muy expresivas


persona match {

  case Persona(nombre, edad) => println(nombre)

}


Desventajas

  • Curva de aprendizaje
  • Ecosistema más complejo


Y entonces? Y ninguno es super mejor, pero podemos tener estas reglas: 


Java Records

Ideal para:

  • DTOs simples
  • APIs REST
  • Código moderno sin dependencias


Son el "mínimo viable elegante" en Java.


Lombok

Ideal para:

  • Proyectos legacy
  • Equipos que ya lo usan


 Soluciona el problema… pero no es parte del lenguaje.


Kotlin

La mejor experiencia general para modelado de datos.

  • copy()
  • destructuring
  • null-safety


Es claramente superior en ergonomía.


Scala

El más poderoso conceptualmente.

  • Pattern matching real
  • Inmutabilidad fuerte
  • Integración con FP


Pero más complejo.


 Clases Java

 Siguen siendo necesarias cuando:

  • Usás JPA
  • Necesitás mutabilidad
  • Requerís control total


Si estás en Java moderno → Records

Si querés máxima productividad → Kotlin

Si buscás poder expresivo → Scala

Si estás en legacy → Lombok o clases



lunes, 20 de abril de 2026

Records en Java: Datos Inmutables de Forma Elegante


Desde Java 14, el lenguaje incorporó una nueva forma de definir clases de datos: los records.

Su objetivo es simple: reducir el boilerplate cuando solo queremos representar datos inmutables.


¿Qué problema vienen a resolver?

Antes de records, una clase típica para representar datos se veía así:


public class Persona {

    private final String nombre;

    private final int edad;


    public Persona(String nombre, int edad) {

        this.nombre = nombre;

        this.edad = edad;

    }


    public String getNombre() {

        return nombre;

    }


    public int getEdad() {

        return edad;

    }


    @Override

    public boolean equals(Object o) { ... }


    @Override

    public int hashCode() { ... }


    @Override

    public String toString() { ... }

}


Mucho código repetitivo, ¿no?

Con records, lo mismo se define así:


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


Y listo.


Java automáticamente genera:

  • Constructor
  • Getters (sin prefijo get)
  • equals()
  • hashCode()
  • toString()


Un record es una clase especial que:

  • Es inmutable
  • Es final
  • Extiende implícitamente de java.lang.Record


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

System.out.println(p.nombre()); // Juan

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


Podemos hacer un constructor personalizado por ejemplo para validar datos:


public record Persona(String nombre, int edad) {

    public Persona {

        if (edad < 0) {

            throw new IllegalArgumentException("Edad inválida");

        }

    }

}


Este es un constructor compacto.

Los records no son solo datos, también pueden tener lógica:


public record Persona(String nombre, int edad) {

    public boolean esMayorDeEdad() {

        return edad >= 18;

    }

}


Restricciones importantes:

  • No podés extender otras clases
  • Los atributos son implícitamente final
  • No hay setters
  • No están pensados para entidades mutables (ej: JPA)


¿Cuándo usar Records?

Usalos cuando:

✔ Tenés objetos de solo datos (DTOs)

✔ Querés inmutabilidad

✔ Buscás claridad y menos boilerplate


Evitarlos cuando:

❌ Necesitás mutabilidad

❌ Estás modelando entidades complejas

❌ Usás frameworks que requieren setters (como JPA tradicional)


Con features modernas (Java 21+), los records se integran muy bien con pattern matching:


if (obj instanceof Persona(String nombre, int edad)) {

    System.out.println(nombre);

}


Esto hace el código más declarativo y expresivo.


Los records son una de las mejoras más importantes en Java moderno:

  • Reducen código repetitivo
  • Fomentan inmutabilidad
  • Mejoran la legibilidad


Son ideales para modelar datos de forma clara y segura.

domingo, 19 de abril de 2026

¿Por qué Java usa Streams?


Cuando Java introdujo Streams en Java 8, no fue solo para “hacer código más lindo”, sino para cambiar la forma en que procesamos colecciones.


Pero otros lenguajes de la JVM ya resolvían esto de forma distinta.

Entonces, la pregunta es: ¿por qué Java eligió Streams y no otro modelo?


Antes de Java 8:

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

for (String s : lista) {

    if (s.length() > 3) {

        result.add(s.toUpperCase());

    }

}


Mucho boilerplate

  • No es declarativo
  • Difícil de paralelizar
  • Mezcla lógica con control de flujo


La solución: Streams en Java


Con Streams:


List<String> result = lista.stream()

    .filter(s -> s.length() > 3)

    .map(String::toUpperCase)

    .toList();


Claves del diseño:

  • Evaluación lazy
  • Pipeline de operaciones
  • Separación entre datos y procesamiento
  • Fácil paralelización (parallelStream())


Java construyó un modelo nuevo, no solo métodos en Collection ¿y por qué? Otros lenguajes los hacían bien. 

Scala

lista.filter(_.length > 3).map(_.toUpperCase)


Características

  • Colecciones inmutables por defecto
  • Operaciones directamente sobre la colección
  • Lazy solo si usás View o LazyList

Scala no necesita Streams porque su API de colecciones ya es funcional


Kotlin

lista.filter { it.length > 3 }

     .map { it.uppercase() }


Características:

  • API funcional sobre colecciones
  • Operaciones eager por defecto


Para lazy:

lista.asSequence()

    .filter { it.length > 3 }

    .map { it.uppercase() }


Diferencia clave

Kotlin separa:

  • List (eager)
  • Sequence (lazy)


Java unificó esto en Streams.


Groovy

lista.findAll { it.length() > 3 }

     .collect { it.toUpperCase() }


Características

  • Muy expresivo
  • Dinámico
  • API funcional desde hace años


Diferencia clave

Más simple, pero:

  • menos eficiente
  • no lazy por defecto
  • sin optimización tipo pipeline


Entonces ¿Por qué Java eligió Streams?


Java tenía restricciones fuertes:

1. Compatibilidad hacia atrás, no podía romper Collection

2. Necesidad de lazy evaluation para evitar:

  • listas intermedias
  • consumo extra de memoria


3. Paralelismo

Streams permiten: lista.parallelStream() sin cambiar el código lógico


4. Pipeline optimizable


La JVM puede optimizar: filter → map → reduce, como una sola operación


Java no eligió Streams por casualidad.

Fue una decisión para:

  • mantener compatibilidad
  • introducir programación funcional
  • mejorar performance
  • habilitar paralelismo


Mientras otros lenguajes:

  • ya eran funcionales (Scala)
  • o tomaron caminos más simples (Kotlin, Groovy)



sábado, 18 de abril de 2026

Java 26: Hacia dónde evoluciona la plataforma


Java 26 representa una versión en evolución dentro del ciclo de releases de Java, donde se profundizan cambios importantes en concurrencia, rendimiento y modelo de datos.

Tipo de release: No LTS

Estado: 🧪 En desarrollo / Early Access

Enfoque: innovación + maduración de features clave


Concurrencia (Project Loom)

Java continúa consolidando un nuevo modelo de concurrencia.


Structured Concurrency (posible estabilización)

Permite agrupar tareas relacionadas como una unidad lógica

Simplifica:

  • manejo de errores
  • cancelación
  • sincronización


Hace que el código concurrente sea más legible y seguro


Virtual Threads (optimización continua)

  • Mejor integración con APIs existentes
  • Ajustes de rendimiento
  • Mayor adopción en frameworks


Lenguaje: menos boilerplate


Pattern Matching (más evolución)

  • Código más declarativo
  • Reducción de casts explícitos
  • Mejor integración con `switch`


Java sigue acercándose a un estilo más expresivo sin perder claridad.

Interoperabilidad nativa

Foreign Function & Memory API (FFM)

  • Más cerca de ser estándar definitivo
  • Alternativa real a JNI


Permite:

  • acceso eficiente a memoria off-heap
  • llamadas a librerías nativas


Clave para aplicaciones de alto rendimiento


Proyecto Valhalla (avance gradual)

Uno de los cambios más importantes a largo plazo.


Value Objects (en progreso)

  • Objetos sin identidad
  • Mejor uso de memoria
  • Mayor eficiencia en estructuras de datos


Impacto esperado:

  • colecciones más rápidas
  • menos overhead de objetos


JVM y rendimiento


  • Mejoras continuas en:
    • G1
    • ZGC
  • Optimización del JIT
  • Mejor uso de CPU moderna


Java 26 no es una versión de adopción masiva, sino una versión que:

  • empuja nuevas ideas
  • estabiliza features incubadas
  • prepara cambios grandes a futuro


martes, 14 de abril de 2026

Alias en imports en Java: lo que no existe (y cómo resolverlo)


Cuando venís de otros lenguajes, es común esperar algo como esto:

import com.ejemplo.A as A1; // ❌


Pero en Java esto simplemente no existe.


 ¿Qué son los alias en imports?


Un alias permite:

  • Importar dos clases con el mismo nombre
  • Y diferenciarlas con nombres alternativos


Ejemplo típico en otros lenguajes:


Kotlin

import com.ejemplo.a.Clase as ClaseA

import com.ejemplo.b.Clase as ClaseB


Scala

import com.ejemplo.a.{Clase => ClaseA}

import com.ejemplo.b.{Clase => ClaseB}


C#

using ClaseA = Ejemplo.A.Clase;

using ClaseB = Ejemplo.B.Clase;


¿Qué pasa en Java?


Si tenés dos clases con el mismo nombre:


import com.ejemplo.a.Clase;

import com.ejemplo.b.Clase; // ❌ conflicto


Java no sabe cuál usar.

La solución es tenés que usar el nombre completo en al menos uno:


import com.ejemplo.a.Clase;


public class Main {

    public static void main(String[] args) {

        Clase a = new Clase();

        com.ejemplo.b.Clase b = new com.ejemplo.b.Clase();

    }

}


¿Por qué Java no tiene alias?


El lenguaje prioriza:

  • Simplicidad
  • Legibilidad explícita
  • Evitar ambigüedades en compilación

No hay transformación de nombres en imports


Problemas reales que genera:

  • Código más verboso
  • Menor ergonomía
  • Conflictos frecuentes en proyectos grandes
  • Difícil integración entre librerías con naming similar

Java no soporta alias en imports y la solución es usar nombres completos. Pero otros lenguajes modernos sí lo resuelven mejor


viernes, 10 de abril de 2026

Java 25: Evolución y consolidación de la plataforma


Java SE 25 continúa el camino de modernización de Java, con foco en concurrencia, rendimiento y maduración de features introducidas en versiones anteriores.

Ojo es No LTS


Concurrencia (Project Loom)


Java sigue apostando fuerte por la concurrencia moderna.

Virtual Threads (madurez)

  • Totalmente integrados en el ecosistema
  • Mejoras en estabilidad y rendimiento
  • Transparencia casi total respecto a threads tradicionales


Beneficio clave: Escalar a miles de tareas concurrentes sin complejidad extra


Scoped Values (evolución)

Alternativa moderna a ThreadLocal:


ScopedValue<String> user = ScopedValue.newInstance();


ScopedValue.where(user, "Emanuel").run(() -> {

    System.out.println(user.get());

});


  • Inmutables
  • Seguros en entornos concurrentes
  • Ideales para usar con Virtual Threads


Structured Concurrency (avance)

  • Mejor organización de tareas relacionadas
  • Manejo más claro de errores y cancelaciones
  • Código más predecible


Lenguaje: más simple y expresivo


Pattern Matching (refinamientos)

  • Mejoras en switch
  • Menos casting manual
  • Código más limpio


switch (obj) {

    case String s -> System.out.println(s);

    case null -> System.out.println("null");

    default -> {}

}


Interoperabilidad


Foreign Function & Memory API

  • Más estable y usable
  • Reemplazo progresivo de JNI


Permite:

  • llamar a código nativo
  • manejar memoria off-heap de forma segura


JVM y rendimiento

Garbage Collectors


Mejoras continuas en:

  • G1
  • ZGC


Optimizaciones generales

  • Menor latencia
  • Mejor throughput
  • Ajustes en el JIT


Ecosistema

  • Mejor integración con frameworks modernos
  • Tooling más alineado con Virtual Threads
  • Preparación para proyectos futuros como Valhalla


Java 25 no busca revolucionar, sino consolidar lo que ya empezó a cambiar Java profundamente:

  • Concurrencia moderna ya usable en producción
  • Lenguaje más expresivo
  • Mejor performance


martes, 7 de abril de 2026

Java 24: Novedades y características principales


Java SE 24 continúa la evolución de la plataforma con foco en rendimiento, concurrencia y simplificación del lenguaje.

Ojo es no LTS!


Concurrencia moderna (Project Loom)

Uno de los ejes más importantes sigue siendo la evolución de la concurrencia.


Virtual Threads (mejoras)

  • Threads livianos gestionados por la JVM
  • Permiten manejar miles o millones de tareas concurrentes
  • Menor costo que los threads tradicionales


Structured Concurrency (incubating)

  • Permite tratar múltiples tareas como una sola unidad lógica
  • Mejora el manejo de errores y cancelaciones


Ejemplo conceptual:


try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {

    scope.fork(() -> servicioA());

    scope.fork(() -> servicioB());

    scope.join();

    scope.throwIfFailed();

}


Lenguaje: más expresividad


Pattern Matching (evolución)

  • Mejora continua en switch
  • Código más declarativo y menos verboso


switch (obj) {

    case String s -> System.out.println(s.length());

    case Integer i -> System.out.println(i * 2);

    default -> {}

}


Interoperabilidad nativa

Foreign Function & Memory API (evolución)

  • Reemplazo moderno de JNI
  • Acceso seguro a memoria fuera del heap
  • Llamadas a código nativo (C/C++)


Beneficios:

  • más performance
  •  menos complejidad que JNI


Rendimiento y JVM

Garbage Collectors

Mejoras en:

  • G1
  • ZGC


Optimizaciones generales

  • Mejor uso de CPU y memoria
  • Reducción de pausas


Otras mejoras

  • Refinamientos en APIs estándar
  • Mejoras internas en la JVM
  • Preparación para futuros proyectos como Valhalla


Java 24 no introduce cambios “revolucionarios”, pero sí consolida tendencias clave:

  • Concurrencia moderna (Loom)
  • Código más expresivo (Pattern Matching)
  • Mejor interoperabilidad (FFM API)
  • Performance constante


Es una versión que prepara el terreno para cambios más grandes en futuras releases.

sábado, 4 de abril de 2026

¿Cual es el estado de los lenguajes que corren sobre la plataforma Java?



La plataforma Java no es solo el lenguaje Java. Gracias a la JVM (Java Virtual Machine), es posible ejecutar múltiples lenguajes con distintos paradigmas y objetivos.

A continuación, un resumen breve de los más relevantes:


Java

Objetivo: Lenguaje generalista, orientado a objetos.

Uso típico: Backend, enterprise, Android (históricamente).

Estado: Activo y en constante evolución (LTS recientes, mejoras funcionales).


Kotlin

Objetivo: Alternativa moderna a Java, más concisa y segura.

Uso típico: Android, backend, multiplataforma.

Estado:  Muy activo, impulsado por JetBrains y adoptado oficialmente por Google.


Scala

Objetivo: Mezclar programación funcional y orientada a objetos.

Uso típico: Big Data, sistemas distribuidos.

Estado: Activo, pero con menor adopción reciente frente a Kotlin.


Groovy

Objetivo: Lenguaje dinámico para simplificar Java.

Uso típico: Scripts, testing, herramientas como Gradle.

Estado:  Estable, pero en segundo plano.


Clojure

Objetivo: Programación funcional pura (Lisp en la JVM).

Uso típico: Sistemas concurrentes, data processing.

Estado: Activo en nichos específicos.


Jython

Objetivo: Implementación de Python sobre la JVM.

Uso típico: Integración con ecosistema Java.

Estado: Limitado (sin soporte moderno de Python 3 completo).


JRuby

Objetivo: Ejecutar Ruby en la JVM.

Uso típico: Integración con sistemas Java.

Estado: Activo, pero nicho.


Frege

Objetivo: Lenguaje funcional inspirado en Haskell.

Uso típico: Académico / experimental.

Estado: Poco activo.


Eta

Objetivo: Llevar Haskell a la JVM.

Uso típico: Funcional puro sobre JVM.

Estado: Proyecto prácticamente detenido.


 JavaScript (GraalVM)

Objetivo: Ejecutar JavaScript en la JVM mediante GraalVM.

Uso típico: Polyglot, microservicios, scripting.

Estado:  Activo y en crecimiento.


Python (GraalVM)

Objetivo: Ejecutar Python sobre la JVM con GraalVM.

Uso típico: Integración polyglot.

Estado:  Experimental.


La JVM es en una plataforma polyglot, donde distintos lenguajes conviven según la necesidad.

Cursos Gugler: Inscripciones para el primer cuatrimestre del año 2026

Se encuentran abiertas las inscripciones para el primer cuatrimestre del año 2026, para todas las capacitaciones dictadas por el Laboratorio de Investigación Gugler. Podés asegurar tu lugar en el curso y comisión que desees !!!.

Las clases inician:

  • Lunes 06/04 , Jueves 09/04 o Sábado 11/04, según el curso que elegiste.

Podés inscribirte utilizando el siguiente enlace: INSCRIBIRME

Cursos, horarios y comisiones disponiblesINFO CURSOS






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