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

  1. Create a Complex number class with operator overloading for basic math operations
  2. Implement a simple calculator using different operators
  3. Build a permission system using bitwise operators
  4. Create a Range class with custom in operator implementation
  5. Design a measurement unit system (Length, Weight) with unit conversions

Quick Quiz

  1. What's the difference between == and === in Kotlin?
  2. When should you use the Elvis operator (?:)?
  3. What does the range operator .. include?
  4. What's the purpose of operator overloading?
Show answers
  1. == checks structural equality (content), === checks referential equality (same object)
  2. When you want to provide a default value for nullable expressions
  3. Both endpoints (it's a closed range, e.g., 1..5 includes 1, 2, 3, 4, 5)
  4. To make custom types work naturally with operators, creating more intuitive and readable code