Operators
Arithmetic, comparison, logical, range and type operators.
Kotlin offers a rich variety of operators that make code more expressive and concise. Besides the basic arithmetic operators, the language provides special operators for null safety, type checking, ranges, and much more.
Arithmetic operators
The basic arithmetic operators work as expected:
val a = 10
val b = 3
println(a + b) // 13 - Addition
println(a - b) // 7 - Subtraction
println(a * b) // 30 - Multiplication
println(a / b) // 3 - Division
println(a % b) // 1 - Modulo (remainder)
Complete example:
fun main() {
val a = 10
val b = 3
println("$a + $b = ${a + b}") // 10 + 3 = 13
println("$a - $b = ${a - b}") // 10 - 3 = 7
println("$a * $b = ${a * b}") // 10 * 3 = 30
println("$a / $b = ${a / b}") // 10 / 3 = 3
println("$a % $b = ${a % b}") // 10 % 3 = 1
}
Compound assignment operators
Kotlin supports compound assignment operators for more concise operations:
var x = 10
x += 5 // x = x + 5
x -= 3 // x = x - 3
x *= 2 // x = x * 2
x /= 4 // x = x / 4
x %= 3 // x = x % 3
Complete example:
fun main() {
var x = 10
println("Initial value: $x") // 10
x += 5
println("After += 5: $x") // 15
x -= 3
println("After -= 3: $x") // 12
x *= 2
println("After *= 2: $x") // 24
x /= 4
println("After /= 4: $x") // 6
x %= 4
println("After %= 4: $x") // 2
}
Comparison operators
The comparison operators return Boolean values:
val a = 5
val b = 10
println(a == b) // false - Equality
println(a != b) // true - Inequality
println(a < b) // true - Less than
println(a > b) // false - Greater than
println(a <= b) // true - Less than or equal
println(a >= b) // false - Greater than or equal
For referential comparison (checking whether they are the same object), use ===:
val person1 = Person("Gustavo", 25)
val person2 = Person("Gustavo", 25)
val person3 = person1
println(person1 == person2) // true (same content if it is a data class)
println(person1 === person2) // false (different objects)
println(person1 === person3) // true (same object)
Complete example:
data class Person(val name: String, val age: Int)
fun main() {
val person1 = Person("Gustavo", 25)
val person2 = Person("Gustavo", 25)
val person3 = person1
// Structural comparison (content)
println(person1 == person2) // true
println(person1 != person2) // false
// Referential comparison (same instance)
println(person1 === person2) // false
println(person1 === person3) // true
println(person1 !== person2) // true
}
Logical operators
Operators for combining Boolean expressions:
val isAdult = true
val hasLicense = false
println(isAdult && hasLicense) // false - AND
println(isAdult || hasLicense) // true - OR
println(!hasLicense) // true - NOT
Complete example:
data class Person(val name: String, val age: Int, val hasDriverLicense: Boolean)
fun main() {
val person = Person("Gustavo", 25, true)
val isAdult = person.age >= 18
val hasLicense = person.hasDriverLicense
val canDrive = isAdult && hasLicense
val needsAttention = !isAdult || !hasLicense
println("Is adult: $isAdult") // true
println("Has license: $hasLicense") // true
println("Can drive: $canDrive") // true
println("Needs attention: $needsAttention") // false
}
The in and !in operators
The in operator checks whether a value is contained in a collection or range:
val numbers = listOf(1, 2, 3, 4, 5)
println(3 in numbers) // true
println(10 !in numbers) // true
val range = 1..10
println(5 in range) // true
println(15 in range) // false
It also works with strings:
val text = "Kotlin Programming"
println("Kotlin" in text) // true
println("Java" !in text) // true
Complete example:
data class Person(val name: String, val age: Int)
fun main() {
val team = listOf(
Person("Gustavo", 25),
Person("Maria", 30),
Person("Pedro", 22)
)
val ages = team.map { it.age }
// Checking in collections
println(25 in ages) // true
println(40 !in ages) // true
// Checking in ranges
val ageRange = 18..65
val allWorkingAge = team.all { it.age in ageRange }
println("Everyone of working age: $allWorkingAge") // true
// Checking in strings
val searchName = "Gustavo Freze"
println("Gustavo" in searchName) // true
println("Pedro" !in searchName) // true
}
The is and !is operators (Type Checking)
The is and !is operators check an object’s type at runtime:
val value: Any = "Kotlin"
if (value is String) {
// Smart cast: value is automatically converted to String here
println(value.length)
}
if (value !is Int) {
println("Not an integer")
}
With smart cast, there is no need for an explicit cast after checking the type:
fun processValue(value: Any) {
when (value) {
is String -> println("String of length ${value.length}")
is Int -> println("Number: ${value * 2}")
is List<*> -> println("List with ${value.size} elements")
else -> println("Unknown type")
}
}
Complete example:
open class Person(val name: String)
class Employee(name: String, val id: Int) : Person(name)
class Customer(name: String, val loyaltyPoints: Int) : Person(name)
fun processPerson(person: Person) {
when (person) {
is Employee -> {
// Smart cast to Employee
println("Employee: ${person.name}, ID: ${person.id}")
}
is Customer -> {
// Smart cast to Customer
println("Customer: ${person.name}, Points: ${person.loyaltyPoints}")
}
else -> {
println("Person: ${person.name}")
}
}
}
fun main() {
val people = listOf(
Person("Gustavo"),
Employee("Maria", 123),
Customer("Pedro", 500)
)
for (person in people) {
processPerson(person)
// Type check
if (person is Employee) {
println(" -> Is an employee")
}
if (person !is Customer) { // CORRECTED: !is without space
println(" -> Is not a customer")
}
}
}
The as and as? operators (Type Casting)
The as operator performs an explicit cast, while as? performs a safe cast (returns null if the cast fails):
val value: Any = "Kotlin"
// Unsafe cast - throws an exception if it fails
val text = value as String
// Safe cast - returns null if it fails
val number = value as? Int // null
val string = value as? String // "Kotlin"
Complete example:
open class Person(val name: String)
class Employee(name: String, val id: Int) : Person(name)
fun main() {
val person: Person = Employee("Gustavo", 123)
val anyValue: Any = person
// Safe cast
val employee = person as? Employee
if (employee != null) {
println("Employee ID: ${employee.id}")
}
// Unsafe cast (works because person really is an Employee)
val emp = person as Employee
println("ID: ${emp.id}")
// Safe cast that returns null
val customer = person as? Customer
println("Is customer? ${customer != null}") // false
// Using it with the Elvis operator
val id = (anyValue as? Employee)?.id ?: -1
println("Employee ID: $id")
}
// Helper class for the example
class Customer(name: String) : Person(name)
The range operator (..)
The .. operator creates ranges, which are very useful in loops and checks:
val range = 1..10 // Closed range: 1 to 10 (inclusive)
val exclusive = 1 until 10 // Half-open range: 1 to 9
val downTo = 10 downTo 1 // Descending range: 10 down to 1
val stepped = 1..10 step 2 // Range with a step: 1, 3, 5, 7, 9
Ranges also work with characters:
val letters = 'a'..'z'
println('k' in letters) // true
Complete example:
data class Person(val name: String, val age: Int)
fun main() {
val people = listOf(
Person("Ana", 17),
Person("Bruno", 25),
Person("Carlos", 30),
Person("Diana", 45),
Person("Eduardo", 70)
)
// Defining age ranges
val children = 0..17
val youngAdults = 18..30
val adults = 31..60
val seniors = 61..120
// Categorizing people
people.forEach { person ->
val category = when (person.age) {
in children -> "Child/Teenager"
in youngAdults -> "Young Adult"
in adults -> "Adult"
in seniors -> "Senior"
else -> "Invalid age"
}
println("${person.name} (${person.age} years old): $category")
}
// Using ranges in loops
println("\nCounting by 5s:")
for (i in 0..20 step 5) {
print("$i ")
}
println("\n\nCountdown:")
for (i in 5 downTo 1) {
print("$i ")
}
println("🚀")
}
The Elvis operator (?:)
The Elvis operator provides a default value when the expression on the left is null:
val name: String? = null
val displayName = name ?: "Anonymous" // "Anonymous"
Very useful with functions that return null:
fun findPerson(id: Int): Person? = // ... look up the person
val person = findPerson(123) ?: Person("Default", 0)
Complete example:
data class Person(val name: String, val age: Int, val email: String? = null)
class PersonService {
private val people = mutableListOf(
Person("Gustavo", 25, "gustavo@email.com"),
Person("Maria", 30),
Person("Pedro", 22, "pedro@email.com")
)
fun findByName(name: String): Person? {
return people.find { it.name == name }
}
fun getEmail(name: String): String {
val person = findByName(name)
return person?.email ?: "noemail@default.com"
}
}
fun main() {
val service = PersonService()
// Using Elvis with functions that return null
val person1 = service.findByName("Gustavo") ?: Person("Not found", 0)
println(person1)
val person2 = service.findByName("Carlos") ?: Person("Not found", 0)
println(person2)
// Using Elvis for default values
println("Gustavo's email: ${service.getEmail("Gustavo")}")
println("Maria's email: ${service.getEmail("Maria")}")
println("Carlos's email: ${service.getEmail("Carlos")}")
// Chaining Elvis operators
val config: String? = null
val backup: String? = null
val finalValue = config ?: backup ?: "default value"
println("Final configuration: $finalValue")
}
The spread operator (*)
The spread operator (*) expands an array into individual arguments:
fun sum(vararg numbers: Int): Int {
return numbers.sum()
}
val array = intArrayOf(1, 2, 3)
val result = sum(*array) // Expands the array
Complete example:
data class Person(val name: String, val skills: List<String>)
fun createTeam(leader: String, vararg members: String): List<String> {
return listOf(leader) + members
}
fun announceSkills(person: String, vararg skills: String) {
println("$person has the following skills:")
skills.forEach { println(" - $it") }
}
fun main() {
// Using spread with arrays
val newMembers = arrayOf("Ana", "Bruno", "Carlos")
val team = createTeam("Gustavo", *newMembers)
println("Team: $team")
// Using spread with typed arrays
val numbers = intArrayOf(10, 20, 30, 40, 50)
val moreNumbers = intArrayOf(60, 70)
// Combining arrays with spread
val allNumbers = intArrayOf(1, 2, *numbers, 99, *moreNumbers, 100)
println("All numbers: ${allNumbers.toList()}")
// Using spread with converted lists
val person = Person(
"Gustavo",
listOf("Kotlin", "Java", "Spring", "SQL")
)
announceSkills(person.name, *person.skills.toTypedArray())
}
Operator Precedence
Precedence determines the order in which operators are evaluated:
- Postfix:
++,--,.,?.,? - Prefix:
-,+,++,--,! - Type:
:,as,as? - Multiplicative:
*,/,% - Additive:
+,- - Range:
.. - Infix functions
- Elvis:
?: - Checks:
in,!in,is,!is - Comparison:
<,>,<=,>= - Equality:
==,!=,===,!== - Conjunction:
&& - Disjunction:
|| - Assignment:
=,+=,-=,*=,/=,%=
Complete example:
fun main() {
// Precedence in action
val result1 = 2 + 3 * 4 // 14, not 20 (multiplication first)
println("2 + 3 * 4 = $result1")
val result2 = (2 + 3) * 4 // 20 (parentheses force the order)
println("(2 + 3) * 4 = $result2")
// Precedence with logical operators
val a = true
val b = false
val c = true
val result3 = a || b && c // true (AND has precedence over OR)
val result4 = (a || b) && c // true (parentheses change the order)
println("true || false && true = $result3")
println("(true || false) && true = $result4")
// Precedence with Elvis and comparison
val number: Int? = null
val result5 = number ?: 10 > 5 // true (?: has lower precedence)
val result6 = (number ?: 10) > 5 // true (clearer with parentheses)
println("null ?: 10 > 5 = $result5")
println("(null ?: 10) > 5 = $result6")
}