Kotlin - Operators (Overview)
Kotlin Operators Overview
Operators are symbols that perform operations on variables and values. Kotlin provides a comprehensive set of operators including arithmetic, comparison, logical, and many others with support for operator overloading.
Arithmetic Operators
Basic Arithmetic
val a = 10
val b = 3
// Basic operations
val sum = a + b // 13 (addition)
val difference = a - b // 7 (subtraction)
val product = a * b // 30 (multiplication)
val quotient = a / b // 3 (integer division)
val remainder = a % b // 1 (modulus/remainder)
// Floating point division
val preciseQuotient = a.toDouble() / b // 3.3333333333333335
println("$a + $b = $sum")
println("$a - $b = $difference")
println("$a * $b = $product")
println("$a / $b = $quotient")
println("$a % $b = $remainder")
Unary Operators
var x = 5
// Unary plus and minus
val positive = +x // 5 (unary plus)
val negative = -x // -5 (unary minus)
// Increment and decrement
println("x = $x") // 5
// Pre-increment/decrement
println("++x = ${++x}") // 6 (increment first, then use)
println("--x = ${--x}") // 5 (decrement first, then use)
// Post-increment/decrement
println("x++ = ${x++}") // 5 (use first, then increment)
println("x = $x") // 6
println("x-- = ${x--}") // 6 (use first, then decrement)
println("x = $x") // 5
Assignment Operators
Basic and Compound Assignment
var number = 10
// Basic assignment
number = 20
println("number = $number") // 20
// Compound assignment operators
number += 5 // equivalent to: number = number + 5
println("After += 5: $number") // 25
number -= 3 // equivalent to: number = number - 3
println("After -= 3: $number") // 22
number *= 2 // equivalent to: number = number * 2
println("After *= 2: $number") // 44
number /= 4 // equivalent to: number = number / 4
println("After /= 4: $number") // 11
number %= 3 // equivalent to: number = number % 3
println("After %= 3: $number") // 2
Comparison Operators
Relational Operators
val a = 10
val b = 20
val c = 10
// Comparison operators
println("a > b: ${a > b}") // false (greater than)
println("a < b: ${a < b}") // true (less than)
println("a >= c: ${a >= c}") // true (greater than or equal)
println("a <= b: ${a <= b}") // true (less than or equal)
println("a == c: ${a == c}") // true (equality)
println("a != b: ${a != b}") // true (inequality)
// String comparison
val str1 = "apple"
val str2 = "banana"
val str3 = "apple"
println("str1 == str3: ${str1 == str3}") // true
println("str1 < str2: ${str1 < str2}") // true (lexicographical)
println("str1.compareTo(str2): ${str1.compareTo(str2)}") // -1
Equality vs Identity
// Structural equality (==) vs referential equality (===)
val list1 = listOf(1, 2, 3)
val list2 = listOf(1, 2, 3)
val list3 = list1
println("list1 == list2: ${list1 == list2}") // true (same content)
println("list1 === list2: ${list1 === list2}") // false (different objects)
println("list1 === list3: ${list1 === list3}") // true (same reference)
// With strings
val s1 = "Hello"
val s2 = "Hello"
val s3 = String("Hello".toCharArray())
println("s1 == s2: ${s1 == s2}") // true
println("s1 === s2: ${s1 === s2}") // true (string interning)
println("s1 == s3: ${s1 == s3}") // true
println("s1 === s3: ${s1 === s3}") // false
Logical Operators
Boolean Logic
val isTrue = true
val isFalse = false
// Logical AND
println("true && false: ${isTrue && isFalse}") // false
println("true && true: ${isTrue && isTrue}") // true
// Logical OR
println("true || false: ${isTrue || isFalse}") // true
println("false || false: ${isFalse || isFalse}") // false
// Logical NOT
println("!true: ${!isTrue}") // false
println("!false: ${!isFalse}") // true
// Complex expressions
val age = 25
val hasLicense = true
val hasInsurance = false
val canDrive = age >= 18 && hasLicense && hasInsurance
println("Can drive: $canDrive") // false
val canRent = age >= 21 || (age >= 18 && hasLicense)
println("Can rent: $canRent") // true
Short-Circuit Evaluation
fun expensiveOperation(): Boolean {
println("Expensive operation called")
return true
}
val condition1 = false
val condition2 = true
// Short-circuit AND: second operand not evaluated if first is false
println("Short-circuit AND:")
val result1 = condition1 && expensiveOperation() // expensiveOperation not called
println("Result: $result1")
// Short-circuit OR: second operand not evaluated if first is true
println("Short-circuit OR:")
val result2 = condition2 || expensiveOperation() // expensiveOperation not called
println("Result: $result2")
Range Operators
Range Creation
// Closed range (includes both endpoints)
val range1 = 1..10 // 1, 2, 3, ..., 10
val range2 = 'a'..'z' // a, b, c, ..., z
// Half-open range (excludes end)
val range3 = 1 until 10 // 1, 2, 3, ..., 9
// Downward range
val range4 = 10 downTo 1 // 10, 9, 8, ..., 1
// Step range
val range5 = 1..10 step 2 // 1, 3, 5, 7, 9
// Character ranges
val alphabet = 'A'..'Z'
val vowels = "aeiou"
println("Numbers 1-10:")
for (i in range1) {
print("$i ")
}
println()
println("Every second number from 1-10:")
for (i in range5) {
print("$i ")
}
println()
Range Operations
val numbers = 1..100
val ages = 18..65
// Membership testing
println("50 in numbers: ${50 in numbers}") // true
println("25 in ages: ${25 in ages}") // true
println("15 in ages: ${15 in ages}") // false
// Range properties
println("numbers.first: ${numbers.first}") // 1
println("numbers.last: ${numbers.last}") // 100
println("ages.isEmpty(): ${ages.isEmpty()}") // false
// Using ranges in when
fun categorizeAge(age: Int): String {
return when (age) {
in 0..12 -> "Child"
in 13..19 -> "Teenager"
in 20..64 -> "Adult"
in 65..120 -> "Senior"
else -> "Invalid age"
}
}
println(categorizeAge(25)) // Adult
println(categorizeAge(70)) // Senior
Bitwise Operators
Bitwise Operations
val a = 12 // Binary: 1100
val b = 10 // Binary: 1010
// Bitwise operations
val and = a and b // 8 (Binary: 1000)
val or = a or b // 14 (Binary: 1110)
val xor = a xor b // 6 (Binary: 0110)
val inv = a.inv() // -13 (Two's complement)
// Shift operations
val leftShift = a shl 2 // 48 (Binary: 110000)
val rightShift = a shr 2 // 3 (Binary: 0011)
val unsignedRightShift = a ushr 2 // 3
println("$a and $b = $and")
println("$a or $b = $or")
println("$a xor $b = $xor")
println("$a.inv() = $inv")
println("$a shl 2 = $leftShift")
println("$a shr 2 = $rightShift")
// Practical example: flags
const val READ = 1 // Binary: 001
const val WRITE = 2 // Binary: 010
const val EXECUTE = 4 // Binary: 100
var permissions = READ or WRITE // Binary: 011
fun hasPermission(permissions: Int, flag: Int): Boolean {
return (permissions and flag) == flag
}
println("Has READ: ${hasPermission(permissions, READ)}") // true
println("Has WRITE: ${hasPermission(permissions, WRITE)}") // true
println("Has EXECUTE: ${hasPermission(permissions, EXECUTE))}") // false
in and !in Operators
Membership Testing
// With ranges
val validAges = 18..65
val age = 25
println("$age in validAges: ${age in validAges}") // true
// With collections
val fruits = listOf("apple", "banana", "cherry")
println("'apple' in fruits: ${'apple' in fruits}") // true
println("'orange' in fruits: ${'orange' in fruits}") // false
// With strings (substring checking)
val text = "Hello, Kotlin!"
println("'Kotlin' in text: ${'Kotlin' in text}") // true
println("'Java' in text: ${'Java' in text}") // false
// Not in operator
val invalidAges = 0..17
println("$age !in invalidAges: ${age !in invalidAges}") // true
// Custom contains implementation
class NumberRange(private val start: Int, private val end: Int) {
operator fun contains(value: Int): Boolean {
return value in start..end
}
}
val customRange = NumberRange(10, 20)
println("15 in customRange: ${15 in customRange}") // true
Elvis Operator
Null Safety with ?:
// Elvis operator for null handling
fun processName(name: String?): String {
return name ?: "Unknown" // If name is null, return "Unknown"
}
val name1: String? = "Alice"
val name2: String? = null
println(processName(name1)) // Alice
println(processName(name2)) // Unknown
// Chaining with other operations
fun getUserDisplay(user: String?, email: String?): String {
val displayName = user?.takeIf { it.isNotBlank() } ?: "Anonymous"
val contact = email?.takeIf { it.contains("@") } ?: "No email"
return "$displayName ($contact)"
}
println(getUserDisplay("Bob", "[email protected]")) // Bob ([email protected])
println(getUserDisplay("", null)) // Anonymous (No email)
// With method calls
class User(val name: String?)
val users = listOf(User("Alice"), User(null), User("Bob"))
users.forEach { user ->
val displayName = user.name?.uppercase() ?: "UNNAMED"
println("User: $displayName")
}
Safe Call and Not-Null Assertion
Null-Safe Operations
val text: String? = "Hello, Kotlin!"
val nullText: String? = null
// Safe call operator (?.)
println("text?.length: ${text?.length}") // 14
println("nullText?.length: ${nullText?.length}") // null
// Safe call chaining
data class Person(val name: String?, val address: Address?)
data class Address(val street: String?, val city: String?)
val person: Person? = Person("Alice", Address("Main St", "Springfield"))
val city = person?.address?.city?.uppercase()
println("City: $city") // SPRINGFIELD
// Not-null assertion operator (!!)
// Use carefully - throws exception if null
val definitelyNotNull = text!!.length
println("Length: $definitelyNotNull") // 14
// This would throw KotlinNullPointerException:
// val crash = nullText!!.length
Operator Overloading
Custom Operators
data class Point(val x: Int, val y: Int) {
// Overload + operator
operator fun plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}
// Overload - operator
operator fun minus(other: Point): Point {
return Point(x - other.x, y - other.y)
}
// Overload * operator (scalar multiplication)
operator fun times(scalar: Int): Point {
return Point(x * scalar, y * scalar)
}
// Overload unary minus
operator fun unaryMinus(): Point {
return Point(-x, -y)
}
// Overload comparison
operator fun compareTo(other: Point): Int {
val thisDistance = x * x + y * y
val otherDistance = other.x * other.x + other.y * other.y
return thisDistance.compareTo(otherDistance)
}
}
// Usage
val p1 = Point(3, 4)
val p2 = Point(1, 2)
val sum = p1 + p2 // Point(4, 6)
val difference = p1 - p2 // Point(2, 2)
val scaled = p1 * 2 // Point(6, 8)
val negated = -p1 // Point(-3, -4)
println("p1 + p2 = $sum")
println("p1 - p2 = $difference")
println("p1 * 2 = $scaled")
println("-p1 = $negated")
println("p1 > p2: ${p1 > p2}") // true (distance comparison)
Collection-like Operators
class Matrix(private val data: Array) {
// Overload [] operator for access
operator fun get(row: Int, col: Int): Int {
return data[row][col]
}
// Overload [] operator for assignment
operator fun set(row: Int, col: Int, value: Int) {
data[row][col] = value
}
// Overload 'in' operator
operator fun contains(value: Int): Boolean {
return data.any { row -> row.contains(value) }
}
// Overload invoke operator (function call)
operator fun invoke(transform: (Int) -> Int): Matrix {
val newData = Array(data.size) { row ->
IntArray(data[row].size) { col ->
transform(data[row][col])
}
}
return Matrix(newData)
}
}
// Usage
val matrix = Matrix(arrayOf(
intArrayOf(1, 2, 3),
intArrayOf(4, 5, 6),
intArrayOf(7, 8, 9)
))
println("matrix[1, 1] = ${matrix[1, 1]}") // 5
matrix[0, 0] = 10
println("After modification: ${matrix[0, 0]}") // 10
println("Contains 5: ${5 in matrix}") // true
println("Contains 15: ${15 in matrix}") // false
// Using invoke operator
val doubled = matrix { it * 2 }
println("Doubled matrix[0, 1] = ${doubled[0, 1]}") // 4
Operator Precedence
Understanding Order of Operations
// Arithmetic precedence: *, /, % before +, -
val result1 = 2 + 3 * 4 // 14, not 20
val result2 = (2 + 3) * 4 // 20
// Comparison vs logical
val a = 5
val b = 10
val c = 15
// This is evaluated as: (a < b) && (b < c)
val inOrder = a < b && b < c // true
// Elvis operator has low precedence
val value: Int? = null
val result3 = value?.times(2) ?: 0 // 0
// Function calls have high precedence
fun double(x: Int) = x * 2
val result4 = 1 + double(3) // 7, not 8
println("2 + 3 * 4 = $result1")
println("(2 + 3) * 4 = $result2")
println("a < b && b < c = $inOrder")
println("1 + double(3) = $result4")
Real-World Examples
Mathematical Vector Operations
data class Vector3D(val x: Double, val y: Double, val z: Double) {
operator fun plus(other: Vector3D) =
Vector3D(x + other.x, y + other.y, z + other.z)
operator fun minus(other: Vector3D) =
Vector3D(x - other.x, y - other.y, z - other.z)
operator fun times(scalar: Double) =
Vector3D(x * scalar, y * scalar, z * scalar)
operator fun div(scalar: Double) =
Vector3D(x / scalar, y / scalar, z / scalar)
// Dot product
infix fun dot(other: Vector3D): Double =
x * other.x + y * other.y + z * other.z
// Magnitude
fun magnitude(): Double =
kotlin.math.sqrt(x * x + y * y + z * z)
// Normalized vector
fun normalized(): Vector3D = this / magnitude()
}
// Physics simulation
val velocity = Vector3D(10.0, 5.0, 0.0)
val acceleration = Vector3D(0.0, -9.8, 0.0) // Gravity
val timeStep = 0.1
val newVelocity = velocity + acceleration * timeStep
val displacement = velocity * timeStep + acceleration * timeStep * timeStep * 0.5
println("New velocity: $newVelocity")
println("Displacement: $displacement")
println("Speed: ${newVelocity.magnitude()}")
Money and Currency Operations
data class Money(val amount: Long, val currency: String) { // Store as cents
operator fun plus(other: Money): Money {
require(currency == other.currency) { "Currency mismatch" }
return Money(amount + other.amount, currency)
}
operator fun minus(other: Money): Money {
require(currency == other.currency) { "Currency mismatch" }
return Money(amount - other.amount, currency)
}
operator fun times(multiplier: Int): Money {
return Money(amount * multiplier, currency)
}
operator fun div(divisor: Int): Money {
return Money(amount / divisor, currency)
}
operator fun compareTo(other: Money): Int {
require(currency == other.currency) { "Currency mismatch" }
return amount.compareTo(other.amount)
}
fun toDisplayString(): String {
val dollars = amount / 100
val cents = amount % 100
return "$currency $dollars.${cents.toString().padStart(2, '0')}"
}
}
// Financial calculations
val price = Money(2599, "USD") // $25.99
val tax = Money(260, "USD") // $2.60
val discount = Money(500, "USD") // $5.00
val total = price + tax - discount
val bulkPrice = price * 3
println("Price: ${price.toDisplayString()}")
println("Total with tax and discount: ${total.toDisplayString()}")
println("Bulk price (3 items): ${bulkPrice.toDisplayString()}")
println("Is bulk cheaper per item? ${bulkPrice / 3 < price}")
Best Practices
✅ Good Practices
- Use Elvis operator (?:) for null safety with defaults
- Prefer safe calls (?.) over not-null assertions (!!)
- Use ranges for readable numeric conditions
- Implement operator overloading only when it makes semantic sense
- Use parentheses to clarify complex expressions
❌ Avoid
- Overusing not-null assertion operator (!!)
- Operator overloading that doesn't match mathematical intuition
- Complex expressions without parentheses
- Bitwise operations unless doing low-level programming
Architecture Note: Kotlin's operators are implemented as function calls under the hood, enabling powerful customization through operator overloading. The null-safety operators (?., ?:, !!) are key to Kotlin's type-safe approach to handling potentially null values.
Practice Exercises
- Create a Complex number class with operator overloading for basic math operations
- Implement a simple calculator using different operators
- Build a permission system using bitwise operators
- Create a Range class with custom in operator implementation
- Design a measurement unit system (Length, Weight) with unit conversions
Quick Quiz
- What's the difference between == and === in Kotlin?
- When should you use the Elvis operator (?:)?
- What does the range operator .. include?
- What's the purpose of operator overloading?
Show answers
- == checks structural equality (content), === checks referential equality (same object)
- When you want to provide a default value for nullable expressions
- Both endpoints (it's a closed range, e.g., 1..5 includes 1, 2, 3, 4, 5)
- To make custom types work naturally with operators, creating more intuitive and readable code