Try-with-resources в Kotlin: как избавиться от finally навсегда

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Почему Java-программисты плачут, когда переходят на Kotlin?

Представьте: вы пишете код на Java, открываете файл, читаете данные, а потом… забываете его закрыть. Знакомая боль? В Java для этого придумали конструкцию try-with-resources, которая автоматически вызывает close(). Но когда разработчики переходят на Kotlin, они часто впадают в ступор: «А где тут мой любимый try (Resource res = ...)?».

Спойлер: в Kotlin нет try-with-resources как отдельной языковой конструкции. И это хорошая новость. Вместо неё язык предлагает более элегантное и лаконичное решение — функцию use. Давайте разберёмся, как это работает и почему ваш код станет чище.

Проблема: ресурсы нужно закрывать

Любой внешний ресурс — файл, сетевое соединение, база данных — требует ручного управления. Если вы откроете FileInputStream и не закроете его, система может «потечь» (утечка памяти), а файл останется заблокированным для других процессов.

В Java эту проблему решают двумя способами:

  • Старый способ: блок finally с явным вызовом close().
  • Новый способ: try-with-resources (появился в Java 7).

Kotlin пошёл дальше: он использует функции расширения и лямбды, чтобы сделать код не только безопасным, но и красивым.

Как выглядит try-with-resources в Java?

Для сравнения посмотрим на классический Java-код:

try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
    String line = br.readLine();
    System.out.println(line);
} catch (IOException e) {
    e.printStackTrace();
}
// br закрывается автоматически

Всё хорошо, но есть нюансы:

  • Нужно объявлять переменную внутри скобок try.
  • Поддерживаются только классы, реализующие AutoCloseable.
  • Код становится громоздким, если ресурсов несколько.

Kotlin-подход: функция use

В Kotlin для безопасной работы с ресурсами используется функция use. Она определена как расширение для интерфейса Closeable (и AutoCloseable для совместимости с Java).

Вот как выглядит тот же пример на Kotlin:

import java.io.BufferedReader
import java.io.FileReader

fun main() {
    BufferedReader(FileReader("file.txt")).use { reader ->
        val line = reader.readLine()
        println(line)
    }
    // reader автоматически закрывается
}

Чисто, лаконично, без лишних try-catch? Не совсем. use сам по себе не ловит исключения. Он только гарантирует вызов close() в конце блока. Если нужно обработать ошибку, добавьте try-catch снаружи:

try {
    BufferedReader(FileReader("file.txt")).use { reader ->
        val line = reader.readLine()
        println(line)
    }
} catch (e: IOException) {
    println("Ошибка при чтении файла: ${e.message}")
}

Как работает use под капотом?

Чтобы понять магию, заглянем в исходники Kotlin. Функция use определена в стандартной библиотеке примерно так:

public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
    var exception: Throwable? = null
    try {
        return block(this)
    } catch (e: Throwable) {
        exception = e
        throw e
    } finally {
        when {
            exception == null -> close()
            else -> try {
                close()
            } catch (closeException: Throwable) {
                // подавляем исключение, добавляем его к основному
                exception.addSuppressed(closeException)
            }
        }
    }
}

Что здесь важно:

  • inline — функция встраивается в код на этапе компиляции, без накладных расходов.
  • finally гарантирует закрытие ресурса.
  • Подавленные исключения — если и close() выбросит ошибку, она не «съест» основное исключение, а добавится к нему через addSuppressed().

Работа с несколькими ресурсами

В Java при использовании нескольких ресурсов приходится вкладывать try друг в друга или разделять точкой с запятой. В Kotlin всё проще — используйте вложенные use или создавайте цепочки:

import java.io.*

fun main() {
    // Вложенные вызовы use
    FileInputStream("input.txt").use { input ->
        FileOutputStream("output.txt").use { output ->
            input.copyTo(output)
        }
    }
    println("Файл скопирован")
}

Или ещё более элегантный вариант с sequence и forEach, но для простоты оставим вложенность. Главное — каждый ресурс будет закрыт, даже если произойдёт ошибка.

Что делать, если класс не реализует Closeable?

Иногда вы работаете с библиотеками, где ресурсы нужно закрывать вручную, но они не наследуют Closeable. Например, старые Java-классы или свои собственные объекты.

В Kotlin можно использовать функцию use для любого объекта, реализующего AutoCloseable. Если ваш класс не реализует ничего — просто добавьте интерфейс:

class MyResource : AutoCloseable {
    override fun close() {
        println("Ресурс закрыт")
    }
    
    fun doWork() {
        println("Работаем...")
    }
}

fun main() {
    MyResource().use { resource ->
        resource.doWork()
    }
    // Вывод: Работаем... \n Ресурс закрыт
}

Теперь ваш кастомный ресурс работает как полноценный участник use-блока.

Работа с потоками ввода-вывода

Одна из самых частых задач — чтение или запись файлов. В Kotlin для этого есть удобные функции-расширения, которые уже используют use внутри:

import java.io.File

fun main() {
    // Чтение всех строк файла (use вызывается неявно)
    val lines = File("data.txt").readLines()
    lines.forEach { println(it) }
    
    // Запись в файл
    File("output.txt").writeText("Hello, Kotlin!")
    
    // Буферизованное чтение с ручным use
    File("data.txt").bufferedReader().use { reader ->
        val firstLine = reader.readLine()
        println("Первая строка: $firstLine")
    }
}

Функции readLines(), writeText(), bufferedReader() уже заботятся о закрытии ресурсов. Но если вам нужен более тонкий контроль — используйте use напрямую.

Срав

Блоги

Рекомендаций по книгам