Всем привет, меня зовут Сергей Прощаев. Я техлид в FinTech, каждый день работаю с Java и Kotlin, а ещё преподаю на курсах в Otus — помогаю разработчикам прокачиваться в современных технологиях. Продолжаю серию статей «Kotlin для новичков». В прошлый раз мы с вами настроили IntelliJ IDEA, разобрались с Gradle и даже запустили «Hello, World!». Надеюсь, у вас всё взлетело, и репозиторий с кодом уже форкнут.
Сегодня мы сделаем следующий, гораздо более важный шаг: начнём писать код, который действительно что‑то делает. Мы поговорим о фундаменте — переменных и базовых операциях. Знаете, обучая стажёров в FinTech, я заметил одну закономерность: новички часто запоминают синтаксис, но не понимают философию языка. Они пишут на Kotlin так, как писали на Java, и удивляются, почему код получается громоздким.
Наша цель сегодня — не просто выучить, что такое val и var, а прочувствовать, как Kotlin помогает нам проектировать правильные, надёжные программы с минимальными усилиями. Мы затронем темы, которые на первых порах кажутся разнородными (типы данных, строки, арифметика, ввод/вывод), но к концу статьи вы увидите, как они складываются в единую стройную картину.
Поехали!
1. В Kotlin всё начинается с объявления: val и var
В любой программе мы работаем с данными. В Kotlin, прежде чем использовать данные, мы должны ответить на вопрос: будут ли они меняться?
Здесь у нас есть два главных инструмента.
val(от value — значение). Это неизменяемая ссыл��а. Представьте себе ячейку в памяти, которую мы заполнили один раз и «запечатали». Изменить содержимое этой ячейки нельзя. В других языках это называют константой или final‑переменной.var(от variable). Это изменяемая ссылка. Содержимое такой ячейки можно перезаписывать сколько угодно раз.
fun main() { // Неизменяемая переменная (рекомендуется по умолчанию) val appName: String = "KotlinFinPortal" println("Добро пожаловать в $appName") // Изменяемая переменная var userScore: Int = 0 println("Ваш текущий счёт: $userScore") userScore = 42 // Перезаписываем значение println("Новый счёт: $userScore") // Ошибка! val нельзя переназначить. // appName = "NewName" }
Мой личный совет (из опыта продакшена): Я требую от своей команды использовать
valвезде, где это возможно. Почему? Это сильно упрощает чтение кода. Когда вы видитеval, вы сразу знаете, что значение не изменится где‑то в другом потоке или через 50 строк кода. Вам не нужно держать в голове историю этой переменной. Это снижает когнитивную нагрузку и количество багов. Если вы через полгода вернётесь к коду и увидитеvar, вы должны задаться вопросом: «А почему, собственно, оно меняется? Может быть, это можно переписать наval?».
2. Волшебство вывода типов (Type Inference)
В примере выше я специально написал тип (: String, : Int), чтобы было понятнее. Но Kotlin настолько умён, что в 90% случаев может определить тип сам. Это называется выводом типов.
Давайте посмотрим в примере:
fun main() { // Kotlin сам понимает, что это String и Int val appName = "KotlinFinPortal" var userScore = 0 // Тип Int val pi = 3.1415 // Тип Double val isEnabled = true // Тип Boolean }
Это лаконично, но здесь кроется небольшая ловушка для новичка. Посмотрите на userScore = 0. Компилятор вывел тип Int. А что, если вы попытаетесь присвоить ему дробное число?
userScore = 42.5 // Ошибка! Требуется Int, а найден Double.
Это «защита от дурака» 😊 надежная защита на уровне Kotlin. Код не скомпилируется, и вы на месте исправите логику, вместо того чтобы получить странное поведение в рантайме.
3. Базовые типы: всё — объект
В Java есть примитивы (int, boolean) и ссылочные типы (Integer, Boolean). В Kotlin этого разделения нет для нас как для разработчиков. Всё является объектом. Это удобно: мы можем вызвать метод у числа.
Давайте напишем еще один простой пример:
fun main() { val number = 42 println(number.toDouble()) // Превращаем Int в Double println(number.toString()) // Превращаем в строку println(42.dec()) // Метод dec() возвращает число на 1 меньше (41) }
Под капотом, конечно, для производительности там могут быть примитивы, но для нас это просто и единообразно.
Краткий обзор базовых типов, которые есть в Kotlin
В таблице приведены все типы данных, которые есть в Kotlin:
Категория | Тип | Размер (бит) | Мин. значение | Макс. значение | Точность (цифры) | Суффикс | Примечание |
Целые (signed) | Byte | 8 | -128 (-2⁷) | 127 (2⁷-1) | — | — | Самый маленький целочисленный |
Short | 16 | -32,768 (-2¹⁵) | 32,767 (2¹⁵-1) | — | — | ||
Int | 32 | -2,147,483,648 (-2³¹) | 2,147,483,647 (2³¹-1) | — | — | Тип по умолчанию для целых | |
Long | 64 | -9,223,372,036,854,775,808 (-2⁶³) | 9,223,372,036,854,775,807 (2⁶³-1) | — | L | Для больших чисел | |
Целые (unsigned) | UByte | 8 | 0 | 255 (2⁸-1) | — |
| Kotlin 1.3+ |
UShort | 16 | 0 | 65,535 (2¹⁶-1) | — |
| ||
UInt | 32 | 0 | 4,294,967,295 (2³²-1) | — |
| ||
ULong | 64 | 0 | 18,446,744,073,709,551,615 (2⁶⁴-1) | — | UL | ||
Вещественные | Float | 32 | 1.4E-45 | 3.4E+38 | 6–7 |
| IEEE 754 single precision |
Double | 64 | 4.9E-324 | 1.8E+308 | 15–16 | — | Тип по умолчанию для дробных | |
Логический | Boolean | 1 | false | true | — | — | Только |
Символьный | Char | 16 |
|
| — | — | Unicode символ |
Строка | String | variable | — | — | — | — | Последовательность Char |
Специальные | Unit | — | — | — | — | — | Аналог |
Nothing | 0 | — | — | — | — | Тип без значений (never returns) | |
Any | — | — | — | — | — | Корень иерархии типов (non‑nullable) | |
Any? | — | — | — | — | — | Корень иерархии типов (nullable) |
Кратко остановлюсь на основных.
Числовые:
Byte(-128..127)Short(-32768..32767)Int(миллиарды, используется чаще всего)Long(ещё больше, нужно указывать суффиксL, например100L)Float(с плавающей точкой, суффиксf, например3.14f)Double(с плавающей точкой двойной точности, используется по умолчанию для дробей)
Символы:
Char(один символ в кавычках, например'A')Логические:
Boolean(true/false)Строки:
String(не является примитивом, но поддержана на уровне языка)
4. Работа с символами (Char) и Unicode
Один из моих любимых вопросов на собеседовании: «Что будет, если сложить два символа?». Новички часто теряются. Давайте разбираться.
Тип Char хранит один символ в кодировке Unicode. Это значит, что переменная типа Charможет хранить не только буквы английского алфавита, но и иероглифы, эмодзи и любые другие символы.
Вернемся в интегрированную среду разработки IntelliJ IDEA и напишем еще пример:
fun main() { val letterA: Char = 'A' val emoji: Char = '\u0394' // Дельта (Δ) в Unicode val sigma: Char = 'Σ' println("Буква: $letterA, код: ${letterA.code}") // Код 65 (ASCII) println("Символ: $emoji") // Δ println("Сумма? ${letterA + emoji}") // Ошибка! Складывать Char нельзя. }
Символы — это не числа. Хотя у каждого есть числовой код, Kotlin запрещает арифметические операции с Char, чтобы избежать логических ошибок. Не пытайтесь получить 'C' сложением 'A' и 2. Для этого есть специальные функции.
5. Строки: шаблоны и многогранность
Мы уже вовсю используем шаблоны строк (знак $). Это то, чего мне так не хватало в Java долгие годы.
Еще небольшой пример:
fun main() { val name = "Сергей" val age = 25 // Простая вставка переменной println("Привет, $name!") // Вставка выражения (нужны фигурные скобки) println("Через год вам будет ${age + 1} лет.") println("Ваше имя из ${name.length} символов.") }
Это не просто синтаксический сахар. Это читаемость вашего кода. Забудьте про конкатенацию через +. Шаблоны — это стандарт.
Многострочные строки (Raw Strings):Если вам нужно вывести JSON, SQL‑запрос или ASCII‑арт, используйте тройные кавычки.
fun main() { val logo = """ | ╔════════════╗ | ║ KOTLIN ║ | ╚════════════╝ """.trimMargin() // Убирает отступы и символ '|' println(logo) }
Правда ли ведь элегантно? 😊
6. Арифметика и преобразование типов (Type Coercion)
С числами всё привычно: +, -, *, /, % (остаток от деления).
Но есть важное отличие от Java и C++, которое ловит многих новичков.
Напишем еще пример:
fun main() { val a: Int = 10 val b: Int = 3 val result = a / b println("Результат: $result") // Будет 3! (целочисленное деление) val correctResult = a / b.toDouble() println("Правильный результат: $correctResult") // 3.333... }
Kotlin строже относится к неявным преобразованиям. Int, делённый на Int, всегда даст Int. Если вы хотите дробный результат, вы должны явно сказать об этом, преобразовав один из оппонентов к Double или Float.
Это называется type coercion (приведение типов), и в Kotlin оно менее автоматическое, чем в Java. Компилятор не даст вам просто так сложить Double и Int, не предупредив. Но если вы сложите их, будет работать неявное преобразование меньшего типа в больший (Int станет Double).
val sum = 10 + 3.5 // sum будет Double (10 преобразовалось в 10.0)
7. Остаток от деления (Modulo) — важный нюанс
Оператор % (rem — remainder) часто используют для проверки на чётность (x % 2 == 0). Но с отрицательными числами в Kotlin (и многих других языках) результат может быть неожиданным.
Давайте еще набросаем пару строк кода:
fun main() { println("10 % 3 = ${10 % 3}") // 1 println("10 % -3 = ${10 % -3}") // 1 println("-10 % 3 = ${-10 % 3}") // -1 (не 2!) }
Правило простое: знак результата всегда совпадает со знаком делимого (левого операнда). Это важно учитывать, например, в графике или математических расчётах, где нужен всегда положительный остаток.
8. Инкремент и декремент
В Kotlin есть операторы ++ и --. Они работают так же, как и везде: префиксная и постфиксная формы. Но, честно говоря, в своей практике мы стараемся избегать постфиксной формы в сложных выражениях.
Давайте посмотрим, как работают эти операторы на небольшом примере:
fun main() { var counter = 0 println("Старт: $counter") println("Постфикс: ${counter++}") // Сначала выведет 0, потом увеличит до 1 println("Сейчас: $counter") // 1 println("Префикс: ${++counter}") // Сначала увеличит до 2, потом выведет }
Best Practice: Если вам нужно просто увеличить счётчик, пишите counter++ на отдельной строке. Если это часть сложного выражения, сто раз подумайте, не сделает ли это код нечитаемым. Обыно лучше вынести в отдельную строку.
9. Ввод данных: знакомьтесь, readln()
До сих пор мы только выводили данные. Пришло время научиться их вводить. В Kotlin для этого есть простая функция readln() (или readLine(), но readln() — современный вариант, который бросает исключение при ошибке, что удобнее).
Небольшой пример:
import java.time.Year // Импортируем для примера fun main() { println("Как вас зовут?") val userName = readln() // Читаем строку println("Привет, $userName! Сколько вам лет?") val ageInput = readln() val age = ageInput.toInt() // Преобразуем строку в число val birthYear = Year.now().value - age println("$userName, вы, вероятно, родились в $birthYear году.") }
Здесь мы коснулись важной темы: преобразования типов. Из консоли мы всегда получаем строку (String). Чтобы работать с числами, мы вызываем у этой строки метод .toInt(), .toDouble() и так далее. Если пользователь введёт не число, программа упадёт с ошибкой. Обработку ошибок мы рассмотрим в следующих статьях.
Реальная история: как null‑безопасность и неизменяемость спасли нам релиз
Расскажу случай из моей практики в FinTech. Мы переписывали старый Java‑монолит, отвечающий за расчёт кредитных лимитов, на Kotlin. Кодовая база была огромной, с тысячами строк и множеством static методов, которые меняли состояние глобальных объектов.
В Java была стандартная проблема: чтобы понять, может ли переменная быть null, нужно было читать тонны документации или дебажить. В итоге в прод уходил код, который иногда падал с NullPointerException (NPE) при определённых комбинациях данных клиента.
Когда мы начали перенос на Kotlin, первое, что мы сделали — начали максимально использовать val и запретили var в публичных API. Второе — это null‑безопасность. В Kotlin типы по умолчанию не могут быть null. Если переменная может быть null, вы должны явно указать это знаком ?.
var userName: String = "Иван" // Никогда не null var userMiddleName: String? = null // Может быть null
Так вот, в ходе рефакторинга одного из модулей, мы наткнулись на метод, который в Java мог вернуть null для одного из полей. При переносе на Kotlin наш новый разработчик (стажёр) случайно поставил обычный тип, без ?. Компилятор выдал ошибку! Он сказал: «Ты утверждаешь, что это поле никогда не равно null, но в коде есть путь, где ты пытаешься вернуть null».
Разработчик был вынужден пойти в бизнес‑логику и разобраться. Оказалось, что в том единственном кейсе, когда возвращался null, бизнес‑правило было устаревшим, и поле на самом деле всегда должно было быть заполнено. Мы исправили логику, убрали null, и код стал чище. Компилятор предотвратил потенциальный NPE, который гарантированно упал бы в продакшене через месяц. В Java мы бы это заметили только на нагрузочном тестировании или, что хуже, в реальной работе. Это та самая магия Kotlin, которая экономит миллионы.
Визуализация: поток данных в программе
Чтобы закрепить понимание, как данные движутся в нашей маленькой программе, давайте взглянем на простую диаграмму последовательности, изображенную на рисунке 1. Может показаться, что для этой статьи она избыточна, но с учетом того, что все разработчики должны уметь ее читать — решил, что мы должны познакомиться и с ней на каком‑то простом примере.

Заключение и путь вперёд
Мы прошли только по верхам, но, надеюсь, вы почувствовали философию Kotlin: он лаконичный, выразительный и очень прагматичный. Он не даёт вам стрелять себе в ногу, заставляя явно объявлять изменяемость (var), явно работать с nullable типами и явно преобразовывать типы.
Что мы разобрали:
Неизменяемые (
val) и изменяемые (var) переменные.Вывод типов и базовую числовую вселенную (
Int,Double,Char).Работу со строками и их шаблонами.
Арифметику и важные нюансы деления и взятия остатка.
Простое взаимодействие с пользователем через консоль.
Это основа, на которой строится всё остальное. В следующих статьях мы перейдём к логическим конструкциям (if, when), циклам и, конечно, к функциям. А ещё нас ждут корутины — тема, которая поначалу пугает, но в Kotlin сделана невероятно элегантно.
Весь код из этой статьи доступен в моём репозитории на GitHub
Если вы хотите не просто читать разрозненные статьи, а выстроить системное понимание языка, научиться применять эти паттерны в реальных проектах (как бэкенд, так и многоплатформенная разработка), приходите на наш курс «Kotlin-разработчик. Базовый уровень» в OTUS.
Не откладывайте изучение на завтра. Рынок ждёт грамотных Kotlin‑разработчиков уже сегодня!
Серия статей «Kotlin для новичков»:
Для тех, кто хочет двигаться в Kotlin дальше и без хаотичных туториалов, у OTUS есть каталог курсов по программированию.
