Avatar ·

Как преобразовать строку (String) в число (Long) в Kotlin? Безопасные способы и обработка ошибок

Привет, коллеги! Я только начал изучать Kotlin (перешел с Java) и столкнулся с, казалось бы, простой задачей. Мне нужно прочитать ID пользователя из текстового поля в приложении (это строка) и передать его в функцию, которая ожидает Long. В Java я обычно использовал `Long.parseLong(str)` или `Long.valueOf(str)`, оборачивая это в try-catch. Но в Kotlin я вижу несколько разных методов: `.toLong()`, `.toLongOrNull()`, какие-то функции расширения... И я запутался, какой из них правильный и безопасный. Вот пример моего кода, который сейчас падает с исключением: ```kotlin fun main() { val userInput: String = "123abc" // пользователь случайно ввел буквы val userId: Long = userInput.toLong() // NumberFormatException! println("User ID: $userId") } ``` Я понимаю, что нужно обрабатывать ошибки, но хочется сделать это красиво, в «kotlin-стайл». Плюс, что если строка может быть `null`? Или содержать пробелы? Есть ли универсальный способ, который не выбросит исключение, а вернет, например, значение по умолчанию или `null`? Подскажите, пожалуйста, как правильно и безопасно конвертировать String в Long в Kotlin, с учетом всех возможных подводных камней (null, мусор в строке, пустая строка). Спасибо! <<>>

Основные способы преобразования String в Long

Вы абсолютно правы: прямое использование .toLong() без проверки — это путь к NumberFormatException. Kotlin предлагает гораздо более элегантные и безопасные альтернативы. Давайте разберем их по порядку.

1. Стандартный метод .toLong() (с обработкой исключений)

Это прямой аналог Java-метода Long.parseLong(). Он выбрасывает исключение, если строка не является валидным числом:

fun main() {
    val validInput = "12345"
    val invalidInput = "12abc"
    val nullInput: String? = null

    // Работает
    val number1: Long = validInput.toLong()
    println(number1) // 12345

    // Падает с NumberFormatException
    // val number2 = invalidInput.toLong()

    // Падает с NullPointerException (если строка nullable)
    // val number3 = nullInput!!.toLong()
}

Когда использовать: только если вы на 100% уверены в корректности строки и готовы ловить исключения через try-catch.

2. Безопасный метод .toLongOrNull() (рекомендуемый)

Это лучший способ для большинства случаев. Метод возвращает Long? (nullable Long): если строка валидна — число, иначе — null. Никаких исключений!

fun main() {
    val userInput = "123abc"
    val userId: Long? = userInput.toLongOrNull()
    
    // Безопасная обработка
    val result = userId ?: -1L // значение по умолчанию
    println("User ID: $result") // User ID: -1

    // Или через when
    when (userId) {
        null -> println("Ошибка ввода!")
        else -> println("ID = $userId")
    }
}

Важно: этот метод также обрабатывает пробелы? Да, но только если строка состоит из цифр и, опционально, знака минус в начале. Пробелы в начале/конце НЕ обрезаются автоматически.

3. Комбинация .trim().toLongOrNull() (для пользовательского ввода)

Если вы работаете с вводом от пользователя (например, из EditText или консоли), всегда удаляйте лишние пробелы:

fun parseUserInput(input: String?): Long? {
    return input
        ?.trim()           // удаляем пробелы, если строка не null
        ?.toLongOrNull()   // конвертируем или возвращаем null
}

fun main() {
    println(parseUserInput("  42  "))    // 42
    println(parseUserInput(""))          // null
    println(parseUserInput("   "))       // null (пустая после trim)
    println(parseUserInput(null))        // null
    println(parseUserInput("3.14"))      // null (не Long)
}

4. Расширение String?.toLongOrDefault(default)

Если вам часто нужно значение по умолчанию, можно написать свой extension:

fun String?.toLongOrDefault(default: Long = 0L): Long {
    return this?.trim()?.toLongOrNull() ?: default
}

fun main() {
    println("456".toLongOrDefault())        // 456
    println("abc".toLongOrDefault(-1L))     // -1
    println(null.toLongOrDefault(42L))      // 42
}

5. Использование toLong(radix) для других систем счисления

Kotlin поддерживает конвертацию из строк в разных системах счисления (radix = 2..36):

fun main() {
    val binaryString = "1010"
    val hexString = "FF"
    
    println(binaryString.toLong(2)) // 10 (двоичная)
    println(hexString.toLong(16))   // 255 (шестнадцатеричная)
    
    // Безопасные версии тоже есть:
    println(binaryString.toLongOrNull(2))  // 10
    println("GG".toLongOrNull(16))         // null
}

Резюме: какой метод выбрать?

  • Для надежного production-кода: используйте .trim().toLongOrNull() + оператор Элвиса (?:) для значения по умолчанию.
  • Если нужен null при ошибке: .toLongOrNull() (с предварительным trim при необходимости).
  • Если строка гарантированно корректна: .toLong(), но лучше всё равно перестраховаться.
  • Для nullable строк: всегда используйте безопасный вызов ?.toLongOrNull().

Главное преимущество Kotlin перед Java в этом вопросе — вы можете избежать исключений вообще, работая через nullable типы и функции с суффиксом OrNull. Это делает код чище и безопаснее.

<<>>

Альтернативный подход: использование NumberFormatException в стиле Kotlin

Хотя toLongOrNull() — предпочтительный способ, иногда вам может понадобиться явно обработать исключение, например, чтобы залогировать ошибку или показать пользователю сообщение. В Kotlin это делается лаконично через runCatching:

fun parseWithLogging(input: String): Long? {
    return runCatching {
        input.trim().toLong()
    }.onFailure { exception ->
        // Логируем ошибку
        println("Не удалось распарсить '$input': ${exception.message}")
    }.getOrNull()
}

fun main() {
    println(parseWithLogging("100"))      // 100
    println(parseWithLogging("abc"))      // null (с логом в консоли)
}

runCatching — это обертка над try-catch, которая возвращает Result. Вы можете использовать .getOrDefault() или .getOrThrow() для гибкой обработки.

Дополнительный совет: если вы парсите большие объемы данных (например, из CSV-файла) и производительность критична, используйте java.lang.Long.parseLong() напрямую — он работает чуть быстрее, чем Kotlin-аналоги. Но для 99% приложений разница незаметна.

Также помните, что Kotlin поддерживает символ подчеркивания в числах для читаемости: "1_000_000".toLong() вернет 1000000.