|
| 1 | +[//]: # (title: Intermediate: Objects) |
| 2 | + |
| 3 | +<tldr> |
| 4 | + <p><img src="icon-1-done.svg" width="20" alt="First step" /> <a href="kotlin-tour-intermediate-extension-functions.md">Extension functions</a><br /> |
| 5 | + <img src="icon-2-done.svg" width="20" alt="Second step" /> <a href="kotlin-tour-intermediate-scope-functions.md">Scope functions</a><br /> |
| 6 | + <img src="icon-3-done.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-done.svg" width="20" alt="Fourth step" /> <a href="kotlin-tour-intermediate-classes-interfaces.md">Classes and interfaces</a><br /> |
| 8 | + <img src="icon-5.svg" width="20" alt="Fourth step" /> <strong>Objects</strong><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 expand your understanding of classes by exploring object declarations. This knowledge will help |
| 16 | +you efficiently manage behavior across your projects. |
| 17 | + |
| 18 | +## Object declarations |
| 19 | + |
| 20 | +In Kotlin, you can use **object declarations** to declare a class with a single instance. In a sense, you declare the |
| 21 | +class and create the single instance _at the same time_. Object declarations are useful when you want to create a class to |
| 22 | +use as a single reference point for your program or to coordinate behavior across a system. |
| 23 | + |
| 24 | +> A class that has only one instance that is easily accessible is called a **singleton**. |
| 25 | +> |
| 26 | +{style="tip"} |
| 27 | + |
| 28 | +Objects in Kotlin are **lazy**, meaning they are created only when accessed. Kotlin also ensures that all |
| 29 | +objects are created in a thread-safe manner so that you don't have to check this manually. |
| 30 | + |
| 31 | +To create an object declaration, use the `object` keyword: |
| 32 | + |
| 33 | +```kotlin |
| 34 | +object DoAuth {} |
| 35 | +``` |
| 36 | + |
| 37 | +Following the name of your `object`, add any properties or member functions within the object body defined by curly braces `{}`. |
| 38 | + |
| 39 | +> Objects can't have constructors, so they don't have headers like classes. |
| 40 | +> |
| 41 | +{style="note"} |
| 42 | + |
| 43 | +For example, let's say that you wanted to create an object called `DoAuth` that is responsible for authentication: |
| 44 | + |
| 45 | +```kotlin |
| 46 | +object DoAuth { |
| 47 | + fun takeParams(username: String, password: String) { |
| 48 | + println("input Auth parameters = $username:$password") |
| 49 | + } |
| 50 | +} |
| 51 | + |
| 52 | +fun main(){ |
| 53 | + // The object is created when the takeParams() function is called |
| 54 | + DoAuth.takeParams("coding_ninja", "N1njaC0ding!") |
| 55 | + // input Auth parameters = coding_ninja:N1njaC0ding! |
| 56 | +} |
| 57 | +``` |
| 58 | +{kotlin-runnable="true" id="kotlin-tour-object-declarations"} |
| 59 | + |
| 60 | +The object has a member function called `takeParams` that accepts `username` and `password` variables as parameters |
| 61 | +and returns a string to the console. The `DoAuth` object is only created when the function is called for the first time. |
| 62 | + |
| 63 | +> Objects can inherit from classes and interfaces. For example: |
| 64 | +> |
| 65 | +> ```kotlin |
| 66 | +> interface Auth { |
| 67 | +> fun takeParams(username: String, password: String) |
| 68 | +> } |
| 69 | +> |
| 70 | +> object DoAuth : Auth { |
| 71 | +> override fun takeParams(username: String, password: String) { |
| 72 | +> println("input Auth parameters = $username:$password") |
| 73 | +> } |
| 74 | +> } |
| 75 | +> ``` |
| 76 | +> |
| 77 | +{style="note"} |
| 78 | +
|
| 79 | +#### Data objects |
| 80 | +
|
| 81 | +To make it easier to print the contents of an object declaration, Kotlin has **data** objects. Similar to data classes, |
| 82 | +which you learned about in the beginner tour, data objects automatically come with additional member functions: |
| 83 | +`toString()` and `equals()`. |
| 84 | +
|
| 85 | +> Unlike data classes, data objects do not come automatically with the `copy()` member function because they only have |
| 86 | +> a single instance that can't be copied. |
| 87 | +> |
| 88 | +{type ="note"} |
| 89 | +
|
| 90 | +To create a data object, use the same syntax as for object declarations but prefix it with the `data` keyword: |
| 91 | +
|
| 92 | +```kotlin |
| 93 | +data object AppConfig {} |
| 94 | +``` |
| 95 | +
|
| 96 | +For example: |
| 97 | +
|
| 98 | +```kotlin |
| 99 | +data object AppConfig { |
| 100 | + var appName: String = "My Application" |
| 101 | + var version: String = "1.0.0" |
| 102 | +} |
| 103 | +
|
| 104 | +fun main() { |
| 105 | + println(AppConfig) |
| 106 | + // AppConfig |
| 107 | + |
| 108 | + println(AppConfig.appName) |
| 109 | + // My Application |
| 110 | +} |
| 111 | +``` |
| 112 | +{kotlin-runnable="true" id="kotlin-tour-data-objects"} |
| 113 | +
|
| 114 | +For more information about data objects, see [](object-declarations.md#data-objects). |
| 115 | +
|
| 116 | +#### Companion objects |
| 117 | +
|
| 118 | +In Kotlin, a class can have an object: a **companion** object. You can only have **one** companion object per class. |
| 119 | +A companion object is created only when its class is referenced for the first time. |
| 120 | +
|
| 121 | +Any properties or functions declared inside a companion object are shared across all class instances. |
| 122 | +
|
| 123 | +To create a companion object within a class, use the same syntax for an object declaration but prefix it with the `companion` |
| 124 | +keyword: |
| 125 | +
|
| 126 | +```kotlin |
| 127 | +companion object Bonger {} |
| 128 | +``` |
| 129 | +
|
| 130 | +> A companion object doesn't have to have a name. If you don't define one, the default is `Companion`. |
| 131 | +> |
| 132 | +{style="note"} |
| 133 | +
|
| 134 | +To access any properties or functions of the companion object, reference the class name. For example: |
| 135 | +
|
| 136 | +```kotlin |
| 137 | +class BigBen { |
| 138 | + companion object Bonger { |
| 139 | + fun getBongs(nTimes: Int) { |
| 140 | + repeat(nTimes) { print("BONG ") } |
| 141 | + } |
| 142 | + } |
| 143 | + } |
| 144 | +
|
| 145 | +fun main() { |
| 146 | + // Companion object is created when the class is referenced for the |
| 147 | + // first time. |
| 148 | + BigBen.getBongs(12) |
| 149 | + // BONG BONG BONG BONG BONG BONG BONG BONG BONG BONG BONG BONG |
| 150 | +} |
| 151 | +``` |
| 152 | +{kotlin-runnable="true" kotlin-min-compiler-version="1.3" id="kotlin-tour-classes-companion-object"} |
| 153 | +
|
| 154 | +This example creates a class called `BigBen` that contains a companion object called `Bonger`. The companion object |
| 155 | +has a member function called `getBongs()` that accepts an integer and prints `"BONG"` to the console the same number of times |
| 156 | +as the integer. |
| 157 | +
|
| 158 | +In the `main()` function, the `getBongs()` function is called by referring to the class name. The companion object is created |
| 159 | +at this point. The `getBongs()` function is called with parameter `12`. |
| 160 | +
|
| 161 | +For more information, see [](object-declarations.md#companion-objects). |
| 162 | +
|
| 163 | +## Object declarations practice |
| 164 | +
|
| 165 | +### Exercise 1 {initial-collapse-state="collapsed" collapsible="true" id="objects-exercise-1"} |
| 166 | +
|
| 167 | +You run a coffee shop and have a system for tracking customer orders. Consider the code below and complete the declaration |
| 168 | +of the second data object so that the following code in the `main()` function runs successfully: |
| 169 | +
|
| 170 | +|---|---| |
| 171 | +
|
| 172 | +```kotlin |
| 173 | +interface Order { |
| 174 | + val orderId: String |
| 175 | + val customerName: String |
| 176 | + val orderTotal: Double |
| 177 | +} |
| 178 | +
|
| 179 | +data object OrderOne: Order { |
| 180 | + override val orderId = "001" |
| 181 | + override val customerName = "Alice" |
| 182 | + override val orderTotal = 15.50 |
| 183 | +} |
| 184 | +
|
| 185 | +data object // Write your code here |
| 186 | +
|
| 187 | +fun main() { |
| 188 | + // Print the contents of each data object |
| 189 | + println("Order One Details: $OrderOne") |
| 190 | + println("Order Two Details: $OrderTwo") |
| 191 | +
|
| 192 | + // Check if the orders are identical |
| 193 | + println("Are the two orders identical? ${OrderOne == OrderTwo}") |
| 194 | +
|
| 195 | + if (OrderOne == OrderTwo) { |
| 196 | + println("The orders are identical.") |
| 197 | + } else { |
| 198 | + println("The orders are unique.") |
| 199 | + } |
| 200 | +
|
| 201 | + println("Do the orders have the same customer name? ${OrderOne.customerName == OrderTwo.customerName}") |
| 202 | +} |
| 203 | +``` |
| 204 | +{validate="false" kotlin-runnable="true" kotlin-min-compiler-version="1.3" id="kotlin-tour-objects-exercise-1"} |
| 205 | +
|
| 206 | +|---|---| |
| 207 | +```kotlin |
| 208 | +interface Order { |
| 209 | + val orderId: String |
| 210 | + val customerName: String |
| 211 | + val orderTotal: Double |
| 212 | +} |
| 213 | +
|
| 214 | +data object OrderOne: Order { |
| 215 | + override val orderId = "001" |
| 216 | + override val customerName = "Alice" |
| 217 | + override val orderTotal = 15.50 |
| 218 | +} |
| 219 | +
|
| 220 | +data object OrderTwo: Order { |
| 221 | + override val orderId = "002" |
| 222 | + override val customerName = "Bob" |
| 223 | + override val orderTotal = 12.75 |
| 224 | +} |
| 225 | +
|
| 226 | +fun main() { |
| 227 | + // Print the contents of each data object |
| 228 | + println("Order One Details: $OrderOne") |
| 229 | + println("Order Two Details: $OrderTwo") |
| 230 | +
|
| 231 | + // Check if the orders are identical |
| 232 | + println("Are the two orders identical? ${OrderOne == OrderTwo}") |
| 233 | +
|
| 234 | + if (OrderOne == OrderTwo) { |
| 235 | + println("The orders are identical.") |
| 236 | + } else { |
| 237 | + println("The orders are unique.") |
| 238 | + } |
| 239 | +
|
| 240 | + println("Do the orders have the same customer name? ${OrderOne.customerName == OrderTwo.customerName}") |
| 241 | +} |
| 242 | +``` |
| 243 | +{initial-collapse-state="collapsed" collapsible="true" collapsed-title="Example solution" id="kotlin-tour-objects-solution-1"} |
| 244 | +
|
| 245 | +### Exercise 2 {initial-collapse-state="collapsed" collapsible="true" id="objects-exercise-2"} |
| 246 | +
|
| 247 | +Create an object declaration that inherits from the `Vehicle` interface to create a unique vehicle type: `FlyingSkateboard`. |
| 248 | +Implement the `name` property and the `move()` function in your object so that the following code in the `main()` function runs |
| 249 | +successfully: |
| 250 | +
|
| 251 | +|---|---| |
| 252 | +
|
| 253 | +```kotlin |
| 254 | +interface Vehicle { |
| 255 | + val name: String |
| 256 | + fun move(): String |
| 257 | +} |
| 258 | +
|
| 259 | +object // Write your code here |
| 260 | +
|
| 261 | +fun main() { |
| 262 | + println("${FlyingSkateboard.name}: ${FlyingSkateboard.move()}") |
| 263 | + println("${FlyingSkateboard.name}: ${FlyingSkateboard.fly()}") |
| 264 | +} |
| 265 | +``` |
| 266 | +{validate="false" kotlin-runnable="true" kotlin-min-compiler-version="1.3" id="kotlin-tour-objects-exercise-2"} |
| 267 | +
|
| 268 | +|---|---| |
| 269 | +```kotlin |
| 270 | +interface Vehicle { |
| 271 | + val name: String |
| 272 | + fun move(): String |
| 273 | +} |
| 274 | +
|
| 275 | +object FlyingSkateboard : Vehicle { |
| 276 | + override val name = "Flying Skateboard" |
| 277 | + override fun move() = "Glides through the air with a hover engine" |
| 278 | +
|
| 279 | + fun fly(): String = "Woooooooo" |
| 280 | +} |
| 281 | +
|
| 282 | +fun main() { |
| 283 | + println("${FlyingSkateboard.name}: ${FlyingSkateboard.move()}") |
| 284 | + println("${FlyingSkateboard.name}: ${FlyingSkateboard.fly()}") |
| 285 | +} |
| 286 | +``` |
| 287 | +{initial-collapse-state="collapsed" collapsible="true" collapsed-title="Example solution" id="kotlin-tour-objects-solution-2"} |
| 288 | +
|
| 289 | +### Exercise 3 {initial-collapse-state="collapsed" collapsible="true" id="objects-exercise-3"} |
| 290 | +
|
| 291 | +You have an app where you want to record temperatures. The class itself stores the information in Celsius, but |
| 292 | +you want to provide an easy way to create an instance in Fahrenheit as well. Complete the data class so that |
| 293 | +the following code in the `main()` function runs successfully: |
| 294 | +
|
| 295 | +<deflist collapsible="true"> |
| 296 | + <def title="Hint"> |
| 297 | + Use a companion object. |
| 298 | + </def> |
| 299 | +</deflist> |
| 300 | +
|
| 301 | +|---|---| |
| 302 | +```kotlin |
| 303 | +data class Temperature(val celsius: Double) { |
| 304 | + val fahrenheit: Double = celsius * 9 / 5 + 32 |
| 305 | +
|
| 306 | + // Write your code here |
| 307 | +} |
| 308 | +
|
| 309 | +fun main() { |
| 310 | + val fahrenheit = 90.0 |
| 311 | + val temp = Temperature.fromFahrenheit(fahrenheit) |
| 312 | + println("${temp.celsius}°C is $fahrenheit °F") |
| 313 | +} |
| 314 | +``` |
| 315 | +{validate="false" kotlin-runnable="true" kotlin-min-compiler-version="1.3" id="kotlin-tour-objects-exercise-3"} |
| 316 | +
|
| 317 | +|---|---| |
| 318 | +```kotlin |
| 319 | +data class Temperature(val celsius: Double) { |
| 320 | + val fahrenheit: Double = celsius * 9 / 5 + 32 |
| 321 | +
|
| 322 | + companion object { |
| 323 | + fun fromFahrenheit(fahrenheit: Double): Temperature = Temperature((fahrenheit - 32) * 5 / 9) |
| 324 | + } |
| 325 | +} |
| 326 | +
|
| 327 | +fun main() { |
| 328 | + val fahrenheit = 90.0 |
| 329 | + val temp = Temperature.fromFahrenheit(fahrenheit) |
| 330 | + println("${temp.celsius}°C is $fahrenheit °F") |
| 331 | +} |
| 332 | +``` |
| 333 | +{initial-collapse-state="collapsed" collapsible="true" collapsed-title="Example solution" id="kotlin-tour-objects-solution-3"} |
| 334 | +
|
| 335 | +## Next step |
| 336 | +
|
| 337 | +[Intermediate: Open and special classes](kotlin-tour-intermediate-open-special-classes.md) |
0 commit comments