Java

Relaciona las abstracciones presentadas en Kotlin con sus mecanismos idiomáticos en Java 25 LTS. La guía compara responsabilidades de diseño y garantías, no solo sintaxis.

Integración con el ecosistema

Un algoritmo correcto también debe compilarse, organizarse y ejecutarse: estas herramientas son las que hacen posible entregar y comparar una implementación real.

Equivalencia desde Kotlin

Herramientas

Cercana

Gradle, invocado mediante ./gradlew, resuelve dependencias declaradas, construye módulos y ejecuta tareas. test ejecuta solo las pruebas; build ya depende de test y check, así que ejecutarlo cubre la verificación completa sin llamar a test por separado antes.

Referencia Kotlin: Gradle, mediante el wrapper ./gradlew, resuelve las dependencias declaradas, construye los subproyectos registrados en settings.gradle.kts y ejecuta tareas como build, test y :app:run.

Mecanismo
Gradle mediante el wrapper ./gradlew, con build.gradle.kts y el catálogo de versiones libs.versions.toml
Riesgo de diseño
Usar una instalación global de Gradle y asumir que coincide con la versión del proyecto; o ejecutar test después de un build exitoso, que ya lo incluyó.
Recomendación
Ejecuta el wrapper del repositorio y declara versiones compartidas en el catálogo. Usa test para una verificación rápida durante el desarrollo y build como la verificación completa.

Diferencias relevantes

  • El wrapper es el ejecutable reproducible; build.gradle.kts y libs.versions.toml describen el build y las versiones.
  • Una tarea puede abarcar varios subproyectos registrados en settings.gradle.kts.
  • Un toolchain de Java 25 requiere Gradle 9.1 o posterior, tanto para compilar con ese lenguaje como para ejecutar Gradle sobre esa JVM.

Instala las dependencias declaradas, construye todos los módulos, ejecuta las pruebas y corre la aplicación de línea de comandos.

docker compose run --rm app ./gradlew test
docker compose run --rm app ./gradlew build
docker compose run --rm app ./gradlew :app:run

Dockerfile mínimo para el toolchain de Java.

FROM eclipse-temurin:25-jdk
WORKDIR /workspace
COPY . .

compose.yaml: construye la imagen y monta el proyecto para correr los comandos anteriores dentro del contenedor.

services:
  app:
    build: .
    volumes:
      - .:/workspace
    working_dir: /workspace

Fuentes

Equivalencia desde Kotlin

Módulos, workspaces y monorepos

Cercana

Un build multiproyecto de Gradle separa el ejecutable de módulos de dominio reutilizables y hace visible el sentido de las dependencias.

Referencia Kotlin: Un build multi-proyecto de Gradle declara sus subproyectos (:app, :optimization:core, :optimization:execution, :optimization:baselines, :optimization:metaheuristics:single-state, :reporting:fair) en settings.gradle.kts mediante include(...), mientras que la lógica de build compartida vive en builds incluidos aparte (build-logic, build-conventions) declarados con includeBuild(...); cada dependencia entre proyectos se declara explícitamente, por ejemplo implementation(projects.optimization.core) en el build.gradle.kts de :app.

Mecanismo
subproyectos de un build multi-proyecto Gradle (include(":modulo") en settings.gradle.kts)
Riesgo de diseño
Permitir que el módulo de dominio dependa de la CLI, serialización o infraestructura.
Recomendación
Haz que los adaptadores dependan del dominio y conserva las reglas de negocio independientes de Gradle y picocli.

Diferencias relevantes

  • include registra los subproyectos en settings.gradle.kts; una dependencia entre ellos se declara en el build del consumidor.
  • Un monorepo no requiere que todos los módulos formen parte de un único artefacto desplegable.

Organiza un ejecutable de línea de comandos que depende de módulos de dominio reutilizables, conservando el sentido de la dependencia.

// settings.gradle.kts
include(":app")
include(":optimization:core")

// app/build.gradle.kts
dependencies {
    implementation(projects.optimization.core)
}

Fuentes

Equivalencia desde Kotlin

Soporte CLI

Cercana

picocli adapta argumentos y salida de consola mediante anotaciones como @Command y @Option; no debe convertirse en el lugar donde vive el algoritmo.

Referencia Kotlin: Clikt define atom como comando raíz sin operación propia (NoOpCliktCommand) con los subcomandos random-search y local-search; cada uno extiende AbstractExperimentCommand, que declara --config y --report como opciones requeridas y --report-format con v4 por defecto, valida la configuración antes de ejecutar el experimento y traduce los errores esperados en CliktError.

Mecanismo
picocli con anotaciones (@Command, @Option) y CommandLine.execute
Riesgo de diseño
Mezclar análisis de argumentos, lectura de archivos y bucle de optimización en call().
Recomendación
Haz que el comando adapte E/S y delegue la ejecución a un servicio que pueda probarse sin proceso externo.

Diferencias relevantes

  • El comando suele implementar Callable<Integer> o Runnable y se ejecuta con CommandLine.execute.
  • La conversión de argumentos y la interacción con consola son responsabilidades de borde.

Ejecuta random-search con un archivo de configuración y escribe su reporte, mostrando ayuda generada y fallando antes de optimizar ante una opción inválida.

@Command(name = "random-search")
class RandomSearchCommand implements Callable<Integer> {
    @Option(names = "--config", required = true) File config;
    @Option(names = "--report", required = true) File report;
    @Option(names = "--report-format", defaultValue = "v4") String reportFormat;

    public Integer call() { /* decodifica, ejecuta y escribe el reporte */ return 0; }
}

Contrato observable compartido por cualquier reimplementación de la CLI.

$ atom random-search --config config.json --report report.json
Completed random-search: 2 runs; report format: v4; report: report.json

Fuentes

Equivalencia desde Kotlin

Serialización

Cercana

Jackson transforma JSON y DTOs, pero una deserialización correcta no prueba que el valor sea válido para el dominio.

Referencia Kotlin: kotlinx.serialization decodifica el JSON tipado mediante un Json compartido (prettyPrint = true, encodeDefaults = true) y un SerializersModule polimórfico para tipos como SearchSpace; readFromJson<T>() decodifica el archivo y deja que SerializationException se propague, mientras que un valor de dominio inválido, como un Delta no positivo, falla por separado en el constructor de su clase de valor.

Mecanismo
Jackson (ObjectMapper y anotaciones @JsonProperty)
Riesgo de diseño
Concluir que una lectura exitosa con ObjectMapper implica una configuración válida, o usar double primitivo para un campo opcional del DTO.
Recomendación
Decodifica, valida y convierte al dominio en pasos separados; comparte un ObjectMapper configurado. Usa Double en el DTO para un campo opcional y aplica el valor por defecto en la conversión.

Diferencias relevantes

  • Un DTO refleja el documento externo; el tipo de dominio expresa invariantes y vocabulario interno.
  • Errores sintácticos, estructurales y de dominio deben diagnosticarse en fronteras distintas.
  • Un campo double primitivo en el DTO no distingue un valor ausente de un 0.0 explícito; Double sí, y el valor por defecto se aplica al convertir al dominio.

Decodifica una configuración JSON con el objetivo, las semillas y el valor objetivo opcional de un experimento, distinguiendo un JSON sintácticamente inválido de un valor de dominio inválido.

package atom.serialization;

import java.util.List;

record ExperimentConfigDto(String objective, List<Integer> seeds, Double targetValue) {
    ExperimentConfig toDomain() {
        double effectiveTarget = targetValue == null ? 0.0 : targetValue;
        return new ExperimentConfig(objective, seeds, effectiveTarget);
    }
}

record ExperimentConfig(String objective, List<Integer> seeds, double targetValue) {}

package atom.serialization;

import com.fasterxml.jackson.databind.ObjectMapper;

final class ExperimentConfigReader {
    private ExperimentConfigReader() {}

    static ExperimentConfig read(String json) throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        return mapper.readValue(json, ExperimentConfigDto.class).toDomain();
    }
}

Payload compartido: seeds es requerido; targetValue es opcional y por defecto 0.0.

{
  "objective": "sphere",
  "seeds": [7, 11],
  "targetValue": 0.0
}

Fuentes

Ecosistemas de pruebas

Verificar el comportamiento de una implementación requiere elegir, entre varias estrategias, la que responde mejor a cada tipo de afirmación sobre el algoritmo.

Equivalencia desde Kotlin

Desarrollo guiado por pruebas (TDD)

Cercana

Formula primero el comportamiento con Given–When–Then. En rojo, ejecuta el test más pequeño que demuestra que falta; en verde, implementa solo lo necesario; al refactorizar, mejora el diseño sin cambiar el comportamiento. Repite el ciclo con la siguiente afirmación. BDD puede ayudar a descubrir o expresar el comportamiento, pero Gherkin y los nombres legibles no son requisitos de TDD.

Referencia Kotlin: TDD parte por una afirmación observable y usa el test para conducir un cambio pequeño.

Mecanismo
JUnit Jupiter con @Test y assertThrows
Riesgo de diseño
Escribir una prueba grande que atraviesa configuración, red o archivos antes de haber fijado una sola regla observable.
Recomendación
Elige una afirmación pequeña, ejecútala en rojo, hazla verde con el cambio mínimo y refactoriza solo mientras permanezca verde.

Diferencias relevantes

  • El ciclo rojo–verde–refactor se conserva entre ecosistemas; cambian el runner, la sintaxis de aserciones y la forma de expresar una excepción.
  • El test debe fallar por el comportamiento ausente, no por una dependencia, una ruta o una integración todavía no necesaria.

Un presupuesto de evaluación positivo se acepta y uno no positivo se rechaza.

@Test
void rejectsANonPositiveBudget() {
    assertThrows(IllegalArgumentException.class, () -> Budget.of(0));
}

Fuentes

Equivalencia desde Kotlin

Selección de estrategia de pruebas

Cercana

Empieza por la afirmación: usa un ejemplo enfocado para un comportamiento conocido, DDT para una matriz finita y PBT para una ley general. Añade pruebas de componente o integración cuando colaboran módulos, pruebas de contrato para una representación compartida y una verificación de entrega cuando importa el entorno documentado. Para algoritmos estocásticos controla colaboradores, conserva semillas reproducibles y prueba invariantes o evidencia estadística, no una trayectoria casual.

Referencia Kotlin: Seleccionar pruebas parte de la afirmación, la frontera afectada y las dependencias que deben controlarse.

Mecanismo
JUnit Jupiter y Gradle para ejecutar evidencia enfocada, parametrizada e integrada
Riesgo de diseño
Usar una prueba end-to-end para demostrar una regla local, o adoptar PBT solo porque hay un generador disponible.
Recomendación
Elige la evidencia más pequeña que pueda observar y falsar la afirmación; amplíala solo cuando una frontera modificada lo requiera.

Diferencias relevantes

  • La selección depende de la afirmación y la frontera, no del lenguaje ni del tamaño de una función.
  • Una prueba más amplia complementa la evidencia enfocada de TDD; no la reemplaza.

La afirmación determina la evidencia más pequeña adecuada antes de ampliar la verificación.

// Regla conocida: ejemplo enfocado.
assertThrows(IllegalArgumentException.class, () -> Budget.of(0));
// Matriz finita: @ParameterizedTest.
// Ley general: una propiedad con jqwik.

Fuentes

Equivalencia desde Kotlin

Pruebas unitarias

Cercana

JUnit Jupiter verifica comportamientos rápidos, deterministas y aislados mediante anotaciones y aserciones. La unidad es el comportamiento, no necesariamente una clase.

Referencia Kotlin: Una prueba unitaria llama al analizador de presupuesto y compara el valor o el error de validación.

Mecanismo
JUnit Jupiter con @Test, assertThrows y Gradle
Riesgo de diseño
Iniciar la CLI completa o Docker para comprobar una regla local del constructor.
Recomendación
Prueba el invariante en el nivel más bajo que lo expone; deja las pruebas de integración para la composición entre capas.

Diferencias relevantes

  • JUnit organiza las pruebas con anotaciones como @Test y assertThrows.
  • Kotest ofrece estilos y matchers distintos, pero ambas herramientas verifican expectativas observables.

Acepta el presupuesto 1000 y rechaza el presupuesto -1 antes de ejecutar optimización.

@Test
void rejectsNegativeBudget() {
    assertThrows(IllegalArgumentException.class, () -> parseBudget("-1"));
}

Fuentes

Equivalencia desde Kotlin

Frameworks y estilos BDD

Cercana

BDD es un proceso para descubrir y especificar comportamiento mediante ejemplos compartidos. Cucumber-JVM ejecuta Gherkin, pero su presencia no convierte por sí sola un proyecto en BDD.

Referencia Kotlin: Un escenario describe la configuración observable; analizar el comando no ejecuta el algoritmo.

Mecanismo
Cucumber-JVM integrado con JUnit Platform
Riesgo de diseño
Escribir escenarios que describen clics, métodos o detalles de implementación en vez de resultados observables.
Recomendación
Formula primero el ejemplo en lenguaje de dominio y usa Gherkin ejecutable solo si la conversación compartida lo necesita.

Diferencias relevantes

  • Los escenarios viven en Gherkin y los pasos Java conectan ese lenguaje de dominio con el sistema.
  • Cucumber agrega una capa de traducción y mantenimiento que solo se justifica para una especificación compartida.

Describe que atom random-search --objective sphere --budget 1000 --seed 11 produce una configuración válida sin ejecutar optimización.

@Given("a budget of {int} evaluations")
public void budget(int value) { arguments = List.of("--budget", String.valueOf(value)); }

@Then("a valid experiment configuration is produced")
public void validConfiguration() { assertDoesNotThrow(() -> parse(arguments)); }

Fuentes

Equivalencia desde Kotlin

Soporte DDT

Cercana

Las pruebas parametrizadas de JUnit ejecutan una misma especificación sobre una tabla finita de casos elegidos por sus particiones y límites.

Referencia Kotlin: Los casos explícitos vinculan argumentos CLI con un resultado de análisis esperado.

Mecanismo
@ParameterizedTest y @MethodSource de JUnit Jupiter
Riesgo de diseño
Agregar filas equivalentes sin explicar qué clase de entrada representa cada una.
Recomendación
Diseña la tabla por clases de equivalencia y límites; separa resultados aceptados y rechazados si el control de flujo del test crece.

Diferencias relevantes

  • @CsvSource funciona para valores simples; @MethodSource permite casos de dominio más expresivos.
  • DDT enumera ejemplos concretos, mientras PBT genera valores a partir de un dominio.

Una tabla cubre presupuesto válido, faltante, negativo, objetivo desconocido, semilla repetida y opción desconocida.

@ParameterizedTest
@MethodSource("budgets")
void parsesBudget(String value, boolean valid) {
    assertEquals(valid, parseBudget(value).isPresent());
}

Fuentes

Equivalencia desde Kotlin

Soporte PBT

Cercana

jqwik verifica leyes generales sobre valores generados, reduce contraejemplos y permite reproducir fallas. Complementa las pruebas de ejemplo, no las reemplaza.

Referencia Kotlin: Una propiedad general se ejecuta sobre valores generados y comunica cómo repetir un contraejemplo.

Mecanismo
jqwik, integrado con JUnit Platform y propiedades @Property
Riesgo de diseño
Convertir un ejemplo fijo, como el presupuesto 1000, en una propiedad trivial.
Recomendación
Úsalo para invariantes amplios —límites, monotonicidad, conservación— y conserva ejemplos para regresiones y mensajes específicos. Las notas de la versión 1.10 de jqwik desaconsejan su uso con agentes de IA; confirma con el curso que la dependencia sigue vigente antes de apoyarte en ella para una tarea.

Diferencias relevantes

  • @Property cuantifica sobre datos generados y @ForAll declara sus entradas.
  • Un generador que produce directamente valores válidos es preferible a filtrar muchos casos con supuestos.

Para cualquier texto decimal positivo aceptado, el presupuesto resultante es positivo; los casos inválidos se reducen y se pueden reproducir.

@Property
void positiveBudgetsRemainPositive(@ForAll @Positive int budget) {
    assertTrue(parseBudget(Integer.toString(budget)).get() > 0);
}

Fuentes

Equivalencia desde Kotlin

DSLs de aserciones

Cercana

AssertJ añade una DSL fluida y diagnósticos especializados a las aserciones de JUnit; no reemplaza una expectativa precisa.

Referencia Kotlin: Las aserciones expresan el resultado observable de una configuración sin ocultar su estructura relevante.

Mecanismo
assertThat fluido de AssertJ además de las aserciones de JUnit
Riesgo de diseño
Escribir una cadena extensa que oculta cuál requisito falló o comparar estructura interna irrelevante.
Recomendación
Prefiere aserciones pequeñas y enfocadas sobre la estructura observable que el test promete.

Diferencias relevantes

  • assertThat pone el valor real al inicio y conserva el tipo para encadenar verificaciones.
  • Las aserciones de colecciones y excepciones expresan mejor el contrato que comparaciones manuales.

Una configuración analizada contiene objective = sphere, budget = 1000 y seeds = [11]; una falla debe identificar esperado y recibido.

assertThat(config)
    .extracting(ExperimentConfig::objective, ExperimentConfig::budget, ExperimentConfig::seeds)
    .containsExactly("sphere", 1000, List.of(11));

Fuentes