Files
obsidian/Kotlin/Синтаксис и типы/Базовый синтаксис/Операторы и выражения.md
T

7.9 KiB
Raw Blame History

title, description, tags
title description tags
Операторы и выражения Арифметика, равенство == и ===, побитовые операции словами, приоритет операторов
kotlin
syntax

В Kotlin почти все операторы - это вызовы функций. a + b компилируется в a.plus(b), поэтому операторы можно определять для своих типов (Операторные функции).

Арифметические

Оператор Функция Пример
+ plus 2 + 3 → 5
- minus 5 - 3 → 2
* times 2 * 3 → 6
/ div 7 / 2 → 3
% rem 7 % 2 → 1

Целочисленное деление отбрасывает дробную часть:

println(7 / 2)       // 3
println(7.0 / 2)     // 3.5
println(7 / 2.0)     // 3.5
println(-7 / 2)      // -3  — округление к нулю
println(-7 % 2)      // -1  — знак остатка совпадает со знаком делимого

Инкремент и декремент

var i = 0
println(i++)   // 0, потом i = 1
println(++i)   // 2

Работают только с var. Присваивания-операторы (+=, -=, *=, /=, %=) - тоже.

Сравнение

<, >, <=, >= транслируются в compareTo:

println(2 < 3)                 // true
println("abc" < "abd")         // true — лексикографически

Работает для любого типа, реализующего Comparable.

Равенство: == и ===

Это главное место, где Kotlin расходится с Java.

Оператор Что делает
== структурное равенство, вызывает equals()
=== ссылочное равенство, сравнивает адреса
!=, !== их отрицания
val a = "Kotlin"
val b = StringBuilder("Kotlin").toString()
println(a == b)      // true  — содержимое совпадает
println(a === b)     // false — разные объекты

== безопасен для null: a == b разворачивается в a?.equals(b) ?: (b === null).

val s: String? = null
println(s == "text")   // false, без NPE
println(s == null)     // true

Логические

val r1 = true && false    // ленивый И
val r2 = true || false    // ленивый ИЛИ
val r3 = !true            // НЕ

val r4 = true and false   // НЕ ленивый — оба операнда вычисляются
val r5 = true or false    // НЕ ленивый
val r6 = true xor true    // false

В обычном коде используют && и ||: они не вычисляют правую часть, если результат уже известен.

Побитовые операции

Символов &, |, ^, <<, >> для чисел в Kotlin нет - только инфиксные функции:

val a = 0b1100
val b = 0b1010
println(a and b)    // 8  = 0b1000
println(a or b)     // 14 = 0b1110
println(a xor b)    // 6  = 0b0110
println(a.inv())    // -13 — побитовое НЕ
println(1 shl 4)    // 16 — сдвиг влево
println(16 shr 2)   // 4  — сдвиг вправо со знаком
println(-16 ushr 2) // беззнаковый сдвиг вправо

Доступны для Int и Long.

Операторы диапазонов и коллекций

println(5 in 1..10)             // true
println(5 !in 1..10)            // false
println("a" in listOf("a","b")) // true — вызывает contains()
val list = listOf(1, 2)
println(list + 3)               // [1, 2, 3] — plus для коллекций

Подробнее: Циклы и диапазоны

Приоритет

От высшего к низшему:

Уровень Операторы
постфиксные ++, --, ., ?., !!
префиксные -, +, ++, --, !
приведение as, as?
умножение *, /, %
сложение +, -
диапазон .., ..<
инфиксные функции and, shl, своя функция infix
проверка принадлежности in, !in
проверка типа is, !is
сравнение <, >, <=, >=
равенство ==, !=, ===, !==
конъюнкция &&
дизъюнкция ||
elvis ?:
присваивание =, +=, …

На практике: сомневаетесь - ставьте скобки.

Всё является выражением

if, when, try возвращают значение:

val max = if (a > b) a else b
val name = when (code) {
    200 -> "ok"
    else -> "unknown"
}
val n = try { text.toInt() } catch (e: NumberFormatException) { 0 }

Подробнее: Условные конструкции

Циклы (for, while) - не выражения, они возвращают Unit.

[!info] Отличия от Java

Java Kotlin
== для объектов - ссылки == - вызов equals()
.equals() - содержимое === - ссылки
a & b побитовое И a and b
a >>> b a ushr b
~a a.inv()
тернарник c ? a : b if (c) a else b
операторы нельзя перегружать можно, через operator fun
s.equals(t) падает, если s null s == t безопасен

Главная ловушка при переходе: в Java == на строках сравнивает ссылки и работает «случайно» благодаря интернированию. В Kotlin == всегда сравнивает содержимое - это и нужно в 99% случаев.

Сравнить: Операторы в Java

[!warning] Подводные камни and и or не ленивые. Для Boolean это рабочий код, но правая часть вычислится всегда:

fun risky(): Boolean { println("вызвана"); return true }
val a = false && risky()   // risky() не вызовется
val b = false and risky()  // "вызвана" напечатается

Деление на целочисленный ноль - исключение, на вещественный - нет:

// println(1 / 0)      // ArithmeticException
println(1.0 / 0)       // Infinity
println(0.0 / 0)       // NaN
println(Double.NaN == Double.NaN)  // false! NaN не равен себе

Приоритет инфиксных функций ниже арифметики:

println(1 shl 2 + 3)     // 32, а не 7: это 1 shl (2+3)
println((1 shl 2) + 3)   // 7

++ на упакованном значении создаёт новый объект - в горячем цикле с Int? это аллокации. Используйте не-nullable типы.

Далее: Условные конструкции