Por que programadores Java choram quando migram para Kotlin?
\n\nImagine: você escreve código em Java, abre um arquivo, lê os dados e depois… esquece de fechá-lo. Dor familiar? Em Java, para isso foi criada a construção try-with-resources, que chama automaticamente o close(). Mas quando os desenvolvedores migram para Kotlin, muitas vezes ficam confusos: "Onde está meu amado try (Resource res = ...)?".
Spoiler: em Kotlin não existe try-with-resources como uma construção de linguagem separada. E isso é uma boa notícia. Em vez disso, a linguagem oferece uma solução mais elegante e concisa — a função use. Vamos entender como isso funciona e por que seu código ficará mais limpo.
O problema: recursos precisam ser fechados
\n\nQualquer recurso externo — arquivo, conexão de rede, banco de dados — requer gerenciamento manual. Se você abrir um FileInputStream e não fechá-lo, o sistema pode "vazar" (vazamento de memória) e o arquivo pode ficar bloqueado para outros processos.
Em Java, esse problema é resolvido de duas maneiras:
\n\n- \n
- Maneira antiga: bloco
finallycom chamada explícita aclose(). \n - Maneira nova:
try-with-resources(introduzido no Java 7). \n
Kotlin foi além: ele usa funções de extensão e lambdas para tornar o código não apenas seguro, mas também bonito.
\n\nComo é o try-with-resources em Java?
\n\nPara comparação, vejamos o código Java clássico:
\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 é fechado automaticamente\n\n\nTudo bem, mas há nuances:
\n- \n
- É necessário declarar a variável dentro dos parênteses do
try. \n - Suporta apenas classes que implementam
AutoCloseable. \n - O código fica volumoso se houver vários recursos. \n
Abordagem Kotlin: a função use
\n\nEm Kotlin, para trabalhar com recursos de forma segura, usa-se a função use. Ela é definida como uma extensão para a interface Closeable (e AutoCloseable para compatibilidade com Java).
Veja como fica o mesmo exemplo em 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 é fechado automaticamente\n}\n\n\nLimpo, conciso, sem try-catch desnecessário? Não exatamente. O use por si só não captura exceções. Ele apenas garante a chamada de close() ao final do bloco. Se precisar tratar um erro, adicione um try-catch externo:
try {\n BufferedReader(FileReader("file.txt")).use { reader ->\n val line = reader.readLine()\n println(line)\n }\n} catch (e: IOException) {\n println("Erro ao ler o arquivo: ${e.message}")\n}\n\n\nComo o use funciona por baixo dos panos?
\n\nPara entender a mágica, vamos dar uma olhada no código-fonte do Kotlin. A função use é definida na biblioteca padrão mais ou menos assim:
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 a exceção, adiciona-a à exceção principal\n exception.addSuppressed(closeExcep