← Kotlin EnglishChapter 12 of 13

Extension Functions

## Learning Objectives - Understand extension functions - Learn extension properties - Work with companion object extensions - Master generics basics - Use generic constraints ## Extension Function Basics Extension functions add functions to existing classes without modifying them: ```kotlin fun String.addExclamation(): String = this + "!" fun main() { val greeting = "Hello".addExclamation() println(greeting) // Hello! } ``` ### Why Extension Functions? ```kotlin // Without extension fun lastChar(str: String): Char = str[str.length - 1] // With extension fun String.lastChar(): Char = this[length - 1] fun main() { println(lastChar("Kotlin")) // n println("Kotlin".lastChar()) // n } ``` ## Extension Syntax ```text fun ClassName.functionName(params): ReturnType { // this refers to ClassName instance } ``` ```kotlin fun Int.isEven(): Boolean = this % 2 == 0 fun String.addNumbers(num: Int): String = this + num fun main() { println(4.isEven()) // true println(5.isEven()) // false println("Answer: ".addNumbers(42)) // Answer: 42 } ``` ## Nullable Receiver Extensions can be called on nullable types: ```kotlin fun String?.orEmpty(): String = this ?: "" fun main() { val nullStr: String? = null println(nullStr.orEmpty()) // "" val normalStr: String? = "Hello" println(normalStr.orEmpty()) // Hello } ``` ## Extension Properties Add properties to existing classes: ```kotlin val String.uppercaseFirst: String get() = if (isEmpty()) "" else this[0].uppercase() + substring(1) var MutableList.lastIndex: Int get() = size - 1 set(value) { /* Not common for immutable */ } fun main() { println("hello".uppercaseFirst) // Hello val list = mutableListOf(1, 2, 3) println(list.lastIndex) // 2 } ``` ## Scope of Extensions ### Regular Extensions ```kotlin package mypackage fun String.addExclamation() = this + "!" // Can only be used when imported ``` ### member Extensions ```kotlin class MyClass { fun String.extended() = "Extended: $this" } ``` ## Companion Object Extensions ```kotlin class MyClass { companion object { // Fields only } // Extension on companion object fun MyClass.Companion.create(): MyClass = MyClass() } // Usage fun main() { val obj = MyClass.create() } ``` ### More Common Example ```kotlin class Person private constructor(val name: String) { companion object { fun fromName(name: String): Person = Person(name) } } fun main() { val person = Person.fromName("Alice") println(person.name) // Alice } ``` ## Generics ### Generic Functions ```kotlin fun List.firstOrNull(): T? = if (isEmpty()) null else this[0] fun printItem(item: T) { println(item) } fun main() { println(listOf(1, 2, 3).firstOrNull()) // 1 println(emptyList().firstOrNull()) // null printItem("Hello") // Hello printItem(42) // 42 } ``` ### Generic Classes ```kotlin class Box(val value: T) { fun getValue(): T = value } class Pair(val first: A, val second: B) fun main() { val intBox = Box(42) val stringBox = Box("Hello") println(intBox.getValue()) // 42 println(stringBox.getValue()) // Hello val pair = Pair(1, "one") println("${pair.first}, ${pair.second}") // 1, one } ``` ### Generic Constraints ```kotlin // Upper bound - T must be Comparable fun > List.maxOrNull(): T? { if (isEmpty()) return null return sortedDescending().first() } // Multiple bounds fun List.joinToString( separator: String = ", " ): String where T : CharSequence, T : Comparable { return this.joinToString(separator) } fun main() { println(listOf(1, 5, 3, 2, 4).maxOrNull()) // 5 val strings = listOf("apple", "cherry", "banana") println(strings.maxOrNull()) // cherry } ``` ## Variance ### Invariance ```kotlin // List is invariant class Container // Container is NOT a subtype of Container ``` ### Covariance (out) Producer only - can only produce, not consume: ```kotlin class Producer { fun produce(): T = TODO() } // Producer IS a subtype of Producer val intProducer: Producer = TODO() val numProducer: Producer = intProducer // OK ``` ### Contravariance (in) Consumer only - can only consume, not produce: ```kotlin class Consumer { fun consume(item: T) {} } // Consumer IS a subtype of Consumer val numConsumer: Consumer = TODO() val intConsumer: Consumer = numConsumer // OK ``` ## Type Erasure Generic type information is erased at runtime: ```kotlin fun main() { val list1: List = listOf("a", "b") val list2: List = listOf(1, 2) // At runtime, both are just List println(list1 is List<*>) // true println(list2 is List<*>) // true // Cannot do this: // if (list is List) // Error: Cannot check specific type } ``` ### Reified Types Use `reified` to make type available at runtime (inline functions only): ```kotlin import kotlin.reflect.typeOf inline fun printType() { println(typeOf()) } fun main() { printType() // kotlin.String printType() // kotlin.Int printType>() // kotlin.collections.List } ``` ## Where Clauses ```kotlin interface Readable interface Writable class Data where T : Readable, T : Writable { fun read(): T = TODO() fun write(item: T) {} } ``` ## Summary - Extension functions add methods to existing classes - Syntax: `fun ClassName.functionName() = ...` - Extension properties add properties to existing classes - Extensions with nullable receiver (`String?`) - Generic functions use `` before function name - Generic classes: `class Box(val value: T)` - Upper bound: `>` - Covariance: `out T`, Contravariance: `in T` - Type erasure removes generics at runtime - `reified` preserves generic type in inline functions

Comments

Comments powered by Giscus

To enable comments, add your Giscus embed code here.

Learn more about Giscus →