Как читать текстовый файл из папки resources в Kotlin: пошаговый туториал
Привет, друг! Если ты когда-нибудь писал на Kotlin под Android или просто создавал консольное приложение, то наверняка сталкивался с простой, но коварной задачей: как прочитать текстовый файл, который лежит в папке resources? Казалось бы, что тут сложного? Но если заглянуть на Stack Overflow, вопрос "How to read a text file from resources in Kotlin?" набирает тысячи просмотров. Значит, проблема реальная.
В этой статье мы разберём 5 надёжных способов сделать это. Ты узнаешь, в чём разница между javaClass и ClassLoader, как не нарваться на NullPointerException и что делать, если твой проект — библиотека, а не приложение. Поехали!
1. Подготовка: куда класть файл?
Прежде чем читать, файл нужно положить в правильное место. В стандартном проекте Kotlin (Gradle, Maven) папка для ресурсов обычно находится здесь:
src/main/resources/
Для тестов — src/test/resources/. Если у тебя Android-проект, ресурсы кладут в src/main/res/raw/ (но там своя логика, об этом ниже). Мы же сосредоточимся на чистом Kotlin/JVM.
Создадим простой файл data.txt с содержимым:
Привет, мир!
Это строки из ресурсов.
Kotlin рулит.
Отлично, инструмент готов. Теперь давай посмотрим, как его открыть.
2. Способ №1: Классика через javaClass
Самый популярный и интуитивно понятный способ — использовать javaClass (это Kotlin-обёртка над getClass() в Java).
import java.io.File
fun readFromResources(fileName: String): String {
val inputStream = this::class.java.classLoader?.getResourceAsStream(fileName)
?: throw IllegalArgumentException("Файл $fileName не найден в ресурсах")
return inputStream.bufferedReader().readText()
}
Разбор полётов:
this::class.java— получаем Java-класс текущего объекта..classLoader— берём ClassLoader (загрузчик классов).getResourceAsStream(fileName)— открываем поток.- Оператор Элвиса
?:бросает исключение, если файла нет.
Вызываем:
fun main() {
val content = readFromResources("data.txt")
println(content)
}
На выходе увидим три строки из нашего файла. Просто и элегантно.
3. Способ №2: Используем object для утилит
Если ты пишешь утилитарный класс, удобно вынести логику в object (синглтон). В Kotlin это делается так:
object ResourceReader {
fun readText(fileName: String): String {
val stream = ResourceReader::class.java.classLoader
.getResourceAsStream(fileName)
?: throw FileNotFoundException("Ресурс $fileName не найден")
return stream.bufferedReader().readText()
}
}
// Использование:
fun main() {
val text = ResourceReader.readText("config.json")
println(text)
}
Обрати внимание: мы используем ResourceReader::class.java — это работает, потому что object — это синглтон, у которого есть класс.
4. Способ №3: Читаем как URL (для получения пути)
Иногда нужно не содержимое, а путь к файлу (например, для передачи в другую библиотеку). Тогда используем getResource():
fun getResourcePath(fileName: String): String {
val url = this::class.java.classLoader.getResource(fileName)
?: throw RuntimeException("Ресурс $fileName не найден")
return url.path
}
Но будь осторожен: если проект запакован в JAR, url.path может указывать внутрь архива, и File не сможет его прочитать. В этом случае лучше всегда использовать поток (getResourceAsStream).
5. Способ №4: Чтение из нескольких файлов (список)
Допустим, у тебя папка resources/data/ с кучей текстовых файлов. Как прочитать все?
import java.io.File
import java.net.URL
fun readAllTextFilesFromFolder(folderName: String): Map<String, String> {
val classLoader = this::class.java.classLoader
val resourceUrl = classLoader.getResource(folderName)
?: return emptyMap()
val files = File(resourceUrl.toURI()).listFiles() ?: return emptyMap()
return files.filter { it.extension == "txt" }.associate { file ->
file.name to file.readText()
}
}
Этот метод вернёт Map, где ключ — имя файла, значение — его содержимое. Удобно для конфигов или локализации.
6. Способ №5: Android-специфика (res/raw)
Если ты разрабатываешь под Android, не используй getResourceAsStream — там своя система ресурсов. Вместо этого:
import android.content.Context
import java.io.BufferedReader
import java.io.InputStreamReader
fun readRawTextFile(context: Context, resId: Int): String {
val inputStream = context.resources.openRawResource(resId)
val reader = BufferedReader(InputStreamReader(inputStream))
return reader.readText()
}
Вызов из Activity:
val text = readRawTextFile(this, R.raw.data)
Главное отличие — файл лежит в res/raw/, а идентификатор генерируется автоматически в R.raw.*.
7. Типичные ошибки и как их избежать
Давай разберём три самые частые проблемы, которые валят новичков:
- NullPointerException — файл не найден. Всегда используй элвис-оператор
?:с осмысленным исключением. - FileNotFoundException в JAR — если ты пытаешься создать
Fileиз ресурса внутри архива, это не сработает. Используй только поток. - Разные ClassLoader'ы — в веб-контейнерах (Tomcat) или тестах иногда нужно использовать
Thread.currentThread().contextClassLoader.
Универсальный совет: для продакшена всегда используй getResourceAsStream(), а не getResource() + File.
8. Сравнение способов: что выбрать?
Вот краткая таблица для быстрого выбора:
- Простое чтение одного файла — способ №1 (javaClass).
- Утилитарный класс/библиотека — способ №2 (object).
- Нужен путь, а не содержимое — способ №3, но с о