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.

Tipos paramétricos y convenciones del lenguaje

Genéricos, operadores y rangos cambian de sintaxis entre ecosistemas, pero las preguntas de diseño detrás son las mismas: qué varía, qué se comprueba y cuándo.

Equivalencia desde Kotlin

Genéricos y varianza

Cercana

Java usa invariancia por defecto y expresa covarianza o contravarianza donde se consume la API: ? extends T para producir y ? super T para consumir.

Referencia Kotlin: Source<out T> es covariante y Sink<in T> es contravariante.

Mecanismo
wildcards ? extends T y ? super T en el uso
Riesgo de diseño
Usar ? extends T para una colección a la que se necesita agregar valores.
Recomendación
Aplica PECS: producer extends, consumer super; mantén separados los roles de fuente y destino.

Diferencias relevantes

  • La varianza no se declara una vez en la interfaz como out o in de Kotlin.
  • Los wildcards describen la relación en cada parámetro y conviene aplicar la regla PECS.

Source<T> produce valores y Sink<T> los consume sin mezclar ambas responsabilidades.

interface Source<T> { T get(); }
static Animal read(Source<? extends Animal> source) { return source.get(); }

Fuentes

Equivalencia desde Kotlin

Operadores

Cercana

Java no permite sobrecargar operadores definidos por la persona usuaria: ni la pertenencia (in) ni la invocación (()) se pueden redefinir sobre un tipo propio. Un método con nombre, como contains, conserva la operación de dominio sin fingir una equivalencia sintáctica; para "invocar como función", la convención es implementar una interfaz funcional y llamar a su método abstracto.

Referencia Kotlin: operator fun contains permite escribir price in range para una pertenencia definida por el dominio.

Mecanismo
método explícito contains
Riesgo de diseño
Escribir una clase con un método invoke o call esperando que Java lo reconozca como operador, en vez de usar una interfaz funcional estándar.
Recomendación
Nombra el método según la operación de dominio (contains, test, matches) y usa las interfaces funcionales de java.util.function cuando el valor deba comportarse como una función.

Diferencias relevantes

  • La pertenencia es explícita (range.contains(price)) en vez de price in range.
  • No existe un operador de invocación: una interfaz funcional como Predicate<Double> se llama mediante test, no con () directamente sobre el objeto.

Comprueba si un precio de 79.9 cae dentro de un rango válido entre 50.0 y 100.0.

record PriceRange(double min, double max) {
    boolean contains(double price) { return price >= min && price <= max; }
}
new PriceRange(50.0, 100.0).contains(79.9);

Sin operador de invocación: se llama al método test de la interfaz funcional.

Predicate<Double> isAboveMinimum = price -> price >= 10.0;
isAboveMinimum.test(79.9);

Fuentes

Equivalencia desde Kotlin

Rangos

Cercana

IntStream.rangeClosed expresa un intervalo de enteros inclusivo. Es una secuencia de operaciones, no una colección reutilizable equivalente a toda progresión de Kotlin.

Referencia Kotlin: 1..limit representa una progresión de extremos inclusivos.

Mecanismo
IntStream.rangeClosed(1, limit)
Riesgo de diseño
Usar IntStream.range esperando que incluya limit.
Recomendación
Nombra el límite y elige rangeClosed solo cuando el contrato incluya explícitamente el extremo superior.

Diferencias relevantes

  • rangeClosed(1, limit) incluye ambos extremos; range excluye el superior.
  • Un stream se consume una vez y sus operaciones terminales disparan el recorrido.

Itera los enteros desde 1 hasta limit, incluyendo limit.

IntStream.rangeClosed(1, limit).forEach(System.out::println);

Fuentes