¿Por qué los programadores de Java lloran cuando se pasan a Kotlin?
\n\nImagine: usted escribe código en Java, abre un archivo, lee datos y luego… se olvida de cerrarlo. ¿Un dolor familiar? En Java, para esto se inventó la construcción try-with-resources, que llama automáticamente a close(). Pero cuando los desarrolladores se pasan a Kotlin, a menudo se quedan perplejos: «¿Dónde está mi querido try (Resource res = ...)?».
Spoiler: en Kotlin no existe try-with-resources como una construcción de lenguaje separada. Y esto es una buena noticia. En su lugar, el lenguaje ofrece una solución más elegante y concisa: la función use. Analicemos cómo funciona y por qué su código será más limpio.
El problema: los recursos deben cerrarse
\n\nCualquier recurso externo (archivo, conexión de red, base de datos) requiere una gestión manual. Si abre un FileInputStream y no lo cierra, el sistema puede «perder memoria» (fuga de memoria) y el archivo permanecerá bloqueado para otros procesos.
En Java, este problema se resuelve de dos maneras:
\n\n- \n
- La forma antigua: bloque
finallycon una llamada explícita aclose(). \n - La forma nueva:
try-with-resources(apareció en Java 7). \n
Kotlin fue más allá: utiliza funciones de extensión y lambdas para hacer que el código no solo sea seguro, sino también hermoso.
\n\n¿Cómo se ve try-with-resources en Java?
\n\nPara comparar, veamos el código Java clásico:
\n\ntry (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {\n String line = br.readLine();\n System.out.println(line);\n} catch (IOException e) {\n e.printStackTrace();\n}\n// br se cierra automáticamente\n\n\nTodo está bien, pero hay matices:
\n- \n
- Es necesario declarar la variable dentro de los paréntesis de
try. \n - Solo se admiten clases que implementan
AutoCloseable. \n - El código se vuelve voluminoso si hay varios recursos. \n
El enfoque de Kotlin: la función use
\n\nEn Kotlin, para trabajar de forma segura con recursos se utiliza la función use. Está definida como una extensión para la interfaz Closeable (y AutoCloseable para compatibilidad con Java).
Así es como se ve el mismo ejemplo en Kotlin:
\n\nimport java.io.BufferedReader\nimport java.io.FileReader\n\nfun main() {\n BufferedReader(FileReader("file.txt")).use { reader ->\n val line = reader.readLine()\n println(line)\n }\n // reader se cierra automáticamente\n}\n\n\n¿Limpio, conciso, sin try-catch innecesarios? No del todo. use por sí mismo no captura excepciones. Solo garantiza la llamada a close() al final del bloque. Si necesita manejar un error, agregue try-catch externamente:
try {\n BufferedReader(FileReader("file.txt")).use { reader ->\n val line = reader.readLine()\n println(line)\n }\n} catch (e: IOException) {\n println("Error al leer el archivo: ${e.message}")\n}\n\n\n¿Cómo funciona use por debajo?
\n\nPara entender la magia, echemos un vistazo al código fuente de Kotlin. La función use está definida en la biblioteca estándar más o menos así:
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {\n var exception: Throwable? = null\n try {\n return block(this)\n } catch (e: Throwable) {\n exception = e\n throw e\n } finally {\n when {\n exception == null -> close()\n else -> try {\n close()\n } catch (closeException: Throwable) {\n // suprime la excepción, la añade a la principal\n exception.addSuppressed(closeExcep