|
| 1 | +[//]: # (title: Intermediate: Extension functions) |
| 2 | + |
| 3 | +<tldr> |
| 4 | + <p><img src="icon-1.svg" width="20" alt="First step" /> <strong>Extension functions</strong><br /> |
| 5 | + <img src="icon-2-todo.svg" width="20" alt="Second step" /> <a href="kotlin-tour-intermediate-scope-functions.md">Scope functions</a><br /> |
| 6 | + <img src="icon-3-todo.svg" width="20" alt="Third step" /> <a href="kotlin-tour-intermediate-lambdas-receiver.md">Lambda expressions with receiver</a><br /> |
| 7 | + <img src="icon-4-todo.svg" width="20" alt="Fourth step" /> <a href="kotlin-tour-intermediate-classes-interfaces.md">Classes and interfaces</a><br /> |
| 8 | + <img src="icon-5-todo.svg" width="20" alt="Fifth step" /> <a href="kotlin-tour-intermediate-objects.md">Objects</a><br /> |
| 9 | + <img src="icon-6-todo.svg" width="20" alt="Sixth step" /> <a href="kotlin-tour-intermediate-open-special-classes.md">Open and special classes</a><br /> |
| 10 | + <img src="icon-7-todo.svg" width="20" alt="Seventh step" /> <a href="kotlin-tour-intermediate-properties.md">Properties</a><br /> |
| 11 | + <img src="icon-8-todo.svg" width="20" alt="Eighth step" /> <a href="kotlin-tour-intermediate-null-safety.md">Null safety</a><br /> |
| 12 | + <img src="icon-9-todo.svg" width="20" alt="Ninth step" /> <a href="kotlin-tour-intermediate-libraries-and-apis.md">Libraries and APIs</a></p> |
| 13 | +</tldr> |
| 14 | + |
| 15 | +In this chapter, you'll explore special Kotlin functions that make your code more concise and readable. Learn how they |
| 16 | +can help you use efficient design patterns to take your projects to the next level. |
| 17 | + |
| 18 | +## Extension functions |
| 19 | + |
| 20 | +In software development, you often need to modify the behavior of a program without altering the original source code. |
| 21 | +For example, in your project, you might want to add extra functionality to a class from a third-party library. |
| 22 | + |
| 23 | +Extension functions allow you to extend a class with additional functionality. You call extension functions the same way |
| 24 | +you call member functions of a class. |
| 25 | + |
| 26 | +Before introducing the syntax for extension functions, you need to understand the terms **receiver type** and |
| 27 | +**receiver object**. |
| 28 | + |
| 29 | +The receiver object is what the function is called on. In other words, the receiver is where or with whom the information is shared. |
| 30 | + |
| 31 | +{width="500"} |
| 32 | + |
| 33 | +In this example, the `main()` function calls the [`.first()`](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/first.html) function. |
| 34 | +The `.first()` function is called **on** the `readOnlyShapes` variable, so the `readOnlyShapes` variable is the receiver. |
| 35 | + |
| 36 | +The receiver object has a **type** so that the compiler understands when the function can be used. |
| 37 | + |
| 38 | +This example uses the `.first()` function from the standard library to return the first element in a list. To create |
| 39 | +your own extension function, write the name of the class that you want to extend followed by a `.` and the name of |
| 40 | +your function. Continue with the rest of the function declaration, including its arguments and return type. |
| 41 | + |
| 42 | +For example: |
| 43 | + |
| 44 | +```kotlin |
| 45 | +fun String.bold(): String = "<b>$this</b>" |
| 46 | + |
| 47 | +fun main() { |
| 48 | + // "hello" is the receiver object |
| 49 | + println("hello".bold()) |
| 50 | + // <b>hello</b> |
| 51 | +} |
| 52 | +``` |
| 53 | +{kotlin-runnable="true" kotlin-min-compiler-version="1.3" id="kotlin-tour-extension-function"} |
| 54 | + |
| 55 | +In this example: |
| 56 | + |
| 57 | +* `String` is the extended class, also known as the receiver type. |
| 58 | +* `bold` is the name of the extension function. |
| 59 | +* The `.bold()` extension function's return type is `String`. |
| 60 | +* `"hello"`, an instance of `String`, is the receiver object. |
| 61 | +* The receiver object is accessed inside the body by the [keyword](keyword-reference.md): `this`. |
| 62 | +* A string template (`$`) is used to access the value of `this`. |
| 63 | +* The `.bold()` extension function takes a string and returns it in a `<b>` HTML element for bold text. |
| 64 | + |
| 65 | +## Extension-oriented design |
| 66 | + |
| 67 | +You can define extension functions anywhere, which enables you to create extension-oriented designs. These designs separate |
| 68 | +core functionality from useful but non-essential features, making your code easier to read and maintain. |
| 69 | + |
| 70 | +A good example is the [`HttpClient`](https://api.ktor.io/ktor-client/ktor-client-core/io.ktor.client/-http-client/index.html) class from the Ktor library, which helps perform network requests. The core of |
| 71 | +its functionality is a single function `request()`, which takes all the information needed for an HTTP request: |
| 72 | + |
| 73 | +```kotlin |
| 74 | +class HttpClient { |
| 75 | + fun request(method: String, url: String, headers: Map<String, String>): HttpResponse { |
| 76 | + // Network code |
| 77 | + } |
| 78 | +} |
| 79 | +``` |
| 80 | +{validate="false"} |
| 81 | + |
| 82 | +In practice, the most popular HTTP requests are GET or POST requests. It makes sense for the library to provide shorter |
| 83 | +names for these common use cases. However, these don't require writing new network code, only a specific request call. |
| 84 | +In other words, they are perfect candidates to be defined as separate `.get()` and `.post()` extension functions: |
| 85 | + |
| 86 | +```kotlin |
| 87 | +fun HttpClient.get(url: String): HttpResponse = request("GET", url, emptyMap()) |
| 88 | +fun HttpClient.post(url: String): HttpResponse = request("POST", url, emptyMap()) |
| 89 | +``` |
| 90 | +{validate="false"} |
| 91 | + |
| 92 | +These `.get()` and `.post()` functions call the `request()` function with the correct HTTP method, so you don't have to. |
| 93 | +They streamline your code and make it easier to understand: |
| 94 | + |
| 95 | +```kotlin |
| 96 | +class HttpClient { |
| 97 | + fun request(method: String, url: String, headers: Map<String, String>): HttpResponse { |
| 98 | + println("Requesting $method to $url with headers: $headers") |
| 99 | + return HttpResponse("Response from $url") |
| 100 | + } |
| 101 | +} |
| 102 | + |
| 103 | +fun HttpClient.get(url: String): HttpResponse = request("GET", url, emptyMap()) |
| 104 | + |
| 105 | +fun main() { |
| 106 | + val client = HttpClient() |
| 107 | + |
| 108 | + // Making a GET request using request() directly |
| 109 | + val getResponseWithMember = client.request("GET", "https://example.com", emptyMap()) |
| 110 | + |
| 111 | + // Making a GET request using the get() extension function |
| 112 | + val getResponseWithExtension = client.get("https://example.com") |
| 113 | +} |
| 114 | +``` |
| 115 | +{validate="false"} |
| 116 | + |
| 117 | +This extension-oriented approach is widely used in Kotlin's [standard library](https://kotlinlang.org/api/latest/jvm/stdlib/) |
| 118 | +and other libraries. For example, the `String` class has many [extension functions](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/#extension-functions) |
| 119 | +to help you work with strings. |
| 120 | + |
| 121 | +For more information about extension functions, see [Extensions](extensions.md). |
| 122 | + |
| 123 | +## Practice |
| 124 | + |
| 125 | +### Exercise 1 {initial-collapse-state="collapsed" collapsible="true" id="extension-functions-exercise-1"} |
| 126 | + |
| 127 | +Write an extension function called `isPositive` that takes an integer and checks whether it is positive. |
| 128 | + |
| 129 | +|---|---| |
| 130 | +```kotlin |
| 131 | +fun Int.// Write your code here |
| 132 | + |
| 133 | +fun main() { |
| 134 | + println(1.isPositive()) |
| 135 | + // true |
| 136 | +} |
| 137 | +``` |
| 138 | +{validate="false" kotlin-runnable="true" kotlin-min-compiler-version="1.3" id="kotlin-tour-extension-functions-exercise-1"} |
| 139 | + |
| 140 | +|---|---| |
| 141 | +```kotlin |
| 142 | +fun Int.isPositive(): Boolean = this > 0 |
| 143 | + |
| 144 | +fun main() { |
| 145 | + println(1.isPositive()) |
| 146 | + // true |
| 147 | +} |
| 148 | +``` |
| 149 | +{initial-collapse-state="collapsed" collapsible="true" collapsed-title="Example solution" id="kotlin-tour-extension-functions-solution-1"} |
| 150 | + |
| 151 | +### Exercise 2 {initial-collapse-state="collapsed" collapsible="true" id="extension-functions-exercise-2"} |
| 152 | + |
| 153 | +Write an extension function called `toLowercaseString` that takes a string and returns a lowercase version. |
| 154 | + |
| 155 | +<deflist collapsible="true"> |
| 156 | + <def title="Hint"> |
| 157 | + Use the <a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/lowercase.html"> <code>.lowercase()</code> |
| 158 | + </a> function for the <code>String</code> type. |
| 159 | + </def> |
| 160 | +</deflist> |
| 161 | + |
| 162 | +|---|---| |
| 163 | +```kotlin |
| 164 | +fun // Write your code here |
| 165 | + |
| 166 | +fun main() { |
| 167 | + println("Hello World!".toLowercaseString()) |
| 168 | + // hello world! |
| 169 | +} |
| 170 | +``` |
| 171 | +{validate="false" kotlin-runnable="true" kotlin-min-compiler-version="1.3" id="kotlin-tour-extension-functions-exercise-2"} |
| 172 | + |
| 173 | +|---|---| |
| 174 | +```kotlin |
| 175 | +fun String.toLowercaseString(): String = this.lowercase() |
| 176 | + |
| 177 | +fun main() { |
| 178 | + println("Hello World!".toLowercaseString()) |
| 179 | + // hello world! |
| 180 | +} |
| 181 | +``` |
| 182 | +{initial-collapse-state="collapsed" collapsible="true" collapsed-title="Example solution" id="kotlin-tour-extension-functions-solution-2"} |
| 183 | + |
| 184 | +## Next step |
| 185 | + |
| 186 | +[Intermediate: Scope functions](kotlin-tour-intermediate-scope-functions.md) |
0 commit comments