├── kotlin.kt ├── Kotlin Beginners Notes.pdf └── README.md /kotlin.kt: -------------------------------------------------------------------------------- 1 | fun main() { 2 | println("Kotlin :)") 3 | } -------------------------------------------------------------------------------- /Kotlin Beginners Notes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suhatanriverdi/Kotlin-Beginners-Notes/HEAD/Kotlin Beginners Notes.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kotlin Beginner Notes 2 | These are all personal notes taken from the Udacity Course (ud9011) of Kotlin Bootcamp for Programmers by Google as well as other resources. You can use it to learn Kotlin if you are a **beginner**, I have taken most of the things mentioned in the all sections/videos of this course including some brief pieces from official documentation and official video of JetBrains as well. I also cared about the order of the topics, so it starts from the basics from top to bottom goes to the more advanced ones. This is not an official documentation. You will be probably finding more personal notes and human mistakes :) 3 | 4 | ![2021-07-26 21_33_44-Editing Kotlin-Notes_README md at main · shtanriverdi_Kotlin-Notes — Mozilla Fir](https://user-images.githubusercontent.com/36234545/127040534-c2448cfd-a4c4-43cd-9bb3-c03787b37e51.png) 5 | 6 | # Table of Contents 7 | 8 | * [Lesson 1 & 2 Introduction | Kotlin Basics](#l1) 9 | * [Lesson 3 | Functions](#l3) 10 | * [Lesson 4 | Classes](#l4) 11 | * [Lesson 5 | Kotlin Essentials: Beyond The Basics](#l5) 12 | 13 |

Lesson 1 & 2 Introduction | Kotlin Basics

14 | 15 | ### Package Definition and Imports 16 | 17 | ```kotlin 18 | // Package specification should be at the top of the source file. 19 | package my.demo 20 | 21 | import kotlin.text.* 22 | 23 | // Main function 24 | fun main(args: Array) { 25 | printHello () 26 | } 27 | 28 | // Alternative main, no need to write parameters! 29 | fun main() { 30 | printHello () 31 | } 32 | 33 | // Prints "Hello Kotlin", println() puts newline, print() 34 | fun printHello () { 35 | println("Hello Kotlin!") 36 | } 37 | 38 | // A function that returns "OK" string 39 | fun start(): String = "OK" 40 | 41 | // Function returns "Genesis", no need to write "return" 42 | fun returnString () : String = "Genesis" 43 | 44 | // Function returns "Genesis2" Alternative Way 45 | fun returnString2 () : String { 46 | return "Genesis2" 47 | } 48 | ``` 49 | 50 | ### Operators, -, +, /, * 51 | 52 | ```kotlin 53 | // Returns integer 54 | println(1 + 1) // Prints: 2 55 | println(53 - 3) // Prints: 50 56 | println(50 / 10) // Prints: 5 57 | println(1 / 2) // Prints: 0 58 | println(6 * 50) // Prints: 300 59 | 60 | // Returns double 61 | println(1.0 / 2.0) // Prints: 0.5 62 | println(1.0 / 2) // Prints: 0.5 63 | println(2 / 2.0) // Prints: 1.0 64 | 65 | // Kotlin let's you overwrite the basic operators 66 | // You can call methods on variables 67 | val fish = 2 68 | println(fish.times(6)) // Prints: 12 69 | println(fish.div(10.0)) // Prints: 0.2 70 | println(fish.plus(3)) // Prints: 5 71 | println(fish.minus(3)) // Prints: -1 72 | 73 | // You can use numbers(basic types) as if they were objects 74 | // Use primitive 'int' as an object 75 | 1.toLong() // 1 76 | println(false.not()) // true 77 | ``` 78 | 79 | ### Boxing 80 | ```kotlin 81 | // Boxing describes the process of converting a primitive value to an object and unboxing therefore the inverse 82 | // All numerical types in Kotlin have a supertype called Number 83 | // Store value one in a variable of type Number 84 | // It'll need to be placed in an object wrapper 85 | // This is called boxing 86 | val boxed: Number = 1 87 | ^ ^ ^ 88 | name type value 89 | 90 | val num: Int = 2 91 | val dob: Double = 2.0 92 | 93 | // Both lines do the exact same thing internally 94 | Integer x = 42; 95 | Integer y = Integer.valueOf(42); 96 | 97 | // Eventhough this is very handy, it unfortunately leads to a decrease in performance 98 | // We can avoid creating these objets wrappers by not storing numbers in objects 99 | // There are two types of variables in Kotlin 100 | // Changeable & Unchangeable 101 | // var val 102 | 103 | // With "val" you can assign value only once 104 | val aquarium = 1 105 | aquarium = 2 // -> ERROR! cannot be reassigned 106 | 107 | // You can assign vals; 108 | val str = "string" 109 | val numInt = 1 110 | val numDouble = 1.0 111 | val bool = false 112 | 113 | // With "var" you can assign a value, and then you can change it 114 | var fish = 2 115 | fish = 50 116 | // Type is inferred meaning that compiler can figure out the type from the context 117 | // Even so the type is inferred, it becomes fixed at compile time, 118 | // So you cannot change a type of a varible in kotlin once it's type has been determined. 119 | fish = "Bubbles" // ERROR 120 | 121 | // We can use variables in operations and there is no punctuation at the end 122 | var str = 8 123 | var a = 5 124 | a + str 125 | print(a + str) 126 | 127 | // Number types won't implicitly convert to other types, so you can't assign 128 | // A short value to a long variable or a byte to an int 129 | val b: Byte = 1 130 | val i: Int = b // ERROR Type Mismatch 131 | 132 | // But you can always assign them by casting like this; 133 | val i: Int = b.toInt() 134 | 135 | // Kotlin supports underscores in numbers 136 | val oneMillion = 1_000_000 137 | val socialSecurityNumber = 999_99_9999L 138 | val hexBytes = 0xFF_EC_DE_5E 139 | val bytes = 0b11010010_01101001_10010100_100100010 140 | 141 | // You can speficy long constants in a format that makes sense to you 142 | // The type is inferred by Kotlin 143 | ``` 144 | 145 | ### Nullability 146 | ```kotlin 147 | // Kotlin helps avoid null pointer exceptions 148 | // When you declare a variables type expicitly, by default its value cannot be null 149 | var rocks: Int = null 150 | 151 | // Use the question mark operator to indicate that a variable can be null 152 | var rocks: Int? = null 153 | 154 | // Whe you have complex data types such as a list, 155 | var lotsOfFish: List = listOf(null, null) 156 | 157 | // You can allow for the list to be null, but if it is not null its elements cannot be null 158 | var evenMoreFish: List? = null 159 | var definitelyFish: List? = null 160 | 161 | // Or you can allow both the list or the elements to be null 162 | definitelyFish = listOf(null, null) 163 | 164 | // Samples 165 | // Creating List 166 | var names5: List = listOf("asd", "adsad3") 167 | 168 | // Allow list to be null 169 | var names: List? = null 170 | 171 | // Allow list items to be null 172 | // But list cannot be null 173 | var names2: List = listOf() 174 | 175 | // ERROR, List cannot be null 176 | var names3: List = null 177 | 178 | // Allow both list items and list itself to be null 179 | // But list cannot be null 180 | var names4: List? = null 181 | 182 | var a: String = "abc" // Regular initialization means non-null by default 183 | a = null // compilation error 184 | // it's guaranteed not to cause an NPE, so you can safely say: 185 | val l = a.length 186 | 187 | // To allow nulls, you can declare a variable as nullable string, written String?: 188 | var b: String? = "abc" // can be set null 189 | b = null // ok 190 | val l = b.length // error: variable 'b' can be null 191 | print(b) 192 | 193 | // List with some null items 194 | var listWithNulls: List = listOf("Kot", null, "melo", null) 195 | 196 | // Checking for null in conditions 197 | // Option 1: First, you can explicitly check if b is null, and handle the two options separately: 198 | val l = if (b != null) b.length else -1 199 | 200 | // Option 2: Safe calls, Your second option is the safe call operator, written "?." 201 | val a = "Kotlin" 202 | val b: String? = null 203 | println(b?.length) 204 | println(a?.length) // Unnecessary safe call 205 | 206 | // This returns "b.length" if "b is not null", and "null" otherwise. The type of this expression is Int? 207 | // "b?.length" is equal to "if (b != null) b.length else -1" 208 | 209 | var b: String? = "abc" 210 | val l = if (b != null) b.length else -1 211 | val l2 = b?.length 212 | 213 | // Such a chain returns null if any of the properties in it is null. 214 | bob?.department?.head?.name 215 | 216 | // If you want to do an operation on the non-null items 217 | // To perform a certain operation only for non-null values, you can use the safe call operator together with let: 218 | val listWithNulls: List = listOf("Kotlin", null) 219 | for (item in listWithNulls) { 220 | item?.let { println(it) } // prints Kotlin and ignores null 221 | } 222 | 223 | // Print also nulls 224 | var listWithNulls: List = listOf("Genesis", null, "melo", null) 225 | print(listWithNulls) 226 | ``` 227 | 228 | ### Elvis operator " ?: " 229 | ```kotlin 230 | /*If the expression to the left of ?: is not null, the elvis operator returns it, otherwise it returns the expression to the right. Note that the right-hand side expression is evaluated only if the left-hand side is null.*/ 231 | 232 | val l: Int = if (b != null) b.length else -1 233 | val l = b?.length ?: -1 234 | 235 | /*If the expression to the left of ?: is not null, the elvis operator returns it, otherwise it returns the expression to the right. Note that the right-hand side expression is evaluated only if the left-hand side is null.*/ 236 | 237 | var b: String? = null 238 | var l = b?.length ?: -1 239 | print(l) 240 | 241 | /*Since throw and return are expressions in Kotlin, they can also be used on the right hand side of the elvis operator. This can be very handy, for example, for checking function arguments: 242 | */ 243 | fun foo(node: Node): String? { 244 | val parent = node.getParent() ?: return null 245 | val name = node.getName() ?: throw IllegalArgumentException("name expected") 246 | // ... 247 | } 248 | ``` 249 | 250 | ### The " ! " Operator & Not-null Assertion Operator " !! " 251 | 252 | ```kotlin 253 | /*This is unsafe nullable type (T?) conversion to a non-nullable type (T), !// will throw NullPointerException if the value is null.*/ 254 | 255 | /*The third option is for NPE-lovers: the not-null assertion operator (!!) converts any value to a non-null type and throws an exception if the value is null. You can write b!!, and this will return a non-null value of b (for example, a String in our example) or throw an NPE if b is null:*/ 256 | 257 | val l = b!!.length 258 | 259 | /*Thus, if you want an NPE, you can have it, but you have to ask for it explicitly, and it does not appear out of the blue.*/ 260 | ``` 261 | 262 | ### Safe casts 263 | ```kotlin 264 | /*Regular casts may result into a ClassCastException if the object is not of the target type. Another option is to use safe casts that return null if the attempt was not successful:*/ 265 | 266 | // Safe Casts 267 | var a = "1" 268 | var b = 5 269 | var aInt: Int? = a as? Int 270 | var bInt: Int? = b as? Int 271 | print(aInt) // null 272 | print(bInt) // 5 273 | ``` 274 | 275 | ### Collections of a nullable type 276 | ```kotlin 277 | /*If you have a collection of elements of a nullable type and want to filter non-null elements, you can do so by using filterNotNull:*/ 278 | 279 | val nullableList: List = listOf(1, 2, null, 4) 280 | val intList: List = nullableList.filterNotNull() 281 | 282 | var nullableList: List = listOf(1, null, 2, null, null, 5) 283 | var nonNullList = nullableList.filterNotNull() 284 | print(nonNullList) // Prints: [1, 2, 5] 285 | 286 | // You can do some cool null testing with the question mark operator saving you the pain of many if else statements 287 | 288 | // You can check if an object or variable is non null before accessing one of its methods 289 | 290 | val fishFoodTreats: Int? = null 291 | return fishFoodTreats?.dec() ?: 0 292 | 293 | val fishFoodTreats = 5 294 | return fishFoodTreats?.dec() ?: 0 295 | // You can also chain null tests in an expression 296 | /*If "fishFoodTreats" is not null use a treat and return a new value and otherwise return the value after the colon which is zero*/ 297 | ``` 298 | 299 | ### Practice Time: Basic Operations 300 | ```kotlin 301 | /* 302 | Solve the following using the operator methods in one line of code. 303 | If you start with 2 fish, and they breed twice, producing 71 offspring the first time, and 233 offspring the second time, and then 13 fish are swallowed by a hungry moray eel, how many fish do you have left? How many aquariums do you need if you can put 30 fish per aquarium? 304 | 305 | Hint: You can chain method calls. 306 | Hint: You can call the methods on numbers, and Kotlin will convert them to objects for you. 307 | Bonus question: What is special about all the numbers of fish?*/ 308 | 309 | // Solution Code 310 | 2.plus(71).plus(233).minus(13).div(30).plus(1) 311 | // Bonus question: If you've noticed, all fish numbers above are prime. 312 | 313 | // My One Line Solution 314 | println(((2.times(71).plus(2.times(233))).minus(13)).div(30).plus((if (595.mod(30) > 0) 1 else 0))) 315 | 316 | // To find how many fishes left 317 | println((2.times(71).plus(2.times(233))).minus(13)) 318 | 319 | // To find Aquariums Needed 320 | println(595.div(30) + if (595.mod(30) > 0) 1 else 0) 321 | ``` 322 | 323 | ### Practice Time: Variables 324 | ```kotlin 325 | /*Create a String variable rainbowColor, set its color value, then change it. 326 | Create a variable blackColor whose value cannot be changed once assigned. Try changing it anyway.*/ 327 | var rainbowColor: String = "green" 328 | rainbowColor = "blue" 329 | 330 | val blackColor: String = "you cannot change me// I am pure Black!" 331 | blackColor = "White!" 332 | 333 | //Alternative 334 | var rainbowColor = "green" 335 | rainbowColor = "blue" 336 | val blackColor = "black" 337 | blackColor = "white" // Error 338 | ``` 339 | 340 | ### Practice Time: Nullability 341 | ```kotlin 342 | // Try to set rainbowColor to null. Declare two variables, greenColor and blueColor. Use two different ways of setting them to null. 343 | 344 | var rainbowColor = "red" 345 | rainbowColor = null // Error 346 | 347 | var greenColor = null 348 | var blueColor: Int? = null 349 | ``` 350 | 351 | ### Practice Time: Nullability/Lists 352 | ```kotlin 353 | // Create a list with two elements that are null; do it in two different ways. 354 | // Next, create a list where the list is null. 355 | // list with two null items 356 | var list = listOf(null,null) 357 | var list1: List = listOf(null, null) 358 | 359 | // The list2 itself is null 360 | var list2: List? = null 361 | ``` 362 | 363 | ### Practice Time: Null Checks 364 | ```kotlin 365 | // Create a nullable integer variable called nullTest, and set it to null. Use a null-check that increases the value by one if it's not null, otherwise returns 0, and prints the result. 366 | // Hint: Use the Elvis operator. 367 | 368 | var nullable: Int? = null 369 | println(nullable?.inc() ?: 0) 370 | println(nullTest?.inc() ?:0) 371 | ``` 372 | 373 | 374 | 375 | ### Strings 376 | ```kotlin 377 | "Hello Fish" // Hello Fish 378 | 379 | // Concatenation 380 | "hello" + "fish" // hello fish 381 | 382 | val numberOfFish = 5 383 | val numberOfPlants = 12 384 | 385 | "I have $numberOfFish fish and $numberOfPlants plants" // I have 5 fish and 12 plants 386 | 387 | // Here two numbers get added first then the result will be printed 388 | "I have ${numberOfFish + numberOfPlants} fish and plants" // I have 17 fish and plants 389 | 390 | val fish = "fish" 391 | val plant = "plant" 392 | println(fish == plant) // false 393 | println(fish != plant) // true 394 | 395 | val A = "A" 396 | val B = "Z" 397 | println(A < B) // true 398 | println(A > B) // false 399 | ``` 400 | 401 | ### If-Else Blocks 402 | ```kotlin 403 | val numberOfFish = 50 404 | val numberOfPlants = 23 405 | if (numberOfFish > numberOfPlants) { 406 | println("Good Ratio!") 407 | } else { 408 | println("unhealthy ratio") 409 | } 410 | ``` 411 | 412 | ### Ranges 413 | ```kotlin 414 | val fish = 50 415 | // .. -> inclusively 1 <= fish <= 50 416 | if (fish in 1..50) { 417 | println(fish.toString() + " is in the range 1 <= fish <= 50!") 418 | } 419 | // until -> exclusively 1 <= fish < 50 420 | if (fish in 1 until 50) { 421 | println(fish) 422 | } else { 423 | println(fish.toString() + " is not in the range 1 <= fish < 50!") 424 | } 425 | ``` 426 | 427 | ### When 428 | ```kotlin 429 | // "when" is the way of switching in Kotlin 430 | val numberOfFish = 50 431 | when (numberOfFish) { 432 | 0 -> println("Empty tank") 433 | 50 -> println("Full tank") 434 | else -> println("Perfect!") 435 | } // Output: Full tank 436 | 437 | val numberOfFish = 50 438 | when (numberOfFish) { 439 | in 1..50 -> println("Full tank") 440 | } // Output: Full tank 441 | 442 | // Create a string which would contain a * symbol n times. 443 | val str: String = "*".repeat(100) 444 | ``` 445 | 446 | ### Practice Time 447 | ```kotlin 448 | // Create three String variables for trout, haddock, and snapper. 449 | // Use a String template to print whether you do or don't like to eat these kinds of fish. 450 | var trout: String = "trout" 451 | var haddock: String = "haddock" 452 | var snappe: String = "snappe" 453 | var currentFish = trout 454 | when (currentFish) { 455 | "trout" -> println("I love it!") 456 | "haddock" -> println("I like it") 457 | "snappe" -> println("I hate it") 458 | else -> println("That's enough fish") 459 | } 460 | 461 | val trout1 = "trout" 462 | var haddock1 = "haddock" 463 | var snapper1 = "snapper" 464 | println("I like to eat $trout1 and $snapper1, but not a big fan of $haddock1.") 465 | ``` 466 | 467 | ### Practice Time 468 | ```kotlin 469 | /*when statements in Kotlin are like case or switch statements in other languages. 470 | Create a when statement with three comparisons: 471 | 472 | If the length of the fishName is 0, print an error message. 473 | If the length is in the range of 3...12, print "Good fish name". 474 | If it's anything else, print "OK fish name".*/ 475 | 476 | var fishName = "Salmon" 477 | when (fishName.length) { 478 | 0 -> println("Fish name cannot be empty!") 479 | in 3..12 -> println("Good fish name") 480 | else -> println("OK fish name") 481 | } 482 | ``` 483 | 484 | ### Arrays & Loops 485 | ```kotlin 486 | // If val variable value is a reference, then you cannot assign it a different reference later 487 | 488 | val myList = mutableListOf("tuna", "salmon", "shark"); 489 | myList = mutableListOf("Koi"); // ERROR// Cannot be re-assigned 490 | 491 | // If you're referencing something that's not immutable(değişmez), it can still change 492 | 493 | // val only applies to the reference and it doesn't make the object it points to immutable 494 | 495 | // Here we cannot assign a different list in myList but we can manipulate the elemets of the list such as removing/adding an element 496 | 497 | val myList = mutableListOf("tuna", "salmon", "shark"); 498 | myList.remove("shark") // True 499 | myList.add("fish") // True 500 | ``` 501 | 502 | ### For/While Loop Examples 503 | ```kotlin 504 | val myList = mutableListOf("tuna", "salmon", "shark"); 505 | // Loop through an array 506 | for (item in myList) { 507 | print(item + " ") // tuna salmon shark 508 | } 509 | // Loop through an array With index 510 | for (index in myList.indices) { 511 | print(myList[index] + " ") // tuna salmon shark 512 | } 513 | for ((index, value) in myList.withIndex()) { 514 | println("the element at $index is $value") // the element at 0 is tuna... 515 | } 516 | // for (i in array.indices) { 517 | // println(array[i]) 518 | // } 519 | // To iterate over a range of numbers, use a range expression 520 | for (i in 1..5) { 521 | // print(i.toString() + " ") /// Alternate 522 | print("$i ") // 1 2 3 4 5 523 | } 524 | for (c in 'a'..'z') { 525 | print("$c ") // a b c d e f g h i j k l m n o p q r s t u v w x y z 526 | } 527 | for (c in 'z' downTo 'a') { 528 | print("$c ") // z y x w v u t s r q p o n m l k j i h g f e d c b a 529 | } 530 | for (c in 10 downTo 0) { 531 | print("$c ") // 10 9 8 7 6 5 4 3 2 1 0 532 | } 533 | for (c in 10 downTo 0 step 2) { 534 | print("$c ") // 10 8 6 4 2 0 535 | } 536 | for (c in 1..10 step 2) { 537 | print("$c ") // 1 3 5 7 9 538 | } 539 | ``` 540 | 541 | ### While loop 542 | ```kotlin 543 | var x = 5 544 | while (x > 0) { 545 | print("$x ") // 5 4 3 2 1 546 | x-- 547 | } 548 | // Arrays work pretty much as you'd expect with some cool additions 549 | // Good practice is to prefer using "lists" over "arrays" everywhere except for performance critical parts of your code 550 | 551 | // It is pretty similar to Java 552 | val l1 = listOf("a") 553 | val l2 = listOf("a") 554 | var x = (l1 == l2) // => true 555 | 556 | val a1 = arrayOf("a") 557 | val a2 = arrayOf("a") 558 | var y = (a1 == a2) // => false 559 | ``` 560 | 561 | ### listOf vs mutableListOf 562 | ```kotlin 563 | /* 564 | List: READ-ONLY 565 | MutableList: READ/WRITE 566 | You can modify a MutableList: change, remove, add... its elements. 567 | In a List you can only read them. 568 | 569 | // Prefer MutableList over Array 570 | // The major difference from usage side is that; 571 | -> Arrays have a fixed size (like int [] in C++) 572 | -> MutableList can adjust their size dynamically (like vectors in C++, a.k.a dynamic arrays) 573 | -> Moreover Array is MUTABLE whereas List is not. (List is read-only, Array is not) 574 | 575 | // Difference between ArrayList() and mutableListOf() in Kotlin 576 | -> The only difference between the two is communicating your intent :) 577 | -> So, there is no difference, just a convenience method.*/ 578 | 579 | // Create an array 580 | val school = arrayOf("fish", "tuna", "salmon") 581 | 582 | // Create Typed array (e.g. integers) 583 | val numbers = intArrayOf(1, 2, 3) 584 | 585 | // Error, Type Mismatch 586 | val test = intArrayOf(2, "foo") 587 | 588 | // But you can mix types in Untyped arrays 589 | val mixedArray = arrayOf("fish", 2, 's', 0.0) 590 | for (element in mixedArray) { 591 | println(element) // fish 2 s 0.0 592 | // print(element.toString() + " ") 593 | } 594 | 595 | // This does not prints the all elements, it prints the array address instead 596 | val mixedArray = arrayOf("fish", 2, 's', 0.0) 597 | print(mixedArray) // [Ljava.lang.Object;@66d3c617 598 | 599 | // You can use joinToString or forEach, forEachIndexed, Arrays.toString( array ) 600 | val mixedArray = arrayOf("fish", 2, 's', 0.0) 601 | print(mixedArray.joinToString()) // fish, 2, s, 0.0 602 | mixedArray.forEach { print("$it ") } // fish, 2, s, 0.0 603 | mixedArray.forEachIndexed { index, any -> println("$any at $index") } 604 | // fish at 0 ... 605 | println(Arrays.toString(mixedArray)) // [fish, 2, s, 0.0] 606 | ``` 607 | 608 | ### Nesting Arrays 609 | ```kotlin 610 | // You can nest arrays 611 | val swarm = listOf(5, 12) 612 | // When you put an array within an array, you have an array of arrays 613 | // !Not a flattened array of the contents of the two 614 | val bigSwarm = arrayOf(swarm, arrayOf("A", "B", "C")) 615 | println(Arrays.toString(bigSwarm)) 616 | println(bigSwarm.asList()) // Shorter Printing Array Alternative 617 | // Prints: [[5, 12], [Ljava.lang.String;@452b3a41] 618 | 619 | // You can nest arrays 620 | val intList = listOf(5, 12) 621 | val stringList = mutableListOf("A", "B", "C") 622 | // OR this -> val stringList = listOf("A", "B", "C") 623 | // When you put "LİST or MUTABLELIST" within an array, you have an array of arrays with merged content but "ARRAYS" are passed by ref as shown above example 624 | val bigList = listOf(intList, stringList) 625 | println(bigList.joinToString()) 626 | // [5, 12], [A, B, C] 627 | ``` 628 | 629 | ### Create Typed Lists, Mutablelists and Arrays 630 | ```kotlin 631 | val intList = listOf(5, 12) 632 | val stringList = listOf("1","2","3","4") 633 | 634 | // Mutablelists 635 | val intList = mutableListOf(5, 12) 636 | val stringList = mutableListOf("1","2","3","4") 637 | 638 | // Array 639 | val intList = arrayOf(5, 12) 640 | val stringList = arrayOf("1","2","3","4") 641 | 642 | // Sized array 643 | var table = Array(words.size) {""} 644 | val literals = arrayOf("January", "February", "March") 645 | 646 | // Create 2D Array 647 | val grid = Array(rows) { Array(cols) { Any() } } 648 | 649 | //String[] in Java equivalent Array in Kotlin 650 | //eg. 651 | var array1 : Array = emptyArray() 652 | var array2: Array = arrayOfNulls(4) 653 | var array3: Array = arrayOf("Mashroom", "Kitkat", "Oreo", "Lolipop") 654 | 655 | val num = arrayOf(1, 2, 3, 4) //implicit type declaration 656 | val num = arrayOf(1, 2, 3) //explicit type declaration 657 | 658 | // Or you can also create typed lists, arrays, mutable lists 659 | val intList = listOf(5, 12) 660 | val listSample: List = listOf(1,2,3) 661 | val mutableListSample: MutableList = mutableListOf(1,2,3) 662 | val stringList = listOf("1","2","3","4") 663 | val stringListSample: List = listOf("1","2","3","4") 664 | var initList = List(4){"s"} // {"s", "s", "s", "s"} 665 | 666 | // Arrays 667 | val intArray = arrayOf(1,2,3) 668 | val intArray2: Array = arrayOf(1,2,3) 669 | val intArray3 = intArrayOf(1,2,3) 670 | val charArray = charArrayOf('a', 'b', 'c') 671 | val intArray = arrayOf(1,2,3) 672 | val intArray2: Array = arrayOf(1,2,3) 673 | val intArray3 = intArrayOf(1,2,3) 674 | val charArray = charArrayOf('a', 'b', 'c') 675 | val stringArray = arrayOf("genesis", "melo") 676 | val stringArray2: Array = arrayOf("genesis", "melo") 677 | val stringOrNulls = arrayOfNulls(5) // returns Array 678 | val stringOrNulls2: Array = arrayOf("", null)// returns Array 679 | var emptyStringArray: Array = emptyArray() 680 | var emptyStringArray2: Array = arrayOf() 681 | var sizedEmptyArray = Array(4){"s"} // {"s", "s", "s", "s"} 682 | 683 | // In this line, we create an array from a range of numbers. 684 | val nums3 = IntArray(5, { i -> i * 2 + 3}) 685 | // This line creates an array with IntArray. It takes the number of elements and a factory function as parameters. 686 | // This is the output. 687 | /* 688 | [1, 2, 3, 4, 5] 689 | [3, 4, 5, 6, 7, 8, 9, 10, 11, 12] 690 | [3, 5, 7, 9, 11] 691 | */ 692 | 693 | // You can read this as initialize an array of 5 elements, assign each item to its index times two 694 | val array = Array(5) {it * 2} 695 | // OR -> val array = List(5) {it * 2} 696 | println(array.joinToString()) // 0, 2, 4, 6, 8 697 | 698 | val list = List(5){ it.times(2) } // Creates List: [0, 2, 4, 6, 8] 699 | 700 | val array = Array(5) { e -> if (e % 2 == 0) 2 else 1 } 701 | println(array.asList()) 702 | 703 | val array = List(5) { e -> if (e % 2 == 0) "even" else "$e" } 704 | println(array.joinToString()) // Prints: even, 1, even, 3, even 705 | 706 | // Loop array with indices 707 | val swarm = listOf(5, 12, 15, 17) 708 | 709 | for (i in 0 until swarm.size) { 710 | print("$i ") // 0 1 2 3 711 | } 712 | 713 | for (i in 0..swarm.size - 1) { 714 | print("$i ") // 0 1 2 3 715 | } 716 | 717 | for (i in swarm.indices) { 718 | print("$i ") // 0 1 2 3 719 | } 720 | 721 | for (indexValuePair in swarm.withIndex()) { 722 | print("index: ${indexValuePair.index}, value: ${indexValuePair.value}\n") 723 | } // Prints: index: 0, value: 5 724 | ``` 725 | 726 | 727 | ### Quiz 728 | ```kotlin 729 | // Read the code below, and then select the correct array initialization that will display the corresponding output. 730 | val array = // initalize array here 731 | val sizes = arrayOf("byte", "kilobyte", "megabyte", "gigabyte", 732 | "terabyte", "petabyte", "exabyte") 733 | for ((i, value) in array.withIndex()) { 734 | println("1 ${sizes[i]} = ${value.toLong()} bytes") 735 | } 736 | 737 | // Output: 738 | 1 byte = 1 bytes 739 | 1 kilobyte = 1000 bytes 740 | 1 megabyte = 1000000 bytes 741 | 1 gigabyte = 1000000000 bytes 742 | 1 terabyte = 1000000000000 bytes 743 | 1 petabyte = 1000000000000000 bytes 744 | 1 exabyte = 1000000000000000000 bytes 745 | 746 | // Answer / Solution Code: 747 | val array = Array(7){ 1000.0.pow(it) } 748 | // Notice how we had to use the double value 1000.0 and not just 1000 to be able to use the "pow" function. 749 | ``` 750 | 751 | 752 | ### Quiz 753 | ```kotlin 754 | /* Which of these options are good reasons to explicitly make a list immutable? There may be more than 1 correct answer. 755 | -> It reduces errors in general. 756 | -> Prevents accidental changing of objects that were meant to be unchangeable. 757 | -> In a multi-threaded environment, makes the variable thread safe, because once it has been assigned by the initial thread, no thread can change it.*/ 758 | 759 | // Answer: Immutable variables are the safest option when you know that a variable will never need to change values. 760 | ``` 761 | 762 | ### Practice Time 763 | ```kotlin 764 | /*Looping over arrays and lists is a fundamental technique that has a lot of flexibility in Kotlin. Let's practice. 765 | 766 | Basic example 767 | Create an integer array of numbers called numbers, from 11 to 15. 768 | Create an empty mutable list for Strings. 769 | Write a for loop that loops over the array and adds the string representation of each number to the list. 770 | 771 | Challenge example 772 | How can you use a for loop to create (a list of) the numbers between 0 and 100 that are divisible by 7?*/ 773 | 774 | // Solution Code 775 | var list3 : MutableList = mutableListOf() 776 | for (i in 0..100 step 7) list3.add(i) 777 | print(list3) 778 | [0, 7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84, 91, 98] 779 | 780 | // OR 781 | 782 | for (i in 0..100 step 7) println(i.toString() + " - ") 783 | 784 | // My Solution 785 | val numbers = Array(5) { it + 11 } 786 | println(numbers.asList()) 787 | var mutableList = mutableListOf() 788 | for (number in numbers) { 789 | mutableList.add(number.toString()) 790 | } 791 | println(mutableList) 792 | // Challange Example 793 | for (number in 7..100 step 7) { 794 | println("$number ") 795 | } 796 | ``` 797 | 798 | ### Kotlin Lists (from Jetbrains Official Video) 799 | ```kotlin 800 | listOf(1, 2, 3) 801 | // [1, 2, 3] 802 | 803 | val l2 = List(5){ "No. $it" } 804 | val l3 = List(5){ idx -> "No. $idx" } 805 | // [No. 0, No. 1, No. 2, No. 3, No. 4] 806 | 807 | val l4 = "word-salad".toList() 808 | // [w, o, r, d, -, s, a, l, a, d] 809 | 810 | val m1 = mapOf( 811 | 1 to "Gold", 812 | 2 to "Silver", 813 | 3 to "Bronze" 814 | ).toList() 815 | // [(1, Gold), (2, Silver), (3, Bronze)] 816 | 817 | generateSequence { 818 | Random.nextInt(100).takeIf { it > 30 } 819 | }.toList() 820 | // [45, 75, 74, 31, 54, 36, 63] 821 | 822 | (0..5).toList() 823 | // [0, 1, 2, 3, 4, 5] 824 | 825 | val mutList = mutableListOf(1, 2, 3) 826 | val otherList = mutList.toList() 827 | 828 | mutList[0] = 5 829 | 830 | mutList 831 | // [5, 2, 3] 832 | 833 | otherList 834 | // [1, 2, 3] 835 | 836 | val myList = listOf("A", "B", "C") 837 | myList.get(0) 838 | myList[0] 839 | 840 | myList.getOrNull(3) 841 | val test = myList.getOrElse(3) { 842 | println("There is no index $it") 843 | ":(" 844 | } 845 | // There is no index 3 846 | // :( 847 | 848 | val listOfNullableItems = listOf(1, 2, null, 4) 849 | // Elvis operator checks if item at index 2 null, if it is null it returns 0, 850 | // returns the element at index 2 otherwise 851 | val item = listOfNullableItems[2] ?: 0 852 | 853 | val myList1 = listOf("a", "b", "c", "d", "e") 854 | myList1.slice(listOf(0, 2, 4)) 855 | // [a, c, e] 856 | 857 | myList1.slice(0..3) 858 | // [a, b, c, d] 859 | 860 | myList1.slice(0..myList1.lastIndex step 2) 861 | // [a, c, e] 862 | 863 | myList1.slice(2 downTo 0) 864 | // [c, b, a] 865 | 866 | mutableListOf(1, 2, 3) 867 | // [1, 2, 3] 868 | 869 | (0..5).toMutableList() 870 | // [0, 1, 2, 3, 4, 5] 871 | 872 | listOf(1, 2, 3).toMutableList() 873 | // [1, 2, 3] 874 | 875 | val m = mutableListOf(1, 2, 3) 876 | m.add(4) 877 | m += 4 878 | // [1, 2, 3, 4, 4] 879 | 880 | m.add(2, 10) 881 | // [1, 2, 10, 3, 4, 4] 882 | 883 | // Append a list to another list 884 | m += listOf(5, 6, 7) 885 | // [1, 2, 10, 3, 4, 4, 5, 6, 7] 886 | 887 | val mList = mutableListOf(1, 2, 3, 3, 3, 4) 888 | mList -= 3 889 | mList.remove(3) 890 | // [1, 2, 3, 4] 891 | 892 | // Removes all instances of given elements from the mList 893 | mList -= listOf(1, 4) 894 | // [2, 3] 895 | 896 | mList.removeAt(1) 897 | // [2] 898 | 899 | mList[0] = 5 900 | // [5] 901 | 902 | val fruits = mutableListOf("Apple", "Apricot", "Cherry") 903 | fruits.fill("sugar") 904 | // [sugar, sugar, sugar] 905 | fruits.clear() 906 | // [] 907 | 908 | // The following will create a new list and return it 909 | val list = listOf(3, 1, 4, 1, 5, 9) 910 | list.shuffled() 911 | // [5, 3, 4, 1, 1, 9] 912 | 913 | list.sorted() 914 | // [1, 1, 3, 4, 5, 9] 915 | 916 | list.reversed() 917 | // [9, 5, 1, 4, 1, 3] 918 | 919 | // The following will do the operations "in-place" without creating a new list 920 | val mm = list.toMutableList() 921 | mm.shuffle() 922 | // [5, 3, 4, 1, 1, 9] 923 | 924 | mm.sort() 925 | // [1, 1, 3, 4, 5, 9] 926 | 927 | mm.reverse() 928 | // [9, 5, 1, 4, 1, 3] 929 | 930 | val numbers = mutableListOf(3, 1, 4, 1, 5 ,9) 931 | numbers.removeAll { it < 5 } 932 | // [5, 9] 933 | 934 | val letters = mutableListOf('a', 'b', '3', 'd', '5') 935 | letters.retainAll { it.isLetter() } 936 | // [a, b, d] 937 | 938 | val letters2 = mutableListOf("A", "B", "C", "D") 939 | val sub = letters2.subList(1, 4) // [ inclusive, exclusive ) 940 | // [B, C, D] 941 | 942 | // There is only one list here, sub list is just a reference, a view of letters2 943 | // And they are reflecting each other 944 | letters2[1] = "Z" 945 | println(sub) 946 | // [Z, C, D] 947 | 948 | sub[2] = "MM" 949 | println(letters2) 950 | // [A, Z, C, MM] 951 | 952 | sub.fill("FF") 953 | println(letters2) 954 | // [A, FF, FF, FF] 955 | 956 | sub.clear() 957 | println(letters2) 958 | // [A] 959 | 960 | letters2.clear() 961 | // println(sub) // ERROR// Because there is no more original list 962 | 963 | val nums = mutableListOf(1, 2 ,3, 4) 964 | // A reversed view of nums 965 | val smun = nums.asReversed() 966 | 967 | println(smun) 968 | // [4, 3, 2, 1] 969 | 970 | nums[1] = 99 971 | println(smun) 972 | // [4, 3, 99, 1] 973 | 974 | smun[2] = -1 975 | println(nums) 976 | // [1, -1, 3, 4] 977 | ``` 978 | 979 |

Lesson 3 | Functions

980 | 981 | ```kotlin 982 | // A function like main returns a type "UNIT" which is Kotlin's way of saying no value 983 | fun main(args: Array) { 984 | println("Hello, world!") 985 | println(test()) // kotlin.Unit 986 | } 987 | 988 | fun test() { 989 | } 990 | ``` 991 | 992 | ### Practice Time 993 | ```kotlin 994 | /*Basic Task 995 | 996 | Create a new Kotlin file. 997 | Copy and paste the main() function from Hello World into the file. 998 | Create a new function, dayOfWeek(). 999 | In the body of the function, print "What day is it today?" 1000 | Call dayOfWeek() from main(). 1001 | Run your program. 1002 | Task List 1003 | 1004 | Extended Task 1005 | 1006 | In the body of the dayOfWeek() function, answer the question by printing the current day of the week. 1007 | 1008 | Hints 1009 | You can use Java libraries (java.util) from Kotlin. For example, to get the day of the week: 1010 | Calendar.getInstance().get(Calendar.DAY_OF_WEEK) 1011 | Type in the code, then press Option + Enter in Mac, or Alt + Enter in Windows, over the red Calendar class to import the library. 1012 | Use a when statement*/ 1013 | 1014 | // Answer: 1015 | import java.util.* 1016 | fun main(args: Array) { 1017 | dayOfWeek() 1018 | } 1019 | 1020 | fun dayOfWeek() { 1021 | println("What day is it today?") 1022 | val day = Calendar.DAY_OF_WEEK 1023 | println(when(day) { 1024 | 1 -> "Monday" 1025 | 2 -> "Tuesday" 1026 | 3 -> "Wednesday" 1027 | 4 -> "Thursday" 1028 | 5 -> "Friday" 1029 | 6 -> "Saturday" 1030 | 7 -> "Sunday" 1031 | else -> "Time has stopped" 1032 | }) 1033 | } 1034 | ``` 1035 | 1036 | ```kotlin 1037 | // Run -> Edit COnfigurations -> Program Args: Kotlin 1038 | fun main(args: Array) { 1039 | println("Hello, ${ args[0] }") // Hello, Kotlin 1040 | } 1041 | 1042 | // Fetching the first element of an array is EXPRESSION 1043 | // not a value, that why we used ${ args[0] } 1044 | 1045 | // In Kotlin almost everthing has a value, even if that value is unit 1046 | // Everything in Kotlin is an expression 1047 | // You can use the value of an "if" expression right away 1048 | 1049 | val isUnit = println("This is an expression") 1050 | println(isUnit) 1051 | // This is an expression 1052 | // kotlin.Unit 1053 | 1054 | val temperature = 10 1055 | val isHot = if (temperature > 50) true else false 1056 | println(isHot) // false 1057 | 1058 | val message = "You are ${ if (temperature > 50) "fried" else "safe" } fish" 1059 | println(message) // You are safe fish 1060 | ``` 1061 | 1062 | ### Exercise: Greetings, Kotlin 1063 | ```kotlin 1064 | /*Create a main() function that takes an argument representing the time in 24-hour format 1065 | (values between and including 0 -> 23). 1066 | 1067 | In the main() function, check if the time is before midday (<12), then print "Good morning, Kotlin"; otherwise, print "Good night, Kotlin". 1068 | 1069 | Notes: 1070 | Remember that all main() function arguments are Strings, so you will have to convert this argument to an Int before you can apply the check. 1071 | 1072 | Advanced 1073 | Try to use Kotlin's string templates to do this in 1 line.*/ 1074 | 1075 | // Your reflection 1076 | fun main(args: Array) { 1077 | println(if (args[0].toInt() < 12) "Good morning, Kotlin" else "Good night, Kotlin") 1078 | } 1079 | 1080 | /*Things to think about 1081 | There are multiple ways you can do this in Kotlin. Make sure you test your code in IntelliJ with multiple values. 1082 | 1083 | Here's one way to do it:*/ 1084 | 1085 | if (args[0].toInt() < 12) println("Good morning, Kotlin") 1086 | else println("Good night, Kotlin" ) 1087 | 1088 | // OR 1089 | 1090 | println("${if (args[0].toInt() < 12) "Good morning, Kotlin" else "Good night, Kotlin"}") 1091 | 1092 | // CTRL + ALT + L -> Indent File 1093 | 1094 | // Repeat an action x times 1095 | repeat(10) { i -> 1096 | println("This line will be printed 10 times") 1097 | println("We are on the ${i + 1}. loop iteration") 1098 | } 1099 | 1100 | // Greets three times 1101 | repeat(3) { 1102 | println("Hello") 1103 | } 1104 | 1105 | // Greets with an index 1106 | repeat(3) { index -> 1107 | println("Hello with index $index") 1108 | } 1109 | ``` 1110 | 1111 | ### Practice Time 1112 | ```kotlin 1113 | import kotlin.random.Random 1114 | fun main(args: Array) { 1115 | val str = "*".repeat(10) // ********** 1116 | println(str) 1117 | // Repeat an action 10 times 1118 | repeat (10) { index -> 1119 | println("${Random.nextInt(7)} index: $index") 1120 | } 1121 | feedTheFish() 1122 | } 1123 | 1124 | fun feedTheFish() { 1125 | val day = randomDay() 1126 | val food = "pellets" 1127 | println("Today is $day and the fish eat $food") 1128 | } 1129 | 1130 | fun randomDay(): String { 1131 | val week = listOf( 1132 | "Monday", 1133 | "Tuesday", 1134 | "Wednesday", 1135 | "Thursday", 1136 | "Friday", 1137 | "Saturday", 1138 | "Sunday" 1139 | ) 1140 | return week[Random.nextInt(7)] 1141 | } 1142 | ``` 1143 | 1144 | ### Practice Time 1145 | ```kotlin 1146 | /*Create a program with a function that returns a fortune cookie message that you can print. 1147 | 1148 | Create a main() function. 1149 | From the main() function, call a function, getFortuneCookie(), that returns a String. 1150 | Create a getFortuneCookie() function that takes no arguments and returns a String. 1151 | In the body of getFortuneCookie(), create a list of fortunes. Here are some ideas: 1152 | 1153 | "You will have a great day!" 1154 | "Things will go well for you today." 1155 | "Enjoy a wonderful day of success." 1156 | "Be humble and all will turn out well." 1157 | "Today is a good day for exercising restraint." 1158 | "Take it easy and enjoy life!" 1159 | "Treasure your friends because they are your greatest fortune." 1160 | Below the list, print: "Enter your birthday: " 1161 | 1162 | Hint: Use print(), not println() 1163 | Create a variable, birthday. 1164 | Read the user's input form the standard input and assign it to birthday. If there is no valid input, set birthday to 1. 1165 | Hint: Use readLine() to read a line of input (completed with Enter) as a String. 1166 | Hint: In Kotlin, you can use toIntOrNull() to convert a number as a String to an Integer numeric. If the user enters "", toIntOrNull returns null. 1167 | Hint: Check for null using the ? operator and use the ?: operator to handle the null case. 1168 | Divide the birthday by the number of fortunes, and use the remainder as the index for the fortune to return. 1169 | Return the fortune. 1170 | In main(), print: "Your fortune is: ", followed by the fortune string. 1171 | Extra practice: 1172 | Use a for loop to run the program 10 times, or until the "Take it easy" fortune has been selected.*/ 1173 | 1174 | // Solution Code 1175 | fun main(args: Array) { 1176 | println("\nYour fortune is: ${getFortuneCookie()}") 1177 | } 1178 | 1179 | fun getFortuneCookie() : String { 1180 | val fortunes = listOf( "You will have a great day!", 1181 | "Things will go well for you today.", 1182 | "Enjoy a wonderful day of success.", 1183 | "Be humble and all will turn out well.", 1184 | "Today is a good day for exercising restraint.", 1185 | "Take it easy and enjoy life!", 1186 | "Treasure your friends, because they are your greatest fortune.") 1187 | print("\nEnter your birthday: ") 1188 | val birthday = readLine()?.toIntOrNull() ?: 1 1189 | return fortunes[birthday.rem(fortunes.size)] 1190 | } 1191 | 1192 | // Extra Practice 1193 | fun main(args: Array) { 1194 | var fortune: String 1195 | for (i in 1..10) { 1196 | fortune = getFortuneCookie() 1197 | println("\nYour fortune is: $fortune") 1198 | if (fortune.contains("Take it easy")) break 1199 | } 1200 | } 1201 | 1202 | // My Solution 1203 | import kotlin.random.Random 1204 | fun main(args: Array) { 1205 | // OR -> for (i in 1..10) { ... } 1206 | repeat (10) { 1207 | val fortune = getFortuneCookie() 1208 | println("Your fortune is: ${fortune.first}") 1209 | if (fortune.second == 5) { 1210 | return 1211 | } 1212 | } 1213 | } 1214 | 1215 | fun getFortuneCookie(): Pair { 1216 | val fortunes = listOf( 1217 | "You will have a great day!", 1218 | "Things will go well for you today.", 1219 | "Enjoy a wonderful day of success.", 1220 | "Be humble and all will turn out well.", 1221 | "Today is a good day for exercising restraint.", 1222 | "Take it easy and enjoy life!", 1223 | "Treasure your friends because they are your greatest fortune." 1224 | ) 1225 | print("Enter your birthday: ") 1226 | var birthday: String = readLine() ?: "1" 1227 | val selectedFortuneIndex = birthday.toInt().rem(fortunes.size) 1228 | return Pair(fortunes[ selectedFortuneIndex ], selectedFortuneIndex) 1229 | } 1230 | ``` 1231 | 1232 | ### Practice Time | Fish Food 1233 | ```kotlin 1234 | import java.time.MonthDay 1235 | import kotlin.random.Random 1236 | 1237 | fun main(args: Array) { 1238 | repeat (10) { index -> 1239 | feedTheFish() 1240 | } 1241 | } 1242 | 1243 | fun fishFood(day: String): String { 1244 | return when (day) { 1245 | "Monday" -> "flakes" 1246 | "Tuesday"-> "redworms" 1247 | "Wednesday" -> "granules" 1248 | "Thursday" -> "mosquitoes" 1249 | "Friday" -> "plankton" 1250 | "Saturday" -> "lettuce" 1251 | else -> "fasting" 1252 | } 1253 | } 1254 | 1255 | fun feedTheFish() { 1256 | val day = randomDay() 1257 | val food = fishFood(day) 1258 | println("Today is $day and the fish eat $food") 1259 | } 1260 | 1261 | fun randomDay(): String { 1262 | val week = listOf( 1263 | "Monday", 1264 | "Tuesday", 1265 | "Wednesday", 1266 | "Thursday", 1267 | "Friday", 1268 | "Saturday", 1269 | "Sunday" 1270 | ) 1271 | return week[Random.nextInt(7)] 1272 | } 1273 | ``` 1274 | 1275 | ### Practice Time 1276 | ```kotlin 1277 | // Solution Code 1278 | fun getBirthday(): Int { 1279 | print("\nEnter your birthday: ") 1280 | return readLine()?.toIntOrNull() ?: 1 1281 | } 1282 | 1283 | fun getFortune(birthday: Int): String { 1284 | val fortunes = listOf("You will have a great day!", 1285 | "Things will go well for you today.", 1286 | "Enjoy a wonderful day of success.", 1287 | "Be humble and all will turn out well.", 1288 | "Today is a good day for exercising restraint.", 1289 | "Take it easy and enjoy life!", 1290 | "Treasure your friends, because they are your greatest fortune.")n 1291 | val index = when (birthday) { 1292 | in 1..7 -> 4 1293 | 28, 31 -> 2 1294 | else -> birthday.rem(fortunes.size) 1295 | } 1296 | return fortunes[index] 1297 | } 1298 | 1299 | // My Code 1300 | import kotlin.random.Random 1301 | fun main() { 1302 | for (i in 1..10) { 1303 | val birthday = getBirthday() 1304 | val fortune: Pair = getFortuneCookie(birthday) 1305 | println("Your fortune is: ${fortune.first}") 1306 | if (fortune.second == 5) { 1307 | break 1308 | } 1309 | } 1310 | } 1311 | 1312 | fun getFortuneCookie(birthday: Int): Pair { 1313 | val fortunes = listOf( 1314 | "You will have a great day!", 1315 | "Things will go well for you today.", 1316 | "Enjoy a wonderful day of success.", 1317 | "Be humble and all will turn out well.", 1318 | "Today is a good day for exercising restraint.", 1319 | "Take it easy and enjoy life!", 1320 | "Treasure your friends because they are your greatest fortune." 1321 | ) 1322 | val index = when (birthday) { 1323 | in 1..10 -> Random.nextInt(3) 1324 | 28, 34 -> Random.nextInt(3, 6) // Means if it is 28 or 34 1325 | else -> birthday.rem(fortunes.size) 1326 | } 1327 | return Pair(fortunes[index], index) 1328 | } 1329 | 1330 | fun getBirthday(): Int { 1331 | print("Enter your birthday: ") 1332 | return readLine()?.toIntOrNull() ?: 1 1333 | } 1334 | ``` 1335 | 1336 | #### Parameters 1337 | ```kotlin 1338 | // Parameters kotlin can have a default value, this means when you call a function, you don't have to specify a value every time for those parameters 1339 | // If the value is missing, the default value is used 1340 | fun main(args: Array) { 1341 | swim() 1342 | swim("Slow") // Specify the default argument positionally 1343 | swim(speed = "Slow") // Or Specify the argument by name 1344 | } 1345 | 1346 | fun swim(speed: String = "fast") { 1347 | println("swimming $speed") 1348 | } 1349 | 1350 | // You can mix default and positional arguments 1351 | fun main(args: Array) { 1352 | swim(5) 1353 | swim(5,"Slow") 1354 | swim(5,speed = "Slow") 1355 | swim(time = 5,speed = "Slow") 1356 | } 1357 | 1358 | fun swim(time: Int, speed: String = "fast") { 1359 | println("swimming $speed") 1360 | } 1361 | 1362 | // It is the best practice to put arguments without defaults first 1363 | // And then the noes with the defaults afterwards 1364 | shouldChangeWater(day, 20, 50) 1365 | shouldChangeWater(day) 1366 | shouldChangeWater(day, dirty = 50) 1367 | 1368 | // Wrong example 1369 | shouldChangeWaterWRONG("Monday") // Error! 1370 | // We have to specify that Monday is the day 1371 | shouldChangeWaterWRONG(day = "Monday") 1372 | 1373 | fun shouldChangeWater(day: String, temperature: Int = 22, dirty: Int = 20) { 1374 | } 1375 | 1376 | // You can define a function where the default variables are listed first or 1377 | // mixed in others, but this easily leads to mistakes 1378 | // If you forget to list all arguments by name 1379 | fun shouldChangeWaterWRONG(temperature: Int = 22, dirty: Int = 20, day: String) { 1380 | } 1381 | ``` 1382 | 1383 | ### Practice Time | Fit More Fish 1384 | ```kotlin 1385 | /*Create a function that checks if we can add another fish into a tank that already has fish in it. 1386 | 1387 | How many fish in a tank? 1388 | The most widely known rule for stocking a tank is the one-inch-per-fish-per-gallon-of-water rule. However that's assuming the tank doesn't have any decorations in it. 1389 | 1390 | Typically, a tank with decorations can contain a total length of fish (in inches) less than or equal to 80% of the tank size (in gallons). A tank without decorations can contain a total length of fish up to 100% of the tank size. 1391 | 1392 | For example: 1393 | A 10 gallon tank with decorations can hold up to 8 inches of fish, for example 4 x 2-inch-long fish. 1394 | A 20 gallon tank without decorations can hold up to 20 inches of fish, for example 6 x 1-inch-long fish and 2 x 2-inch-long fish. 1395 | fitMoreFish function 1396 | Create a function that takes these arguments: 1397 | 1398 | tankSize (in gallons) 1399 | currentFish (a list of Ints representing the length of each fish currently in the tank) 1400 | fishSize (the length of the new fish we want to add to the tank) 1401 | hasDecorations (true if the the tank has decorations, false if not) 1402 | You can assume that typically a tank has decorations, and that a typical fish is 2 inches long. That means you can set those values as default parameters. 1403 | 1404 | Output 1405 | Make sure you test your code against the following calls, and that you get the correct output for each. 1406 | 1407 | canAddFish(10.0, listOf(3,3,3)) ---> false 1408 | canAddFish(8.0, listOf(2,2,2), hasDecorations = false) ---> true 1409 | canAddFish(9.0, listOf(1,1,3), 3) ---> false 1410 | canAddFish(10.0, listOf(), 7, true) ---> true 1411 | 1412 | Things to think about 1413 | Again, there are so many ways you can do this, this is one of them: 1414 | 1415 | fun canAddFish(tankSize: Double, currentFish: List, fishSize: Int = 2, hasDecorations: Boolean = true): Boolean { 1416 | return (tankSize * if (hasDecorations) 0.8 else 1.0) >= (currentFish.sum() + fishSize) 1417 | } 1418 | 1419 | Notice how you can use the .sum() function in the list? This is a way to add up all elements in a list without having to use loops.*/ 1420 | 1421 | // Solution Code 1422 | fun main(args: Array) { 1423 | println(canAddFish(10, listOf(3,3,3))) // ---> false 1424 | println(canAddFish(8, listOf(2,2,2), hasDecorations = false)) // ---> true 1425 | println(canAddFish(9, listOf(1,1,3), 3)) // ---> false 1426 | println(canAddFish(10, listOf(), 7, true)) // ---> true 1427 | } 1428 | 1429 | // Alternative Shorter Solution 1430 | fun canAddFishAlternative(tankSize: Double, currentFish: List, fishSize: Int = 2, hasDecorations: Boolean = true): Boolean { 1431 | return (tankSize * if (hasDecorations) 0.8 else 1.0) >= (currentFish.sum() + fishSize) 1432 | } 1433 | 1434 | fun canAddFish(tankSize: Int, currentFish: List, fishSize: Int = 2, hasDecorations: Boolean = true): Boolean { 1435 | var availableTankSize = tankSize 1436 | // Without decorations, Total length of fish <= 100% of the tank size 1437 | if (!hasDecorations) { 1438 | availableTankSize -= currentFish.sum() 1439 | } 1440 | // With decorations, Total length of fish <= 80% of the tank size 1441 | else { 1442 | availableTankSize = availableTankSize.times(4).div(5) 1443 | availableTankSize -= currentFish.sum() 1444 | } 1445 | println("tankSize: $tankSize, sum: ${currentFish.sum()}, availableTankSize: $availableTankSize") 1446 | return fishSize <= availableTankSize 1447 | } 1448 | ``` 1449 | 1450 | ### Practice Time 1451 | ```kotlin 1452 | /*Create a program that suggests an activity based on various parameters. 1453 | 1454 | Start in a new file with a main function. 1455 | From main(), create a function, whatShouldIDoToday(). 1456 | Let the function have three parameters. 1457 | mood: a required string parameter 1458 | weather: a string parameter that defaults to "sunny" 1459 | temperature: an Integer parameter that defaults to 24 (Celsius). 1460 | Use a when construct to return some activities based on combinations of conditions. For example: 1461 | mood == "happy" && weather == "Sunny" -> "go for a walk" 1462 | else -> "Stay home and read." 1463 | Copy/paste your finished function into REPL, and call it with combinations of arguments. For example: 1464 | whatShouldIDoToday("sad") 1465 | > Stay home and read. 1466 | Note: Keep your work as you will do more with this code in the next practice.*/ 1467 | 1468 | fun main(args: Array) { 1469 | println(whatShouldIDoToday("happy")) 1470 | } 1471 | 1472 | fun whatShouldIDoToday(mood: String, weather: String = "sunny", temperature: Int = 24) : String { 1473 | return when { 1474 | mood == "happy" && weather == "Sunny" -> "go for a walk" 1475 | else -> "Stay home and read." 1476 | } 1477 | } 1478 | 1479 | // My Code 1480 | fun main(args: Array) { 1481 | println(whatShouldIDoToday("sad")) 1482 | } 1483 | 1484 | fun whatShouldIDoToday(mood: String, weather: String = "sunny", temperature: Int = 24): String { 1485 | return when { 1486 | mood == "happy" && weather == "Sunny" -> "go for a walk" 1487 | else -> "Stay home and read." 1488 | } 1489 | } 1490 | ``` 1491 | ```kotlin 1492 | // Return type can be inferred from the function 1493 | // The name of the function give a hint to the reader about the expected value 1494 | fun isTooHot(temperature: Int) = temperature > 30 1495 | fun isDirty(dirty: Int) = dirty > 30 1496 | fun isSunday(day: String): Boolean = day == "Sunday" 1497 | 1498 | fun shouldChangeWater(day: String, temperature: Int = 22, dirty: Int = 20): Boolean { 1499 | // Way 3 with one line function syntax 1500 | return when { 1501 | isTooHot(temperature) -> true 1502 | isDirty(dirty) -> true 1503 | isSunday(day) -> true 1504 | else -> false 1505 | } 1506 | // Way 2 1507 | // val isTooHot = temperature > 30 1508 | // val isDirty = dirty > 30 1509 | // val isSunday = day == "Sunday" 1510 | // return when { 1511 | // isTooHot -> true 1512 | // isDirty -> true 1513 | // isSunday -> true 1514 | // else -> false 1515 | // } 1516 | 1517 | // Way 1 1518 | // return when { 1519 | // temperature > 30 -> true 1520 | // dirty > 30 -> true 1521 | // day == "Sunday" -> true 1522 | // else -> false 1523 | // } 1524 | 1525 | // Sometimes you might be tempted to use expensive functions to initialize default parameter 1526 | // Examples of expensive operations include reading files or allocating a lot of memory 1527 | // BE CAREFUL WITH THIS, They can affect the performance of your code quite a bit 1528 | // Because Default parameters are evaluated at call time by Kotlin 1529 | 1530 | fun getDirtySensorReading() = 20 1531 | fun shouldChangeWater(dirty: Int = getDirtySensorReading()): Boolean { 1532 | // ... 1533 | } 1534 | 1535 | /////////////////////////////////////////////////////////////////// 1536 | fun main(args: Array) { 1537 | aquariumStatusReport() 1538 | aquariumStatusReport("sfg") 1539 | } 1540 | /* 1541 | * Every time you call aquariumStatusReport() without passing a value for the aquarium argument 1542 | * a new aquarium will be made which is costly 1543 | * */ 1544 | fun makeNewAquarium() = println("Building a new aquarium.....") 1545 | fun aquariumStatusReport(aquarium: Any = makeNewAquarium()) { 1546 | // Any can hold any type of object 1547 | } 1548 | /////////////////////////////////////////////////////////////////// 1549 | 1550 | // In kotlin, for and while loops are not expressions 1551 | val noValue = for (x in 1..2) {} // For is not an expression, and only expressions are allowed here! 1552 | val notThisEither = while (false) {} // For is not an expression, and only expressions are allowed here! 1553 | ``` 1554 | 1555 | ### Practice Time 1556 | ```kotlin 1557 | fun main(args: Array) { 1558 | println(whatShouldIDoToday("happy", "sunny")) 1559 | println(whatShouldIDoToday("sad")) 1560 | print("How do you feel?") 1561 | println(whatShouldIDoToday(readLine()!!)) 1562 | } 1563 | 1564 | fun isVeryHot (temperature: Int) = temperature > 35 1565 | 1566 | fun isSadRainyCold (mood: String, weather: String, temperature: Int) = 1567 | mood == "sad" && weather == "rainy" && temperature == 0 1568 | 1569 | fun isHappySunny (mood: String, weather: String) = mood == "happy" && weather == "sunny" 1570 | 1571 | fun whatShouldIDoToday(mood: String, weather: String = "sunny", temperature: Int = 24) : String { 1572 | return when { 1573 | isVeryHot(temperature) -> "go swimming" 1574 | isSadRainyCold(mood, weather, temperature) -> "stay in bed" 1575 | isHappySunny(mood, weather) -> "go for a walk" 1576 | else -> "Stay home and read." 1577 | } 1578 | } 1579 | 1580 | // My Code 1581 | fun main() { 1582 | print("Enter mood: ") 1583 | val mood = readLine().orEmpty() // OR -> readLine() ?: "" 1584 | // Double Bang operator does the following line; 1585 | // if (readLine() != null) readLine() else throw NullPointerException("Expression 'readLine()' must not be null") 1586 | println(whatShouldIDoToday(mood)) 1587 | } 1588 | 1589 | fun shouldWalk(mood: String, weather: String) = mood == "happy" && weather == "sunny" 1590 | fun shouldSleep(mood: String, weather: String, temperature: Int) = mood == "sad" && weather == "rainy" && temperature == 0 1591 | fun shouldSwim(mood: String, weather: String, temperature: Int) = temperature > 35 1592 | 1593 | fun whatShouldIDoToday(mood: String, weather: String = "sunny", temperature: Int = 24): String { 1594 | return when { 1595 | shouldWalk(mood, weather) -> "go for a walk" 1596 | shouldSleep(mood, weather, temperature) -> "stay in bed" 1597 | shouldSwim(mood, weather, temperature) -> "go swimming" 1598 | else -> "Stay home and read." 1599 | } 1600 | } 1601 | ``` 1602 | 1603 | ### Repeat and While 1604 | ```kotlin 1605 | // Using repeat: 1606 | fun main(args: Array) { 1607 | var fortune: String = "" 1608 | repeat (10) { 1609 | fortune = getFortune(getBirthday()) 1610 | println("\nYour fortune is: $fortune") 1611 | if (fortune.contains("Take it easy")) break; 1612 | } 1613 | } 1614 | 1615 | // Using a while loop: 1616 | fun main(args: Array) { 1617 | var fortune: String = "" 1618 | while (!fortune.contains("Take it easy")) { 1619 | fortune = getFortune(getBirthday()) 1620 | println("\nYour fortune is: $fortune") 1621 | } 1622 | } 1623 | ``` 1624 | 1625 | 1626 | ### Filters 1627 | ```kotlin 1628 | val list = listOf("abc", "ghf", "aaa", "tur") 1629 | println(list.filter { it[0] == 'a' }) // Outputs: [abc, aaa] 1630 | // Returns the elements that satisfy the condition it[0] == 'a' 1631 | // In Kotlin, 'c' -> characters, "string" -> string 1632 | // Strings and chars are not interchangebla// They are different things! 1633 | // For example list.filter { it[0] == "a" } // ERROR// because we do char == string which is not valid 1634 | 1635 | // Filter is a standard library function on list in kotlin 1636 | 1637 | // The difference between EAGER and LAZY 1638 | // -> AN EAGER algorithm executes immediately and returns a result. 1639 | // -> A LAZY algorithm defers computation until it is necessary to execute and then produces a result. 1640 | 1641 | // By default, filter analyst is EAGER that means everytime you call filter, it creates a new list with he elements that pass through the filter 1642 | 1643 | // EAGER example 1644 | fun main() { 1645 | val list = listOf("rock", "pagoda", "plastic", "tur") 1646 | // Decoration EAGER here// We'll hold a new list 1647 | // containing strings that starts with 'p' 1648 | val decorations = list.filter { it[0] == 'p' } 1649 | println(decorations) 1650 | } 1651 | 1652 | // If you want LAZY Behaviour, you can use SEQUENCES// A sequence is a collection that only look at one item at a time starting at the beginning and going to the end, conveniently, this is exactly the API filter needs 1653 | 1654 | // When you return the filter results as a sequence, our filtered variable won't hold a new list, it will hold a sequence of all of the list elements and knowledge of the filter to apply to its elements 1655 | 1656 | // Ehenever you access elements of the sequence, the filter is applied and the results are returned to you 1657 | 1658 | // Of course, if we want to turn our sequence back into the list, we can call "toList()" at that point, a filter will be run and all of the values that start with P will be put in the new list 1659 | fun main() { 1660 | val list = listOf("rock", "pagoda", "plastic", "tur") 1661 | // Apply filter LAZILY 1662 | val decorations = list.asSequence().filter { it[0] == 'p' } 1663 | println(decorations) // kotlin.sequences.FilteringSequence@1fb3ebeb 1664 | println(decorations.toList()) // Ignite the filter!: [pagoda, plastic] 1665 | } 1666 | 1667 | // Let's use the function map and tell it to print every item, since it's lazy, calling map does not print anything 1668 | 1669 | // Let's use the function map and tell it to print every item 1670 | val lazyMap = decorations.asSequence().map { 1671 | println("map $it") 1672 | it 1673 | } 1674 | println(lazyMap) // kotlin.sequences.TransformingSequence@53d8d10a 1675 | 1676 | // When I take the first element however, you can see that the map operation reads the first value 1677 | fun main() { 1678 | val list = listOf("rock", "pagoda", "plastic", "tur") 1679 | // Apply filter LAZILY 1680 | val decorations = list.asSequence().filter { it[0] == 'p' } 1681 | // Let's use the function map and tell it to print every item 1682 | val lazyMap = decorations.asSequence().map { 1683 | println("map $it") // map pagoda 1684 | it 1685 | } 1686 | println(lazyMap) // kotlin.sequences.TransformingSequence@53d8d10a 1687 | println("first: ${lazyMap.first()}") // first: pagoda 1688 | } 1689 | 1690 | // Of course taking the full list will iterate over all the values 1691 | fun main() { 1692 | val list = listOf("rock", "pagoda", "plastic", "tur") 1693 | // Apply filter LAZILY 1694 | val decorations = list.asSequence().filter { it[0] == 'p' } 1695 | // Let's use the function map and tell it to print every item 1696 | val lazyMap = decorations.asSequence().map { 1697 | println("map $it") 1698 | it 1699 | } 1700 | println("all: ${lazyMap.toList()}") 1701 | } 1702 | 1703 | // map pagoda 1704 | // map plastic 1705 | // all: [pagoda, plastic] 1706 | ``` 1707 | 1708 | 1709 | ### Practice Time 1710 | ```kotlin 1711 | /*You can do the following filter exercise in REPL. 1712 | 1713 | Create a list of spices, as follows: 1714 | val spices = listOf("curry", "pepper", "cayenne", "ginger", "red curry", "green curry", "red pepper" ) 1715 | 1716 | Create a filter that gets all the curries and sorts them by string length. 1717 | Hint: After you type the dot (.), IntelliJ will give you a list of functions you can apply. 1718 | 1719 | Filter the list of spices to return all the spices that start with 'c' and end in 'e'. Do it in two different ways. 1720 | 1721 | Take the first three elements of the list and return the ones that start with 'c'. 1722 | 1723 | Note: We will be able to do a lot more interesting stuff with filters after you learn about classes and Map.*/ 1724 | 1725 | fun main() { 1726 | val spices = listOf("curry", "pepper", "cayenne", "ginger", "red curry", "green curry", "red pepper") 1727 | // 2 1728 | println(spices.filter { s -> s.contains("curry") }.sortedBy { s -> s.length }) 1729 | // 3 1730 | println(spices.filter { s -> s.startsWith('c') && s.endsWith('e') }) 1731 | println(spices.filter { s -> s.first() == 'c' && s.last() == 'e' }) 1732 | println(spices.filter { s -> s[0] == 'c' && s[s.length - 1] == 'e' }) 1733 | // 4 1734 | println(spices.take(3).filter { s -> s.first() == 'c' }) 1735 | 1736 | // OR WE CAN USE "it" 1737 | val spices = listOf("curry", "pepper", "cayenne", "ginger", "red curry", "green curry", "red pepper") 1738 | // 2 1739 | println(spices.filter { it.contains("curry") }.sortedBy { it.length }) 1740 | // 3 1741 | println(spices.filter { it.startsWith('c') && it.endsWith('e') }) 1742 | println(spices.filter { it.first() == 'c' && it.last() == 'e' }) 1743 | println(spices.filter { it[0] == 'c' && it[it.length - 1] == 'e' }) 1744 | // 4 1745 | println(spices.take(3).filter { it.first() == 'c' }) 1746 | } 1747 | 1748 | // Solution Code 1749 | // Sorting curries by string length 1750 | spices.filter { it.contains("curry") }.sortedBy { it.length } 1751 | 1752 | // Filtering by those that start with 'c' and end with 'e' 1753 | spices.filter{it.startsWith('c')}.filter{it.endsWith('e')} 1754 | > [cayenne] 1755 | // OR 1756 | spices.filter { {it.startsWith('c') && it.endsWith('e') } 1757 | > [cayenne] 1758 | 1759 | // Filtering the first 3 items by 'c' 1760 | spices.take(3).filter{it.startsWith('c')} 1761 | > [curry, cayenne] 1762 | ``` 1763 | 1764 | ### Kotlin Labmdas 1765 | ```kotlin 1766 | // Lambda functions are used when you need a function FOR A SHORT PERIOD OF TIME. 1767 | // A LAMBDA is an expression that makes a function, instead of declaring a named function, we declare a function that has no name 1768 | 1769 | fun main() { 1770 | // Lambda function 1771 | { println("Hello") }() 1772 | } 1773 | 1774 | // We can declera a variable called swim and assign it to a lambda 1775 | // Lambda function, If we put "()" this runs/calls the lambda function 1776 | { println("Hello") }() // Hello 1777 | 1778 | // We can also say: run { println("Hello") } // Hello 1779 | // We can declera a variable called swim and assign it to a lambda 1780 | var swimDontRun = { println("swim") } // swim 1781 | var swimRunDirectly = { println("swim") }() // swim 1782 | var swimRunDirectly2 = run { println("swim") } // swim 1783 | 1784 | // We can call variable just like a regular function 1785 | swimDontRun() // Output: swim 1786 | 1787 | // Lambdas can take arguments just like named functions 1788 | // Lambda arguments go on the left hand side of what's called a function arrow 1789 | // The body of the lambda goes after the function arrow 1790 | fun main() { 1791 | var dirty = 20 1792 | val waterFilter = { dirty: Int -> dirty / 2 } 1793 | println(waterFilter(dirty)) // 10 1794 | // waterFilter can be any function that takes an int and returns an int 1795 | val waterFilter2: (Int) -> Int = { abc: Int -> abc + 2 } 1796 | // We don't have to specify the type of the lambda argument anymore 1797 | val waterFilter3: (Int) -> Int = { abc -> abc + 2 } 1798 | } 1799 | ``` 1800 | 1801 | ### Higher-Order Functions 1802 | ```kotlin 1803 | // The real power of lambda happens when we make higher-order functions 1804 | // A higher-order function is just any function that takes a function as the argument 1805 | // Kotlin prefers function arguments to be the last parameter 1806 | // Higher-order function that takes function as an argument 1807 | fun updateDirty(dirty: Int, operation: (Int) -> Int): Int { 1808 | return operation(dirty) 1809 | } 1810 | 1811 | /* 1812 | * When you combine higher-order functions with lambdas 1813 | * Kotlin has a special syntax 1814 | * it's called the last parameter called syntax 1815 | * */ 1816 | fun dirtyProcessor() { 1817 | dirty = updateDirty(dirty, waterFilter) 1818 | println("1: $dirty") 1819 | // since feedFish is a named function and not a lambda 1820 | // you'll need to use a double colon to pass it 1821 | // This way Kotlin know you're not trying to call feedFish 1822 | // and it will let you pass a REFERENCE 1823 | // So here we don't call the function but we pass it to another function and then 1824 | // that function will run the function passed it to 1825 | dirty = updateDirty(dirty, ::feedFish) 1826 | // " :: " means, it creates a member reference or a class reference. 1827 | println("2: $dirty") 1828 | // Above method is similar as the following 1829 | dirty = feedFish(dirty) 1830 | println("22: $dirty") 1831 | // Here we call updateDirty again, but this time 1832 | // we pass a lambda as an argument for the parameter operation 1833 | /* 1834 | * What's really interesting here, a lambda is an argument to updateDirty 1835 | * but since we're passing it as the last parameter 1836 | * we don't have to put it inside the function parentheses 1837 | * */ 1838 | dirty = updateDirty(dirty) { dirty -> 1839 | dirty + 50 1840 | } 1841 | /* 1842 | * To really show you what is going on, 1843 | * you can put the parentheses back in, here you can see we're just 1844 | * passing the lambda as an argument updateDirty 1845 | * */ 1846 | dirty = updateDirty(dirty, { dirty -> 1847 | dirty + 50 1848 | }) 1849 | /* 1850 | * Using this syntax we can define functions that look like they're built-in to the language 1851 | * Actually, we've already used a few higher-order functions from the standard library 1852 | * */ 1853 | val list = listOf(1, 2, 3) 1854 | list.filter { 1855 | it == 2 1856 | } 1857 | /* 1858 | * The filter function we used in the last section, takes a lambda and 1859 | * uses it to filter a list, 1860 | * repeat is also just a function that takes a repeat count and a lambda that is repeated 1861 | * */ 1862 | } 1863 | ``` 1864 | 1865 | ### Practice Time 1866 | ```kotlin 1867 | // What is the difference between? 1868 | val random1 = random() 1869 | val random2 = { random() } 1870 | // Try it out in REPL or a file: 1871 | > The second will generate a random number every time random2 is accessed. 1872 | // ANSWER 1873 | // random1 has a value assigned at compile time, and the value never changes when the variable is accessed. 1874 | // random2 has a lambda assigned at compile time, and the lambda is executed every time the variable is referenced, returning a different value. 1875 | ``` 1876 | 1877 | ### Practice Time | Lambdas 1878 | ```kotlin 1879 | // Create a lambda and assign it to rollDice, which returns a dice roll (number between 1 and 12). 1880 | // Extend the lambda to take an argument indicating the number of sides of the dice used for the roll. 1881 | // If you haven't done so, fix the lambda to return 0 if the number of sides passed in is 0. 1882 | // Create a new variable, rollDice2, for this same lambda using the function type notation. 1883 | 1884 | // Solution Code 1885 | val rollDice = { Random().nextInt(12) + 1} 1886 | val rollDice = { sides: Int -> 1887 | Random().nextInt(sides) + 1 1888 | } 1889 | val rollDice0 = { sides: Int -> 1890 | if (sides == 0) 0 1891 | else Random().nextInt(sides) + 1 1892 | } 1893 | val rollDice2: (Int) -> Int = { sides -> 1894 | if (sides == 0) 0 1895 | else Random().nextInt(sides) + 1 1896 | } 1897 | 1898 | // My Code 1899 | import kotlin.random.Random 1900 | fun main() { 1901 | val rollDice6Sides = { Random.nextInt(12) + 1 } 1902 | val rollDice = { sides: Int -> 1903 | if (sides == 0) 0 1904 | else Random.nextInt(sides) + 1 1905 | } 1906 | repeat(10) { 1907 | println("${rollDice(0)}") 1908 | } 1909 | val rollDice2: (Int) -> Int = { sides: Int -> 1910 | if (sides == 0) 0 1911 | else Random.nextInt(sides) + 1 1912 | } 1913 | } 1914 | ``` 1915 | 1916 | ### Practice Time | Extra Questions 1917 | ```kotlin 1918 | // Why would you want to use the function type notation instead of just the lambda? 1919 | 1920 | // Create a function gamePlay() that takes a roll of the dice as an argument and prints it out. 1921 | 1922 | // Pass your rollDice2 function as an argument to gamePlay() to generate a dice roll every time gamePlay() is called. 1923 | 1924 | // Solution Explanation 1925 | // Function type notation is MORE READABLE, which REDUCES ERRORS, clearly showing the what type is passed in and what type is returned. 1926 | 1927 | // Solution Code 1928 | gamePlay(rollDice2(4)) 1929 | fun gamePlay(diceRoll: Int){ 1930 | // do something with the dice roll 1931 | println(diceRoll) 1932 | } 1933 | 1934 | // My Code 1935 | import kotlin.random.Random 1936 | fun main() { 1937 | // Why would you want to use the function type notation instead of just the lambda? 1938 | // -> We might want to know what type we pass in the function 1939 | // this will reduce errors related to parameters 1940 | val dice = { sides: Int -> 1941 | Random.nextInt(sides) + 1 1942 | } 1943 | val rollDice2: (Int) -> Int = { sides: Int -> 1944 | if (sides == 0) 0 1945 | else Random.nextInt(sides) + 1 1946 | } 1947 | gamePlay(dice, rollDice2) 1948 | } 1949 | 1950 | fun gamePlay(dice: (Int) -> Int, dice2: (Int) -> Int) { 1951 | println(dice2(6)) 1952 | println(dice(6)) 1953 | } 1954 | ``` 1955 | 1956 |

Lesson 4 | Classes

1957 | 1958 | ```kotlin 1959 | /* 1960 | Classes are blue prints for objects 1961 | 1962 | -> Class - Object Blueprint 1963 | (like an Aquarium Plan) 1964 | 1965 | -> Objects are instances of classes that is the actual aquarium 1966 | (Actual Aquarium) 1967 | 1968 | -> Properties are characteristics of classes such as the length 1969 | (Aquarium width, height) 1970 | 1971 | -> Methods are the functionality of the class, class function, what the object could do, for example fill with water 1972 | ( fillWithWater() ) 1973 | 1974 | -> Interfaces are a specification that a class can implement 1975 | (Specification a class can implement ( Clean )), for example, cleaning is common not just to aquariums and cleaning generally happens in similar ways, So we can have an interface clean and aquarium could implement it 1976 | */ 1977 | ``` 1978 | 1979 | ### Practice Time 1980 | ```kotlin 1981 | /*Earlier, we created and filtered a list of spices. Spices are much better represented as objects than as simple strings. Because they are objects, we can perform different things with them - such as cooking. 1982 | 1983 | To recap, let's make a simple Spice class. It doesn't do much, but it will serve as the starting point for the next practice. 1984 | 1985 | Create class, SimpleSpice. 1986 | Let the class be a property with a String for the name of the spice, and a String for the level of spiciness. 1987 | Set the name to curry and the spiciness to mild. 1988 | Using a string for spiciness is nice for users, but not useful for calculations. Add a heat property to your class with a getter that returns a numeric value for spiciness. Use a value of 5 for mild. 1989 | Create an instance of SimpleSpice and print out its name and heat.*/ 1990 | 1991 | // My Code 1992 | class Spice { 1993 | var name: String = "curry" 1994 | var spiciness: String = "mild" 1995 | fun heat(): Int { 1996 | return when (spiciness) { 1997 | "mild" -> 5 1998 | else -> 6 1999 | } 2000 | } 2001 | } 2002 | 2003 | fun main() { 2004 | // Create spice class instance 2005 | val mySpice = Spice() 2006 | println("name: ${mySpice.name}, heat: ${mySpice.heat()}") 2007 | } 2008 | 2009 | 2010 | // Solution Code 2011 | class SimpleSpice() { 2012 | val name = "curry" 2013 | val spiciness = "mild" 2014 | val heat: Int 2015 | get() {return 5 } 2016 | } 2017 | // In main 2018 | val simpleSpice = SimpleSpice() 2019 | println("${simpleSpice.name} ${simpleSpice.heat}") 2020 | ``` 2021 | 2022 | ### Package Visibility 2023 | ```kotlin 2024 | // In kotlin everything is public by default, that means all of your variables and classes can be accessed everywhere, even the member variables of an object 2025 | 2026 | // Visibility modifiers in Kotlin 2027 | -> public - Default. Everywhere 2028 | -> private - File 2029 | -> internal - Module 2030 | 2031 | // At the packege level, if you don't specify any visibility modifier, 2032 | // " public " is used by default 2033 | // Which means that your devlarations will be visible everywhere 2034 | // A module is a set of Kotlin files compiled together, when it's internal we can use it from anywhere inside our project 2035 | 2036 | // For members declared inside the class, again by default they are public 2037 | ``` 2038 | 2039 | ### Class Visibility 2040 | ```kotlin 2041 | // Public means that any client who sees the class can also see it's public members 2042 | // Private means members are only visible inside the class, importantly subclasses can't see private members 2043 | // Protected means the same as private but members are also visible to subclasses 2044 | // Class members can have a visibility of internal as well 2045 | ``` 2046 | 2047 | ### Class Examples 2048 | ```kotlin 2049 | // Sample Code 2050 | // FILE: Main 2051 | package Aquarium 2052 | fun main() { 2053 | // Create spice class instance 2054 | // val mySpice = Spice() 2055 | // println("name: ${mySpice.name}, heat: ${mySpice.heat}") 2056 | buildAquarium() 2057 | } 2058 | 2059 | // If you mark a declaration private, 2060 | // it will only be visible the inside the file containing declaration 2061 | // Since we're only going to use buildAquarium inside this file 2062 | // We can make it private 2063 | // If you mark buildAquarium " internal " it is visible anywhere in the same module 2064 | private fun buildAquarium() { 2065 | // Creates new instance of Aquarium by calling its constructor 2066 | val myAquarium = Aquarium() 2067 | // Under the hood, Kotlin actually made a getter for all three properties 2068 | // Even though we did not write any code 2069 | println("Length: ${myAquarium.length}" + 2070 | " Width: ${myAquarium.width}" + 2071 | " Height: ${myAquarium.height}") 2072 | 2073 | // We don't have to chance " myAquarium " to a var because, we're not changing the aquarium 2074 | // It's the same object we're modifying its properties 2075 | myAquarium.height = 80 2076 | println("New Height: ${myAquarium.height} cm") 2077 | println("Volume: ${myAquarium.volume} liter") 2078 | } 2079 | 2080 | // FILE: Aquarium 2081 | package Aquarium 2082 | class Aquarium { 2083 | var length = 100 2084 | var width = 20 2085 | var height = 40 2086 | 2087 | // Sample Getter/Setter Syntax 2088 | var volume: Int 2089 | get() { 2090 | return width * height * length / 1000 2091 | } 2092 | set(value) { 2093 | height = (value * 1000) / (width * length) 2094 | } 2095 | 2096 | // Alternative one liner Getter/Setter Syntax 2097 | var volume2: Int 2098 | get() = width * height * length / 1000 2099 | // By convention, the name of the setter parameter is " value " 2100 | private set(value) { height = (value * 1000) / (width * length) } 2101 | // If we didn't want anyone outside the class to be able to use 2102 | // the setter, we could make it private 2103 | // private set(value) { height = (value * 1000) / (width * length) } 2104 | // In kotlin everything is public by default 2105 | 2106 | // fun volume(): Int { 2107 | // return width * height * length / 1000 2108 | // } 2109 | // 2110 | // // Alternative, one liner 2111 | // fun volume1() = width * height * length / 1000 2112 | } 2113 | 2114 | // Sample Code For Classes 2115 | package Aquarium 2116 | 2117 | // Constructor 2118 | class Test(id: Int, name: String, val testVal: String) { 2119 | /* 2120 | * The primary constructor cannot contain any code. 2121 | * Initialization code can be placed in initializer blocks, 2122 | * which are prefixed with the init keyword. 2123 | * */ 2124 | // As the name says, 2125 | // "also" expressions does some additional processing on the object it was invoked. 2126 | // Unlike let, it returns the original object instead of any new return data. 2127 | var firstProperty = "First property: $name".also(::println) 2128 | // Alternative of " also " 2129 | // val b = "SDads: $name".also { println(it) } 2130 | 2131 | init { 2132 | // .... 2133 | println("First this block will be executed") 2134 | } 2135 | 2136 | val secondProperty = "Second property: ${name.length}".also(::println) 2137 | init { 2138 | println("Second initializer block that prints ${name.length}") 2139 | } 2140 | } 2141 | 2142 | // The class can also declare secondary constructors, which are prefixed with constructor 2143 | class Person(val pets: MutableList = mutableListOf()) 2144 | 2145 | class Pet { 2146 | // Secondary constructor 2147 | constructor(owner: Person) { 2148 | // Add this pet to he list of its owner's 2149 | owner.pets.add(this) 2150 | } 2151 | } 2152 | 2153 | /* 2154 | * A class in Kotlin can have a primary constructor and one or more secondary constructors. 2155 | * The primary constructor is part of the class header: 2156 | * it goes after the class name (and optional type parameters). 2157 | * */ 2158 | class Person constructor(firstName: String) { 2159 | 2160 | } 2161 | 2162 | // If the primary constructor does not have any annotations or visibility modifiers, 2163 | // the constructor keyword can be omitted: 2164 | class Person2 (firstName: String) { 2165 | 2166 | } 2167 | 2168 | fun main() { 2169 | val test = Test(24, "Melo") 2170 | println("Age: ${test.firstProperty}, Name: ${test.secondProperty}") 2171 | test.firstProperty = "123" 2172 | println("${test.firstProperty}") 2173 | test.testVal 2174 | } 2175 | 2176 | // Kotlin has a concise syntax for declaring properties and initializing them from the primary constructor: 2177 | class Person(val firstName: String, val lastName: String, var age: Int) 2178 | 2179 | // Such declarations can also include default values of the class properties: 2180 | class Person(val firstName: String, val lastName: String, var isEmployed: Boolean = true) 2181 | 2182 | // You can use a trailing comma when you declare class properties: 2183 | 2184 | class Person( 2185 | val firstName: String, 2186 | val lastName: String, 2187 | var age: Int, // trailing comma 2188 | ) { /*...*/ } 2189 | 2190 | /* 2191 | VISIBILITY 2192 | 2193 | PACKAGE: 2194 | public - default. Everywhere 2195 | private - file 2196 | internal - module 2197 | 2198 | CLASS: 2199 | sealed - only subclass in same file 2200 | 2201 | INSIDE CLASS: 2202 | public - default. Everywhere. 2203 | private - inside class, not subclasses 2204 | protected - inside class and subclasses 2205 | internal - module 2206 | */ 2207 | ``` 2208 | 2209 | ### Practice Time 2210 | ```kotlin 2211 | ``` 2212 | 2213 | ### Sample Code 2214 | ```kotlin 2215 | package Aquarium 2216 | 2217 | // Constructor 2218 | class Test(id: Int, name: String) { 2219 | /* 2220 | * The primary constructor cannot contain any code. 2221 | * Initialization code can be placed in initializer blocks, 2222 | * which are prefixed with the init keyword. 2223 | * */ 2224 | // As the name says, 2225 | // "also" expressions does some additional processing on the object it was invoked. 2226 | // Unlike let, it returns the original object instead of any new return data. 2227 | var firstProperty = "First property: $name".also(::println) 2228 | // Alternative of " also " 2229 | // val b = "SDads: $name".also { println(it) } 2230 | 2231 | init { 2232 | // .... 2233 | println("First this block will be executed") 2234 | } 2235 | 2236 | val secondProperty = "Second property: ${name.length}".also(::println) 2237 | 2238 | init { 2239 | println("Second initializer block that prints ${name.length}") 2240 | } 2241 | 2242 | /* 2243 | * Accessing the Backing Field 2244 | * Every property we define is backed by a field 2245 | * that can only be accessed within its get() and set() methods 2246 | * using the special field keyword. 2247 | * The field keyword is used to access or modify the property’s value. 2248 | * This allows us to define custom logic within the get() and set() methods 2249 | * */ 2250 | var rating: Int = 5 2251 | get() { 2252 | if (field < 5) { 2253 | println("Warning This is a Terrible Book!") 2254 | } 2255 | return field 2256 | } 2257 | set(value) { 2258 | field = when { 2259 | value > 10 -> 10 2260 | value < 0 -> 0 2261 | else -> value 2262 | } 2263 | } 2264 | 2265 | // Getters and setters are auto-generated in Kotlin. 2266 | // In Kotlin, a property doesn’t require explicit getter or setter methods: 2267 | var author: String = "Frank Herbert" 2268 | // Redundant getter ! 2269 | get() { 2270 | return field 2271 | } 2272 | // Redundant setter ! 2273 | set(value) { 2274 | field = value 2275 | } 2276 | /* 2277 | * Defining a custom getter or setter allows us 2278 | * to perform any number of useful operations like input validation, 2279 | * logging, or data transformations. 2280 | * By adding this business logic directly to the getter or setter, 2281 | * we ensure that it’s always performed when the property is accessed. 2282 | * Try to avoid or minimize side-effects 2283 | * in the getter and setter methods as much as possible. 2284 | * It makes our code harder to understand. 2285 | * */ 2286 | 2287 | /* 2288 | * If we want to be able to modify a property’s value, 2289 | *we mark it with the var keyword. 2290 | * If we want an immutable property, we mark it with a val keyword. 2291 | * The main difference is that val properties can’t have setters. 2292 | * */ 2293 | val isWorthReading: Boolean get() = this.rating > 5 2294 | // set(value) { // A 'val'-property cannot have a setter! 2295 | // // ERROR 2296 | // } 2297 | // In this sense, the property acts as a method when using a custom getter. 2298 | 2299 | /* 2300 | * Now any consumers of the book class can read the inventory property 2301 | * but only the Book class can modify it. 2302 | * */ 2303 | var inventory: Int = 0 2304 | private set 2305 | /* 2306 | * Note that the default visibility for properties is public. 2307 | * The getter will always have the same visibility as the property itself. 2308 | * For example, if the property is private, the getter is private. 2309 | * */ 2310 | 2311 | // Backing Fields 2312 | /* 2313 | In Kotlin, a field is only used as a part of a property 2314 | to hold its value in memory. Fields can not be declared directly. 2315 | However, when a property needs a backing field, Kotlin provides it automatically. 2316 | This backing field can be referenced in the accessors using the "field" identifier: 2317 | * */ 2318 | var counter = 0 // the initializer assigns the backing field directly 2319 | set(value) { 2320 | if (value >= 0) 2321 | field = value 2322 | // counter = value // ERROR StackOverflow: Using actual name 'counter' would make setter recursive 2323 | } 2324 | 2325 | // For example, in the following case there will be no backing field: 2326 | // val isEmpty: Boolean 2327 | // get() = this.size == 0 2328 | } 2329 | 2330 | /* 2331 | * A class in Kotlin can have a primary constructor and one or more secondary constructors. 2332 | * The primary constructor is part of the class header: 2333 | * it goes after the class name (and optional type parameters). 2334 | * */ 2335 | class PersonTest constructor(firstName: String) { 2336 | 2337 | } 2338 | 2339 | // If the primary constructor does not have any annotations or visibility modifiers, 2340 | // the constructor keyword can be omitted: 2341 | class Person2 (firstName: String) { 2342 | 2343 | } 2344 | 2345 | // The class can also declare secondary constructors, which are prefixed with constructor 2346 | class Person(val pets: MutableList = mutableListOf()) 2347 | 2348 | class Pet { 2349 | // Secondary constructor 2350 | constructor(owner: Person) { 2351 | // Add this pet to he list of its owner's 2352 | owner.pets.add(this) 2353 | } 2354 | } 2355 | 2356 | fun main() { 2357 | val test = Test(24, "Melo") 2358 | println("Age: ${test.firstProperty}, Name: ${test.secondProperty}") 2359 | test.firstProperty = "123" 2360 | println("First Author: ${test.author}") 2361 | test.author = "Melo Genesis" 2362 | println("Second Author: ${test.author}") 2363 | } 2364 | ``` 2365 | 2366 | ### Practice Time 2367 | ```kotlin 2368 | /*Earlier, we created and filtered a list of spices. Spices are much better represented as objects than as simple strings. Because they are objects, we can perform different things with them - such as cooking. 2369 | 2370 | To recap, let's make a simple Spice class. It doesn't do much, but it will serve as the starting point for the next practice. 2371 | 2372 | Create class, SimpleSpice. 2373 | Let the class be a property with a String for the name of the spice, and a String for the level of spiciness. 2374 | Set the name to curry and the spiciness to mild. 2375 | Using a string for spiciness is nice for users, but not useful for calculations. Add a heat property to your class with a getter that returns a numeric value for spiciness. Use a value of 5 for mild. 2376 | Create an instance of SimpleSpice and print out its name and heat.*/ 2377 | 2378 | class SimpleSpice(){ 2379 | val name = "curry" 2380 | val spiciness = "mild" 2381 | val heat: Int 2382 | get() {return 5 } 2383 | } 2384 | val simpleSpice = SimpleSpice() 2385 | println("${simpleSpice.name} ${simpleSpice.heat}") 2386 | ``` 2387 | 2388 | ### Sample Codes Continued 2389 | ```kotlin 2390 | /////////////////////////////// Sample Code /////////////////////////////////////// 2391 | package Aquarium 2392 | class Aquarium { 2393 | var length = 100 2394 | var width = 20 2395 | var height = 40 2396 | 2397 | // Sample Getter/Setter Syntax 2398 | var volume: Int 2399 | get() { 2400 | return width * height * length / 1000 2401 | } 2402 | set(value) { 2403 | height = (value * 1000) / (width * length) 2404 | } 2405 | 2406 | // Alternative one liner Getter/Setter Syntax 2407 | var volume2: Int 2408 | get() = width * height * length / 1000 2409 | // By convention, the name of the setter parameter is " value " 2410 | private set(value) { height = (value * 1000) / (width * length) } 2411 | // If we didn't want anyone outside the class to be able to use 2412 | // the setter, we could make it private 2413 | // private set(value) { height = (value * 1000) / (width * length) } 2414 | // In kotlin everything is public by default 2415 | 2416 | // fun volume(): Int { 2417 | // return width * height * length / 1000 2418 | // } 2419 | // 2420 | // // Alternative, one liner 2421 | // fun volume1() = width * height * length / 1000 2422 | } 2423 | 2424 | /////////////////////////////////////////////////////////////////////////////////// 2425 | 2426 | // With default parameters constructor overloading is not needed 2427 | 2428 | //////////////////////////////////////////////////////////////////////////////////////// 2429 | // Solution Code 2430 | class Spice(val name: String, val spiciness: String = "mild") { 2431 | 2432 | private val heat: Int 2433 | get() { 2434 | return when (spiciness) { 2435 | "mild" -> 1 2436 | "medium" -> 3 2437 | "spicy" -> 5 2438 | "very spicy" -> 7 2439 | "extremely spicy" -> 10 2440 | else -> 0 2441 | } 2442 | } 2443 | } 2444 | 2445 | val spices1 = listOf( 2446 | Spice("curry", "mild"), 2447 | Spice("pepper", "medium"), 2448 | Spice("cayenne", "spicy"), 2449 | Spice("ginger", "mild"), 2450 | Spice("red curry", "medium"), 2451 | Spice("green curry", "mild"), 2452 | Spice("hot pepper", "extremely spicy") 2453 | ) 2454 | 2455 | val spice = Spice("cayenne", spiciness = "spicy") 2456 | 2457 | val spicelist = spices1.filter {it.heat < 5} 2458 | 2459 | fun makeSalt() = Spice("Salt") 2460 | /////////////////////////////////////////////////////////////////////////////////// 2461 | 2462 | // Kotlin does not have a new keyword 2463 | 2464 | /////////////////////////////////////////////////////////////////////////////////// 2465 | // My Code 2466 | package Aquarium 2467 | class Spice(val name: String, val spiciness: String = "mild") { 2468 | val heat: Int 2469 | get() { 2470 | return when (spiciness) { 2471 | "mild" -> 1 2472 | "medium" -> 3 2473 | "spicy" -> 5 2474 | "very spicy" -> 7 2475 | "extremely spicy" -> 10 2476 | else -> 0 2477 | } 2478 | } 2479 | init { 2480 | println("Name. $name, Spiciness: $spiciness, Heat: $heat") 2481 | } 2482 | } 2483 | 2484 | fun makeSalt(): Spice { 2485 | return Spice("Salt") 2486 | } 2487 | 2488 | fun main() { 2489 | val spices = listOf( 2490 | Spice("curry", "mild"), 2491 | Spice("pepper", "medium"), 2492 | Spice("cayenne", "spicy"), 2493 | Spice("ginger", "mild"), 2494 | Spice("red curry", "medium"), 2495 | Spice("green curry", "mild"), 2496 | Spice("hot pepper", "extremely spicy") 2497 | ) 2498 | val spice = spices.filter { 2499 | it.heat < 5 2500 | } 2501 | fun makeSalt() = Spice("Salt") 2502 | } 2503 | /////////////////////////////////////////////////////////////////////////////////// 2504 | 2505 | /////////////////////////////////////////////////////////////////////////////////// 2506 | package Aquarium 2507 | class Aquarium (var length: Int = 100, var width: Int = 20, var height: Int = 40) { 2508 | var volume: Int 2509 | get() = width * height * length / 1000 2510 | // By convention, the name of the setter parameter is " value " 2511 | private set(value) { height = (value * 1000) / (width * length) } 2512 | 2513 | // The inferred data type is double 2514 | var water = volume * 0.9 2515 | 2516 | // If we need to have another constructor than the default one for our class, we can create secondary constructor 2517 | // For example, instead of specifying the dimensions when we create the aquarium 2518 | // we might want to specify the number of fish when we create an aquarium in buildAquarium 2519 | constructor(numberOfFish: Int): this() { 2520 | val water = numberOfFish * 2000 // cm3 2521 | val tank = water + water * 0.1 2522 | height = (tank / (length * width)).toInt() 2523 | } 2524 | // Note that we can't mix constructor arguments, so we cannot create an aquarium passing the length and the number of fish 2525 | // The arguments have to match exactly with one of the available constructors 2526 | } 2527 | ``` 2528 | 2529 | ## Inheritance 2530 | ```kotlin 2531 | package Aquarium 2532 | import kotlin.math.PI 2533 | // It doesn't say explicitly but this class actually inherits from the top level class " Any " 2534 | /* 2535 | * The first thing we have to do to be able to inherit from a class 2536 | * is make the class " open ", by default classes are not subclassible 2537 | * We have to explicitly allow it 2538 | * */ 2539 | open class Aquarium (var length: Int = 100, var width: Int = 20, var height: Int = 40): Any() { 2540 | // We could add "Any()" but it's not required and doesn't give anything extra 2541 | open var volume: Int 2542 | get() = width * height * length / 1000 2543 | set(value) { height = (value * 1000) / (width * length) } 2544 | // Private setters are not allowed for open properties! 2545 | open var water = volume * 0.9 2546 | 2547 | constructor(numberOfFish: Int): this() { 2548 | val water = numberOfFish * 2000 // cm3 2549 | val tank = water + water * 0.1 2550 | height = (tank / (length * width)).toInt() 2551 | } 2552 | } 2553 | 2554 | // All classes in Kotlin have a common superclass Any, that is the default superclass for a class with no supertypes declared: 2555 | 2556 | class Example // Implicitly inherits from Any 2557 | 2558 | // Any has three methods: equals(), hashCode() and toString(). Thus, they are defined for all Kotlin classes. 2559 | 2560 | // By default, Kotlin classes are final: they can’t be inherited. To make a class inheritable, mark it with the "open" keyword. 2561 | 2562 | open class Base //Class is open for inheritance 2563 | 2564 | // To declare an explicit supertype, place the type after a colon in the class header: 2565 | 2566 | open class Base(p: Int) 2567 | 2568 | class Derived(p: Int) : Base(p) 2569 | 2570 | 2571 | // Overriding Methods 2572 | // Kotlin requires explicit modifiers for overridable members and overrides: 2573 | open class Shape { 2574 | open fun draw() { /*...*/ } 2575 | fun fill() { /*...*/ } 2576 | } 2577 | 2578 | class Circle() : Shape() { 2579 | override fun draw() { /*...*/ } 2580 | } 2581 | 2582 | // Inheritance Explanation 2583 | /* 2584 | * Let's say we want to have a different type of aquarium such as cylindrical tower 2585 | * Tower tanks are a lot like regular aquariums 2586 | * But they are also different in some ways 2587 | * So we couldn't inherit a lot of stuff from our basic aquarium 2588 | * and change the things that are different 2589 | * Now, int the same file is okay, we can create a tower tank that inherits from aquarium 2590 | * We specify the inheritance or the parent class, after the colon 2591 | * */ 2592 | class TowerTank(): Aquarium() { 2593 | // We need to change how the volume is calculated 2594 | // And we don't want to fill as much water into the tall tank 2595 | // We are doing this by overriding the water property in tower tank 2596 | override var water = volume * 0.8 2597 | 2598 | // Members are not available for subclassing by default 2599 | // This is so we don't accidentally leak implementation details without meaning to 2600 | override var volume: Int 2601 | get() = (width * height * length / 1000 * PI).toInt() 2602 | set(value) { 2603 | height = (value * 1000) / (width*length) 2604 | } 2605 | } 2606 | 2607 | //////////////////////////////////////////////////////////////////////////////////// // Solution Code 2608 | open class Book(val title: String, val author: String) { 2609 | private var currentPage = 1 2610 | open fun readPage() { 2611 | currentPage++ 2612 | } 2613 | } 2614 | 2615 | class eBook(title: String, author: String, var format: String = "text") : Book(title, author) { 2616 | private var wordsRead = 0 2617 | override fun readPage() { 2618 | wordsRead = wordsRead + 250 2619 | } 2620 | } 2621 | /////////////////////////////////////////////////////////////////////////////////// 2622 | 2623 | /////////////////////////////////////////////////////////////////////////////////// 2624 | // My Code 2625 | package Aquarium 2626 | open class Book(val title: String, val author: String) { 2627 | private var currentPage = 1 2628 | open fun readPage() { 2629 | currentPage++ 2630 | } 2631 | } 2632 | 2633 | // Subclass 2634 | class eBook(title: String, author: String, var format: String = "text"): Book(title, author) { 2635 | var wordCount = 0 2636 | override fun readPage() { 2637 | wordCount += 250 2638 | } 2639 | } 2640 | /////////////////////////////////////////////////////////////////////////////////// 2641 | ``` 2642 | 2643 | ## Interfaces 2644 | ```kotlin 2645 | /* Different types of fish have lots in common, and they do similar things in somewhat different ways 2646 | 2647 | For example; 2648 | All fish have a color and all fish have to eat 2649 | So we want to make sure that all our fish that we create do that 2650 | Kotlin offers two ways of doing that 2651 | 2652 | 1) Abstract Classes 2653 | 2) Interfaces 2654 | 2655 | Both are classes that cannot be instantiated on their own which means you cannot create objects of those types yet 2656 | 2657 | The difference is that ABSTRACT CLASSES HAVE CONSTRUCTORS while Interfaceses don't have any constructor logic 2658 | 2659 | A final thing you can do in Kotlin, when using classes that implement interfaces is create objects where you specify that the only thing you can do with them is what's defined in the interface*/ 2660 | 2661 | 2662 | ``` 2663 | 2664 | ### Interface Examples & Explanations 2665 | ```kotlin 2666 | //////////////////////// AquariumFish.kt Class//////////////////////////// 2667 | package Aquarium 2668 | // Simple Abstract Class 2669 | /* 2670 | * Because AquariumFish is abstract we can't make instances of AquariumFish directly! 2671 | * We need to provide sub classes that implement its missing functionality 2672 | * */ 2673 | abstract class AquariumFish { 2674 | abstract val color: String 2675 | } 2676 | 2677 | // Two subclasses, we have to implement the abstract property color// otherwise 2678 | // it will leave us with errors// as following 2679 | /* 2680 | * ERROR: Class 'Shark' is not abstract and does not implement 2681 | * abstract base class member public abstract val color: String 2682 | * defined in Aquarium.AquariumFish 2683 | * */ 2684 | // Now we can use it like any other class 2685 | class Shark: AquariumFish(), FishAction { 2686 | override val color = "gray" 2687 | override fun eat() { 2688 | println("hunt and eat fish") 2689 | } 2690 | } 2691 | 2692 | // Add a comma and then the FishAction interface without "()" and implement eat 2693 | // You have to implement interface methods! 2694 | class Plecostomus: AquariumFish(), FishAction { 2695 | override val color = "gold" 2696 | override fun eat() { 2697 | println("much on algae") 2698 | } 2699 | } 2700 | 2701 | // Interface example, FishAction that defines an eat function 2702 | interface FishAction { 2703 | fun eat() 2704 | } 2705 | 2706 | ////////////////////////////// Main //////////////////////////////////////// 2707 | package Aquarium 2708 | fun main() { 2709 | // // Create Aquarium class instance 2710 | buildAquarium() 2711 | makeFish() 2712 | } 2713 | 2714 | // If you mark a declaration private, 2715 | // it will only be visible the inside the file containing declaration 2716 | // Since we're only going to use buildAquarium inside this file 2717 | // We can make it private 2718 | // If you mark buildAquarium " internal " it is visible anywhere in the same module 2719 | private fun buildAquarium() { 2720 | // Creates new instance of Aquarium by calling its constructor 2721 | val myAquarium = Aquarium() 2722 | // Under the hood, Kotlin actually made a getter for all three properties 2723 | // Even though we did not write any code 2724 | println( 2725 | "Length: ${myAquarium.length}" + 2726 | " Width: ${myAquarium.width}" + 2727 | " Height: ${myAquarium.height}" 2728 | ) 2729 | 2730 | // We don't have to chance " myAquarium " to a var because, we're not changing the aquarium 2731 | // It's the same object we're modifying its properties 2732 | myAquarium.height = 80 2733 | println("New Height: ${myAquarium.height} cm") 2734 | println("Volume: ${myAquarium.volume} liters") 2735 | // To make this more readable, let's pass in name parameters 2736 | val smallAquarium = Aquarium(length = 20, width = 15, height = 30) 2737 | val smallAquarium2 = Aquarium(numberOfFish = 9) 2738 | println("Small Aquarium: Volume: ${smallAquarium2.volume} " + 2739 | "liters with length ${smallAquarium2.length} " + 2740 | "width ${smallAquarium2.width} " + 2741 | "height ${smallAquarium2.height}") 2742 | } 2743 | 2744 | /* 2745 | * This function creates a shark and a pleco and prints out their colors 2746 | * */ 2747 | fun makeFish() { 2748 | val shark = Shark() 2749 | val pleco = Plecostomus() 2750 | 2751 | println("Shark: ${shark.color} \n Pleco: ${pleco.color}") 2752 | 2753 | shark.eat() 2754 | pleco.eat() 2755 | } 2756 | 2757 | /* 2758 | * When a fish gets the food, it eats it, we don't care what kind of fish it is 2759 | * as long as it can eat the food. "Eat" is defined in fish action, So every fish we passed 2760 | * to feed fish needs to implement fish action, we don't care about any other properties 2761 | * As long as it implements fish action, we can use it. 2762 | * Only fish that implement fish action can be passed into "feedFish" 2763 | * This is a simplistic example but when you have a lot of classes 2764 | * this can help you keep clearer and more organized 2765 | * */ 2766 | fun feedFish(fish: FishAction) { 2767 | fish.eat() 2768 | } 2769 | //////////////////////////////////////////////////////////////////////////////// 2770 | 2771 | //////////////////////////////////////////////////////////////////////////////// 2772 | package Aquarium 2773 | // Simple Abstract Class 2774 | /* 2775 | * Because AquariumFish is abstract we can't make instances of AquariumFish directly! 2776 | * We need to provide sub classes that implement its missing functionality 2777 | * */ 2778 | abstract class AquariumFish { 2779 | abstract val color: String 2780 | } 2781 | 2782 | // Two subclasses, we have to implement the abstract property color// otherwise 2783 | // it will leave us with errors// as following 2784 | /* 2785 | * ERROR: Class 'Shark' is not abstract and does not implement 2786 | * abstract base class member public abstract val color: String 2787 | * defined in Aquarium.AquariumFish 2788 | * */ 2789 | // Now we can use it like any other class 2790 | class Shark: AquariumFish(), FishAction { 2791 | override val color = "gray" 2792 | override fun eat() { 2793 | println("hunt and eat fish") 2794 | } 2795 | } 2796 | 2797 | // Add a comma and then the FishAction interface without "()" and implement eat 2798 | // You have to implement Interface methods! 2799 | class Plecostomus: AquariumFish(), FishAction { 2800 | override val color = "gold" 2801 | override fun eat() { 2802 | println("much on algae") 2803 | } 2804 | } 2805 | 2806 | // Interface example, FishAction that defines an eat function 2807 | interface FishAction { 2808 | fun eat() 2809 | } 2810 | //////////////////////////////////////////////////////////////////////////////// 2811 | 2812 | //////////////////////////////////////////////////////////////////////////////// 2813 | Difference Between Abstract Classes And Interfaces 2814 | // There is really only one syntax difference in Kotlin between abstract classes and interfaces. 2815 | -> Abstract classes can have constructors and interfaces cannot 2816 | 2817 | // Both abstract classes and interfaces can contain implementations of methods 2818 | // On interfaces we call them default implementations 2819 | // The big difference really is in when and how you use them 2820 | 2821 | // Use an interface if you have a lot of methods and one or two defalt implementations like this; 2822 | interface AquariumAction { 2823 | fun eat() 2824 | fun jump() 2825 | fun clean() 2826 | fun catchFish() 2827 | fun swim() { 2828 | println("swim") 2829 | } 2830 | } 2831 | 2832 | // Use an abstract class anytime you can't complete a class 2833 | interface FishActionTest { 2834 | fun eat() 2835 | } 2836 | 2837 | abstract class AquariumFishTest: FishActionTest { 2838 | abstract val color: String 2839 | override fun eat() { 2840 | println("yum") 2841 | } 2842 | } 2843 | 2844 | // Making all aquarium fish implement "FishActionTest", we can provide a default implementation for "eat" while leaving color abstract, that's because there isn't really a good default color far a fish 2845 | 2846 | // But really Kotlin provides us a better tool for this than abstract classes 2847 | 2848 | // INTERFACE DELEGATION let's you add features to a class via composition 2849 | // Composition is when you use an instance of another class as opposed to inheriting from it 2850 | 2851 | // Instead of requiring the caller's sublass' giant abstract class, give them a small interface and let them delegate those interfaces to an object 2852 | 2853 | // How do we do composition ? 2854 | 2855 | /////////////////////////////////////////////////////////////////////////////////// 2856 | package Aquarium 2857 | /* 2858 | * Interface delegation is really powerful 2859 | * and you should generally consider how to use it whenever you 2860 | * might use an abstract class in another language 2861 | * It let's you use composition to plug-in behaviours 2862 | * instead of requiring a lot of sub classes each specialized in a different way 2863 | * */ 2864 | 2865 | fun main() { 2866 | delegate() 2867 | } 2868 | 2869 | fun delegate() { 2870 | val pleco = Plecostomus2() 2871 | println("Fish has color ${pleco.color}") 2872 | pleco.eat() 2873 | } 2874 | 2875 | // Let's start breaking up aquarium fish into interfaces 2876 | interface FishAction2 { 2877 | fun eat() 2878 | } 2879 | 2880 | // 2881 | interface FishColor { 2882 | val color: String 2883 | } 2884 | 2885 | /* 2886 | * We can remove inheritance from aquarium fish 2887 | * because we get all the functionality from the interfaces 2888 | * and we don't even have to change the code in the body of plecostomus 2889 | * */ 2890 | // Fish color could have been implemented by a class instead of object 2891 | // But this time, we would have had to create many unnecessary same class objects 2892 | // Here, FishColor interface is implemented by GoldColor object which will be only one 2893 | // object at all 2894 | /* This means implement the interface fish color, 2895 | by deferring all calls to the object, gold color 2896 | So everytime you call the color property on this class, it will actually 2897 | call the color property on gold color 2898 | * */ 2899 | /* 2900 | * Of course there are different colors of plecostomi in the world 2901 | * So we can make the fish color object a constructor parameter 2902 | * with a default of gold color and defer calls to the color property whatever 2903 | * fish color we get passed in 2904 | * */ 2905 | // Now Plecostomus2 doesn't have a body, all its overrides are handled by 2906 | // interface delegation 2907 | class Plecostomus2(fishColor: FishColor = GoldColor): 2908 | FishAction2 by PrintingFishAction("a lot of algae"), 2909 | FishColor by GoldColor 2910 | 2911 | /* 2912 | * It doesn't really make sense to make multiple instances of 2913 | * gold color as they would all do the exact same thing 2914 | * Kotlin let's us declare a class where we can only have one instance by using 2915 | * the keyword "object" instead of "class" 2916 | * */ 2917 | // This will declare a class and make exactly one instance of it 2918 | // The instance will be called gold color and there's no way 2919 | // to make another instance of this class but that's okay we don't need to 2920 | // If you're familiar with the Singleton Pattern this is how to implement it in Kotlin 2921 | /* 2922 | * In software engineering, the singleton pattern is a software design pattern 2923 | * that restricts the instantiation of a class to one "single" instance. 2924 | * This is useful when exactly one object is needed to coordinate actions across the system. 2925 | * The term comes from the mathematical concept of a singleton. 2926 | * */ 2927 | object GoldColor : FishColor { 2928 | override val color = "gold" 2929 | } 2930 | 2931 | // If we were passed in a red color, then fish color would be by red color and return red 2932 | object RedColor : FishColor { 2933 | override val color = "red" 2934 | } 2935 | 2936 | // Instead of printing a fixed string, we print our whatever food we were passed 2937 | // Since we have a member variable food, we can't make PrintingFishAction an object 2938 | // We want a different instance for each food that we passed in 2939 | // Constructors are not allowed for "object" 2940 | class PrintingFishAction(val food: String) : FishAction2 { 2941 | override fun eat() { 2942 | println(food) 2943 | } 2944 | } 2945 | /////////////////////////////////////////////////////////////////////////////////// 2946 | ``` 2947 | 2948 | ### Delegation Design Pattern 2949 | ```kotlin 2950 | package Delegation 2951 | /* 2952 | * KOTLIN DELEGATION 2953 | * 2954 | * Delegation is an object oriented design pattern 2955 | * And Kotlin supports it natively 2956 | * Delegation Pattern means delegating the responsibilities 2957 | * to other objects. 2958 | * */ 2959 | 2960 | class FilePlayer(private val file: String): Player { 2961 | override fun play() { 2962 | println("$file is playing...") 2963 | } 2964 | } 2965 | 2966 | class FileDownloader(private val file: String): Downloader { 2967 | override fun download() { 2968 | println("$file downloaded") 2969 | } 2970 | } 2971 | 2972 | /* 2973 | * Here, we will be delegating the responsibility of 2974 | * "download()" and "play()" interfaces to 2975 | * "Downloader" and "Player" objects that we pass in 2976 | * So the class is just forwarding the responsibility 2977 | * */ 2978 | class MediaFile( 2979 | private val downloader: Downloader, 2980 | private val player: Player 2981 | ) : Downloader by downloader, Player by player { 2982 | /* 2983 | * We don't need to write following two methods 2984 | * because Kotlin already supports delegation natively 2985 | * This is boilerplate code 2986 | * */ 2987 | // override fun download() { 2988 | // downloader.download() 2989 | // } 2990 | // 2991 | // override fun play() { 2992 | // player.play() 2993 | // } 2994 | } 2995 | 2996 | fun main() { 2997 | val file = "FileGenesis1.mp4" 2998 | val mediaFile = MediaFile(FileDownloader(file), FilePlayer(file)) 2999 | mediaFile.download() 3000 | mediaFile.play() 3001 | } 3002 | 3003 | interface Downloader { 3004 | fun download() 3005 | } 3006 | 3007 | interface Player { 3008 | fun play() 3009 | } 3010 | ``` 3011 | 3012 | ### Difference Between "Open Class" and "Abstract Class" 3013 | ```kotlin 3014 | // Imagine you have 2 classes 3015 | 3016 | Class Person [parent class] 3017 | Class Coder [sub/child class] 3018 | 3019 | /*When you want to inherit Coder from Person you have to make Person open, so it is available to inherit from. Meanwhile you can make objects from Person itself. 3020 | 3021 | When you don't need to make objects from parent class(in our case it's Person) or you don't see any meaning creating objects from it you can use abstract instead of open. 3022 | 3023 | It works the same way as open does. But the main difference is that you cannot make objects from Person(parent class) anymore. 3024 | 3025 | Abstract class cannot be instantiated and must be inherited, abstract classes are open for extending by default. 3026 | 3027 | Open modifier on the class allows inheriting it. If the class has not open modifier it is considered final and cannot be inherited.*/ 3028 | ``` 3029 | 3030 | ### Practice Time | Abstract & Interface 3031 | ```kotlin 3032 | /*Let's go back to your spices. Make Spice an abstract class, and then create some subclasses that are actual spices. 3033 | 3034 | It's easiest (organizationally) if you make a new package, Spices, with a file, Spice, that has a main() function. 3035 | Copy/paste your Spice class code into that new file. 3036 | Make Spice abstract. 3037 | Create a subclass, Curry. Curry can have varying levels of spiciness, so we don't want to use the default value, but rather pass in the spiciness value. 3038 | Spices are processed in different ways before they can be used. Add an abstract method prepareSpice to Spice, and implement it in Curry. 3039 | Curry is ground into a powder, so let's call a method grind(). However, grinding is something that's not unique to curry, or even to spices, and it's always done in a grinder. So we can create an Interface, Grinder, that implements the grind() method. Do that now. 3040 | Then add the Grinder interface to the Curry class.*/ 3041 | 3042 | // Delegation 3043 | // Using the provided code from the lesson for guidance, add a yellow color to Curry. 3044 | 3045 | fun main (args: Array) { 3046 | delegate() 3047 | } 3048 | 3049 | fun delegate() { 3050 | val pleco = Plecostomus() 3051 | println("Fish has has color ${pleco.color}") 3052 | pleco.eat() 3053 | } 3054 | 3055 | interface FishAction { 3056 | fun eat() 3057 | } 3058 | 3059 | interface FishColor { 3060 | val color: String 3061 | } 3062 | 3063 | object GoldColor : FishColor { 3064 | override val color = "gold" 3065 | } 3066 | 3067 | class PrintingFishAction(val food: String) : FishAction { 3068 | override fun eat() { 3069 | println(food) 3070 | } 3071 | } 3072 | 3073 | class Plecostomus (fishColor: FishColor = GoldColor): 3074 | FishAction by PrintingFishAction("eat a lot of algae"), 3075 | FishColor by fishColor 3076 | 3077 | // Interface 3078 | /*Create an interface, SpiceColor, that has a color property. You can use a String for the color. 3079 | Create a singleton subclass, YellowSpiceColor, using the object keyword, because all instances of Curry and other spices can use the same YellowSpiceColor instance. 3080 | Add a color property to Curry of type SpiceColor, and set the default value to YellowSpiceColor. 3081 | Add SpiceColor as an interface, and let it be by color. 3082 | Create an instance of Curry, and print its color. However, color is actually a property common to all spices, so you can move it to the parent class. 3083 | Change your code so that the SpiceColor interface is added to the Spice class and inherited by Curry.*/ 3084 | 3085 | // Solution Code 3086 | abstract class Spice(val name: String, val spiciness: String = "mild", color: SpiceColor) : SpiceColor by color { 3087 | abstract fun prepareSpice() 3088 | } 3089 | 3090 | class Curry(name: String, spiciness: String, color: SpiceColor = YellowSpiceColor) : Spice(name, spiciness, color), Grinder { 3091 | override fun grind() { 3092 | } 3093 | 3094 | override fun prepareSpice() { 3095 | grind() 3096 | } 3097 | } 3098 | 3099 | interface Grinder { 3100 | fun grind() 3101 | } 3102 | 3103 | interface SpiceColor { 3104 | val color: String 3105 | } 3106 | 3107 | object YellowSpiceColor : SpiceColor { 3108 | override val color = "Yellow" 3109 | } 3110 | ``` 3111 | 3112 | ## Data Classes 3113 | ```kotlin 3114 | package Decorations 3115 | /* 3116 | * DATA CLASSES 3117 | * Often, we have classes that mostly act as data containers 3118 | * In Kotlin, for classes that mostly hold data, 3119 | * there is a class with benefits 3120 | * */ 3121 | fun main() { 3122 | makeDecoration() 3123 | } 3124 | 3125 | fun makeDecoration() { 3126 | // Create instance of Decorations class 3127 | val d1 = Decorations("granite") 3128 | 3129 | /* 3130 | * With a data class printing the object 3131 | * prints the values of properties 3132 | * instead of just an address of the object 3133 | * that is the object pointer 3134 | * basically it creates toString for us to print the properties 3135 | * */ 3136 | println(d1) // Decorations(rocks=granite) 3137 | 3138 | /* 3139 | * Data class also provides an equals method to compare two 3140 | * instances of a data class 3141 | * */ 3142 | val d2 = Decorations("slate") 3143 | println(d2) // Decorations(rocks=slate) 3144 | 3145 | val d3 = Decorations("slate") 3146 | println(d3) // Decorations(rocks=slate) 3147 | 3148 | // Comparison 3149 | println(d1 == d2) // false 3150 | println(d3 == d2) // true 3151 | 3152 | // We can copy data objects using the copy method 3153 | // This creates a new object with the same 3154 | // property values 3155 | val d4 = d3.copy() 3156 | println(d3) 3157 | println(d4) 3158 | 3159 | // Another Decoration 3160 | val d5 = Decorations2("crystal", "wood", "diver") 3161 | println(d5) 3162 | 3163 | /* 3164 | * DECOMPOSITION 3165 | * To get at the properties and assign them to variables 3166 | * Kotlin let's us use a process called decomposition 3167 | * */ 3168 | 3169 | // We can make three variables, one for each property 3170 | // and assign the object to it 3171 | // Kotlin puts the property values in each variable and 3172 | // we can then use it 3173 | // We do need to put parentheses around the variables for decomposition 3174 | // The number of variables must match the number of properties 3175 | // or we get compiler error 3176 | // The variables are assigned in the order in which 3177 | // they are declared in the class 3178 | val (rock, wood, diver) = d5 3179 | // Or we can also do this alternatively 3180 | val (rock2, wood2, diver2) = Decorations2("crystal", "wood", "diver") 3181 | println(rock) 3182 | println(wood) 3183 | println(diver) 3184 | } 3185 | 3186 | // Data classes must have 3187 | // at least one primary constructor parameter 3188 | data class Decorations(val rocks: String) { 3189 | } 3190 | 3191 | data class Decorations2( 3192 | val rocks: String, 3193 | val wood: String, 3194 | val diver: String) { 3195 | } 3196 | ``` 3197 | 3198 | ### Practice Time 3199 | ```kotlin 3200 | /*Create a simple data class, SpiceContainer, that holds one spice. 3201 | Give SpiceContainer a property, label, that is derived from the name of the spice. 3202 | Create some containers with spices and print out their labels.*/ 3203 | 3204 | // Solution Code 3205 | data class SpiceContainer(var spice: Spice) { 3206 | val label = spice.name 3207 | } 3208 | 3209 | val spiceCabinet = listOf(SpiceContainer(Curry("Yellow Curry", "mild")), 3210 | SpiceContainer(Curry("Red Curry", "medium")), 3211 | SpiceContainer(Curry("Green Curry", "spicy"))) 3212 | 3213 | for(element in spiceCabinet) println(element.label) 3214 | 3215 | // My Code 3216 | package DataClasses 3217 | abstract class Spice(val name: String, val spiciness: String = "mild", ) { 3218 | } 3219 | 3220 | class Curry(name: String, spice: String): Spice(name, spice) { 3221 | } 3222 | 3223 | data class SpiceContainer( 3224 | val spice: Spice, 3225 | val label: String = spice.name, 3226 | val spiciness: String = spice.spiciness 3227 | ) 3228 | 3229 | fun main() { 3230 | val spiceCabinet = listOf(SpiceContainer(Curry("Yellow Curry", "mild")), 3231 | SpiceContainer(Curry("Red Curry", "medium")), 3232 | SpiceContainer(Curry("Green Curry", "spicy"))) 3233 | 3234 | for (element in spiceCabinet) { 3235 | println("${element.label}, ${element.spiciness}") 3236 | } 3237 | } 3238 | ``` 3239 | 3240 | ## Special Purpose Classes | Singletons, Enums, Sealed Classes 3241 | 3242 | ### Singeletons | Objects 3243 | ```kotlin 3244 | /* 3245 | * SINGLETONS - "Object" 3246 | * 3247 | * To create singleton, use the "object" keyword 3248 | * when you declare you class 3249 | * 3250 | * Anytime you're defining a class that 3251 | * shouldn't be instantiated multiple times 3252 | * you can use the "object" keyword in place of class 3253 | * 3254 | * Kotlin will instantiate exactly one instance of the class 3255 | * 3256 | * Since there can be only one MobyDick, we declare it as an object 3257 | * instead of a class 3258 | * */ 3259 | object MobyDickWhale { 3260 | val author = "Herman Melville" 3261 | fun jump () { 3262 | // ... 3263 | } 3264 | } 3265 | ``` 3266 | 3267 | ### Enums 3268 | ```kotlin 3269 | /* 3270 | * ENUMS 3271 | * 3272 | * which lets you enumerate items 3273 | * enums actually define a class 3274 | * and you can give them properties or even methods 3275 | * 3276 | * Enums are like singletons, Kotlin will make 3277 | * exactly one red, exactly one green and exactly one blue 3278 | * there is no way to create more than one color object 3279 | * And, there is not any way to define more colors 3280 | * other then where the enum is declared 3281 | * */ 3282 | enum class Color(val rgb: Int) { 3283 | RED(0xFF0000), 3284 | GREEN(0x00FF00), 3285 | BLUE(0x0000FF) 3286 | } 3287 | 3288 | // The most basic usage of enum classes is implementing type-safe enums: 3289 | enum class Direction { 3290 | NORTH, SOUTH, WEST, EAST 3291 | } 3292 | 3293 | // Each enum constant is an object. Enum constants are separated with commas. 3294 | // Since each enum is an instance of the enum class, it can be initialized as: 3295 | 3296 | enum class Color(val rgb: Int) { 3297 | RED(0xFF0000), 3298 | GREEN(0x00FF00), 3299 | BLUE(0x0000FF) 3300 | } 3301 | ``` 3302 | 3303 | ### Sealed Classes 3304 | ```kotlin 3305 | /* 3306 | * SEALED CLASS 3307 | * 3308 | * It's a class that can be subclassed 3309 | * but only inside the file which it's declared 3310 | * If you try to subclass it in a different file, you'll get an error 3311 | * This makes sealed classes a safe way to represent a fixed number of types 3312 | * 3313 | * They're great for returning success or error from a network API 3314 | * 3315 | * */ 3316 | sealed class Seal { 3317 | 3318 | } 3319 | 3320 | // If we want to create more Seals we have to put them 3321 | // In this file, since the Seal class is in this file! 3322 | // I can't subclass Seal in any other file 3323 | // Since They're all in the same file 3324 | // Kotlin knows statically(at compile time) about all of the subclasses 3325 | class SeaLion: Seal() 3326 | class Walrus: Seal() 3327 | 3328 | /* 3329 | * I can use a "when" statement to check 3330 | * what type of seal I have 3331 | * And If I don't match all of the types of seal 3332 | * Kotlin will give me a compiler error! 3333 | * */ 3334 | fun matchSeal(seal: Seal): String { 3335 | return when (seal) { 3336 | is Walrus -> "walrus" 3337 | is SeaLion -> "seaLion" 3338 | } 3339 | } 3340 | ``` 3341 | 3342 | ### Practice Time 3343 | ```kotlin 3344 | // You used object in the previous lesson and quiz. 3345 | // And now that you know about enums, here's the code for Color as an enum: 3346 | 3347 | enum class Color(val rgb: Int) { 3348 | RED(0xFF0000), GREEN(0x00FF00), BLUE(0x0000FF); 3349 | } 3350 | 3351 | // In SpiceColor, change the type of color from String to the Color class, and set the appropriate color in YellowSpiceColor. 3352 | // Hint: The color code for yellow is YELLOW(0xFFFF00) 3353 | 3354 | // Make Spice a sealed class. 3355 | // What is the effect of doing this? 3356 | // Why is this useful? 3357 | 3358 | // Solution Code 3359 | interface SpiceColor { 3360 | val color: Color 3361 | } 3362 | 3363 | object YellowSpiceColor : SpiceColor { 3364 | override val color = Color.YELLOW 3365 | } 3366 | 3367 | // Answer Explanation: 3368 | // Making Spice a sealed class helps keep all the spices together in one file. 3369 | ``` 3370 | 3371 |

Lesson 5 | Kotlin Essentials: Beyond The Basics

3372 | 3373 | ### Pairs 3374 | ```kotlin 3375 | package Collections 3376 | fun main() { 3377 | // Sample Generic Pair 3378 | val equipment = "fishnet" to "catching" 3379 | println(equipment.first) 3380 | println(equipment.second) 3381 | 3382 | // You can also chain the pairs 3383 | val chain = "A" to "B" to "C" to "D" 3384 | println(chain.first) // Output: ((A, B), C) 3385 | println(chain.first.first.first) // Output: A 3386 | 3387 | val testChain = ("A" to "B" to "C") to "D" 3388 | println(testChain) 3389 | 3390 | // You can also create triplets 3391 | val triple: Triple = Triple(1, 2, 3) 3392 | 3393 | // deconstructing 3394 | val fishnet = "fishnet" to "catching fish" 3395 | val (tool, use) = fishnet 3396 | val (first, second, third) = Triple(1, 2 ,3) 3397 | 3398 | val fishnetString = fishnet.toString() 3399 | val fishnetList = fishnet.toList() 3400 | 3401 | // We can use them to return more than one variable from a function 3402 | // and we can destruct it and use 3403 | val (tool2, use2) = giveMeATool() 3404 | } 3405 | 3406 | fun giveMeATool(): Pair { 3407 | return Pair("fishnet", "catching") 3408 | // return ("fishnet" to "catching") // Alternative 3409 | } 3410 | ``` 3411 | 3412 | ### Practice Time 3413 | ```kotlin 3414 | /* 3415 | Let's go through an example of getting information about a book in the format of a Pair. Generally, you want information about both the title and the author, and perhaps also the year. 3416 | 3417 | Let’s create a basic book class, with a title, author, and year. Of course, you could get each of the properties separately. 3418 | Create a method that returns both the title and the author as a Pair. 3419 | Create a method that returns the title, author and year as a Triple. Use the documentation to find out how to use Triple. 3420 | Create a book instance. 3421 | Print out the information about the book in a sentence, such as: “Here is your book X written by Y in Z.” 3422 | */ 3423 | 3424 | // My Code 3425 | package lesson_5 3426 | class Book( 3427 | val title: String, 3428 | val author: String, 3429 | val year: Int 3430 | ) { 3431 | fun getTitleAuthor(): Pair { 3432 | return Pair(title, author) 3433 | } 3434 | 3435 | fun getTitleAuthorYear(): Triple { 3436 | return Triple(title, author, year) 3437 | } 3438 | } 3439 | 3440 | fun main() { 3441 | val book = Book("Elon Musk", "Ashlee Vance", 2012) 3442 | 3443 | val (title, author) = book.getTitleAuthor() 3444 | val (title1, author1, year) = book.getTitleAuthorYear() 3445 | 3446 | println("Here is your book ${book.title} written by ${book.author} in ${book.year}") 3447 | } 3448 | 3449 | // Solution Code 3450 | class Book(val title: String, val author: String, val year: Int) { 3451 | 3452 | fun getTitleAuthor(): Pair { 3453 | return (title to author) 3454 | } 3455 | 3456 | fun getTitleAuthorYear(): Triple { 3457 | return Triple(title, author, year) 3458 | } 3459 | } 3460 | 3461 | fun main(args: Array) { 3462 | val book = Book("Romeon and Juliet", "William Shakespeare", 1597) 3463 | val bookTitleAuthor = book.getTitleAuthor() 3464 | val bookTitleAuthorYear = book.getTitleAuthorYear() 3465 | 3466 | println("Here is your book ${bookTitleAuthor.first} by ${bookTitleAuthor.second}") 3467 | 3468 | println("Here is your book ${bookTitleAuthorYear.first} " + 3469 | "by ${bookTitleAuthorYear.second} written in ${bookTitleAuthorYear.third}") 3470 | } 3471 | ``` 3472 | 3473 | ### Practice Time 3474 | ```kotlin 3475 | /* 3476 | One book is rarely alone, and one author rarely writes just one book. 3477 | Create a Set of book titles called allBooks, for example, by William Shakespeare. 3478 | Create a Map called library that associates the set of books, allBooks, to the author. 3479 | Use the collections function any() on library to see if any of the books are “Hamlet’ 3480 | Create a MutableMap called moreBooks, and add one title/author to it. 3481 | 3482 | Use getOrPut() to see whether a title is in the map, and if the title is not in the map, add it. 3483 | 3484 | Hints: 3485 | any() is applied to a collection and takes a lambda as its argument, for example: 3486 | 3487 | myList.any {it.contains(“name”)} 3488 | 3489 | getOrPut() is a handy function that will check whether a key is in a map, and if it is, will return the value. Otherwise, it will add the key with the supplied value to the map. 3490 | mapOf() may come in handy. 3491 | */ 3492 | 3493 | // Solution Code 3494 | val allBooks = setOf("Macbeth", "Romeo and Juliet", "Hamlet", "A Midsummer Night's Dream") 3495 | val library = mapOf("Shakespeare" to allBooks) 3496 | println(library.any { it.value.contains("Hamlet") }) 3497 | 3498 | val moreBooks = mutableMapOf("Wilhelm Tell" to "Schiller") 3499 | moreBooks.getOrPut("Jungle Book") { "Kipling" } 3500 | moreBooks.getOrPut("Hamlet") { "Shakespeare" } 3501 | println(moreBooks) 3502 | 3503 | // My Code 3504 | val allBooksOfOneAuthor = setOf("A", "B", "C") 3505 | // { set of books, author } 3506 | val library = mapOf( 3507 | "by William Shakespeare" to allBooksOfOneAuthor, 3508 | "Genesis" to setOf("M", "H", "N") 3509 | ) 3510 | 3511 | val hamletFound = library.any() { it.value.contains("Hamlet") } 3512 | println("Hamlet is " + (if (hamletFound) " " else "not ") + "in the Library") 3513 | 3514 | val moreBooks = mutableMapOf>() 3515 | moreBooks["Melo"] = setOf("istanbul", "ankara", "izmir") 3516 | moreBooks.getOrPut("Seno") { setOf("Senosis") } 3517 | ``` 3518 | 3519 | ### Constants 3520 | ```kotlin 3521 | // We can make top level constants and assign them a value at compile time using "const" 3522 | // We have "val" and "const val" now, What is the difference? 3523 | // Top Level, Compile Time Constant Variable 3524 | // The value is always determined at compile time 3525 | // const value set it compile time so we cannot call and execute a function 3526 | // to get its value set 3527 | const val num = 5 3528 | // However, const val only works at the top level and in classes declared with object 3529 | // Not with regular classes declared with class 3530 | 3531 | /* 3532 | * So we can use this to create a file or object that 3533 | * contains only constants and import them one-by-one 3534 | * */ 3535 | const val CONSTANT = "top-level constant" 3536 | 3537 | object Constants { 3538 | const val CONSTANT2 = "top-level constant" 3539 | } 3540 | 3541 | val foo = Constants.CONSTANT2 3542 | // Kotlin des not have a concept of class level constants 3543 | // To define constants inside a class 3544 | // You have to wrap them into a companion object 3545 | class MyClass { 3546 | /* 3547 | * The differences between 3548 | * "Regular Objects" and "Companion Objects" are as follows; 3549 | * 3550 | * -> Companion objects are initialized from the static constructor 3551 | * of the containing class, that is, they are created when the object is created 3552 | * 3553 | * -> Plain objects are initialized lazily on the first access to that object 3554 | * that is, when they are first used 3555 | * */ 3556 | companion object { 3557 | const val CONSTANT3 = "constant inside companion" 3558 | } 3559 | // So you need to wrap constants in classes inside a companion object 3560 | } 3561 | 3562 | fun main() { 3563 | // Difference between val and const val 3564 | // With val, the value that is assigned can be determined during program execution 3565 | val number = 5 3566 | 3567 | // For example we can assign the return value from a function 3568 | fun complexFunctionCall() {} 3569 | // Because we can set it during execution 3570 | val result = complexFunctionCall() 3571 | 3572 | // ERROR! 3573 | // Modifier 'const' is not applicable to 'local variable' 3574 | // This should be Top Level! 3575 | const val num = 5 3576 | } 3577 | 3578 | // There are 3 different ways in which you can create constants in Kotlin. It’s not the most exciting topic, but you’ll use constants all the time. 3579 | 3580 | // For each situation, decide when you would use a CONSTANT, an ENUM, and a DATA CLASS. 3581 | ``` 3582 | 3583 | ### Quiz Question 3584 | ```kotlin 3585 | /*Let’s continue with our books setup to practice creating constants in Kotlin. There are 3 different ways in which you can create constants in Kotlin. It’s not the most exciting topic, but you’ll use constants all the time. 3586 | 3587 | For each situation, decide when you would use a constant, an enum, and a data class. 3588 | 3589 | Situation -> Data Type 3590 | 3591 | 1) Storing simple values without any functionality. For example, a URL or a numeric code. -> use Constants 3592 | 3593 | 2) They are objects that store groups of values that are related. They offer type safety. -> use enums 3594 | 3595 | 3) Creating objects that only have properties without additional functionality. 3596 | -> Use data classes 3597 | */ 3598 | ``` 3599 | 3600 | ### Practice Time 3601 | ```kotlin 3602 | /*Create a top-level constant for the maximum number of books a person could borrow. 3603 | Inside the Book class, create a method canBorrow() that returns true or false depending on whether a user has already borrowed the max number of books. 3604 | Create a Constants object that provides constants to the book. For this example, provide the BASE_URL for all books in the library catalog. Inside Book, add a method printUrl that creates and prints a URL composed of BASE_URL, the book title, and “.html”. 3605 | The base URL is really of interest to the Book class. As such, it makes sense to limit its scope to the Book class. Use a companion object to define the constant in Book.*/ 3606 | 3607 | // Solution Code 3608 | const val MAX_NUMBER_BOOKS = 20 3609 | 3610 | fun canBorrow(hasBooks: Int): Boolean { 3611 | return (hasBooks < MAX_NUMBER_BOOKS) 3612 | } 3613 | 3614 | object Constants { 3615 | const val BASE_URL = "http://www.turtlecare.net/" 3616 | } 3617 | 3618 | fun printUrl() { 3619 | println(Constants.BASE_URL + title + ".html") 3620 | } 3621 | 3622 | companion object { 3623 | val BASE_URL = "http://www.turtlecare.net/" 3624 | } 3625 | 3626 | // My Code 3627 | package lesson_5 3628 | const val maximumBooks = 100 3629 | 3630 | object Constants { 3631 | const val BASE_URL = "www.library.com/" 3632 | } 3633 | 3634 | class Book( 3635 | val title: String, 3636 | val author: String, 3637 | val year: Int, 3638 | val maxBooks: Int = 0 3639 | ) { 3640 | 3641 | companion object BookURL { 3642 | const val BASE_URL = "www.library.com/" 3643 | } 3644 | 3645 | fun printUrl() { 3646 | println( 3647 | Constants.BASE_URL + "/" 3648 | + title + "/" 3649 | + author + "/" 3650 | + year + "/" 3651 | + ".html" 3652 | ) 3653 | } 3654 | 3655 | fun getTitleAuthor(): Pair { 3656 | return Pair(title, author) 3657 | } 3658 | 3659 | fun getTitleAuthorYear(): Triple { 3660 | return Triple(title, author, year) 3661 | } 3662 | 3663 | fun canBorrow(): Boolean { 3664 | return maxBooks < maximumBooks 3665 | } 3666 | } 3667 | 3668 | fun main() { 3669 | val book = Book("Elon Musk", "Ashlee Vance", 2012) 3670 | 3671 | val (title, author) = book.getTitleAuthor() 3672 | val (title1, author1, year) = book.getTitleAuthorYear() 3673 | 3674 | println("Here is your book ${book.title} written by ${book.author} in ${book.year}") 3675 | } 3676 | ``` 3677 | 3678 | ### Extension Functions 3679 | ```kotlin 3680 | /* 3681 | Extension Functions allow you to add functions to an existing class without having access to its source code. Under the hood, extensions do not actually modify the classes they extend. By defining a new entension you do not insert new members into the class. We merely make a new function callable with the dot-notation on variables of this type. Once we declare an extension function, it's avaiable as if it were declared on the class. 3682 | */ 3683 | 3684 | // Extension Functions are great way to add helpful functionality to classes that you don't own 3685 | 3686 | // We merely make a new function callable 3687 | // with the dot-notation on variables of this type 3688 | fun String.hasSpaces(): Boolean { 3689 | val found = this.find { it == ' ' } 3690 | return found != null 3691 | } 3692 | 3693 | // We can also write one line 3694 | fun String.hasSpacesShorterWay() = this.find { it == ' ' } != null 3695 | 3696 | fun Int.hasZero(): Boolean { 3697 | val found = this.toString().find { it == '0' } 3698 | return found != null 3699 | } 3700 | 3701 | fun main() { 3702 | println("Does it have spaces?".hasSpaces()) 3703 | println(1232.hasZero()) 3704 | } 3705 | 3706 | // You can also use it to separate the core API from helper methods on classes you do own 3707 | class AquariumPlant(val color: String, private val size: Int) 3708 | // This extension function is just a helper 3709 | // Extension functions are defined outside of the class they extend 3710 | fun AquariumPlant.isRed() = color == "Red" 3711 | // So, they cannot access to private variables! 3712 | fun AquariumPlant.isBig() = size > 50 3713 | // ERROR// Cannot access 'size': it is private in 'AquariumPlant' 3714 | 3715 | // You should think of them as helper functions that rely only on the public API 3716 | // xtension functions are always resolved statically based on the variable they're applied to, that is at compile time 3717 | 3718 | // We can define extension properties too, "isGreen" is the property name 3719 | // We can use "isGreen" just like a regular property 3720 | val AquariumPlant.isGreen: Boolean 3721 | get() = color == "Green" 3722 | 3723 | fun propertyExample() { 3724 | val plant = AquariumPlant("Green", 50) 3725 | println(plant.isGreen) // true 3726 | } 3727 | ``` 3728 | 3729 | ### Extension Function Examples 3730 | ```kotlin 3731 | package lesson_5 3732 | // We merely make a new function callable 3733 | // with the dot-notation on variables of this type 3734 | fun String.hasSpaces(): Boolean { 3735 | val found = this.find { it == ' ' } 3736 | return found != null 3737 | } 3738 | 3739 | // We can also write one line 3740 | fun String.hasSpacesShorterWay() = this.find { it == ' ' } != null 3741 | 3742 | fun Int.hasZero(): Boolean { 3743 | val found = this.toString().find { it == '0' } 3744 | return found != null 3745 | } 3746 | 3747 | // This extension function is just a helper 3748 | // Extension functions are defined outside of the class they extend 3749 | fun AquariumPlant.isRed() = color == "Red" 3750 | // So, they cannot access to private variables! 3751 | //fun AquariumPlant.isBig() = size > 50 3752 | // ERROR// Cannot access 'size': it is private in 'AquariumPlant' 3753 | 3754 | open class AquariumPlant(val color: String, private val size: Int) 3755 | class GreenLeafyPlant(size: Int): AquariumPlant("Green", size) 3756 | 3757 | fun AquariumPlant.print() = println("AquariumPlant") 3758 | fun GreenLeafyPlant.print() = println("GreenLeafyPlant") 3759 | 3760 | // We can define extension properties too, "isGreen" is the property name 3761 | // We can use "isGreen" just like a regular property 3762 | val AquariumPlant.isGreen: Boolean 3763 | get() = color == "Green" 3764 | 3765 | fun propertyExample() { 3766 | val plant = AquariumPlant("Green", 50) 3767 | println(plant.isGreen) // true 3768 | } 3769 | 3770 | // We can also make the class we are extending which is sometimes called the receiver nullable 3771 | // If we do that then the "this" variable used in the body can be null 3772 | // The object on which the extension function is called can be null 3773 | // We indicate this with a question mark after "AquariumPlant?" but before the dot 3774 | fun AquariumPlant?.pull() { 3775 | // Inside the body we can test for null by using "?.apply" 3776 | // If object is not null, the apply body will be executed 3777 | this?.apply { 3778 | println("removing $this") 3779 | } 3780 | } 3781 | 3782 | /* 3783 | * You would want to take a nullable receiver if you expect 3784 | * the callers will want to call your extension function on nullable variables 3785 | * */ 3786 | fun nullableExample() { 3787 | val plantNull: AquariumPlant? = null 3788 | plantNull.pull() // ok 3789 | 3790 | val plantNotNull = AquariumPlant("Black", 15) 3791 | plantNotNull.pull() // ok 3792 | } 3793 | 3794 | fun main() { 3795 | val plant = GreenLeafyPlant(50) 3796 | plant.print() // GreenLeafyPlant 3797 | /* 3798 | * Compiler just looks at the type of the variable 3799 | * So at compile time, AquariumPlant is an AquariumPlant 3800 | * So it will print "AquariumPlant" 3801 | * */ 3802 | val aquariumPlant: AquariumPlant = plant // Type is not "GreenLeafyPlant" anymore 3803 | aquariumPlant.print() // AquariumPlant 3804 | propertyExample() // true 3805 | nullableExample() 3806 | } 3807 | ``` 3808 | 3809 | ### Practice Time 3810 | ```kotlin 3811 | /* 3812 | It can be useful to know the weight of a book, for example, for shipping. The weight of a book can change because sometimes pages get torn, and the page count changes. While calculating the weight could be defined as a method, it’s more like a helper function. Besides, it would hurt a book's feelings to have a method that tears up its pages. 3813 | 3814 | Add a mutable property pages to Book. 3815 | Create an extension function on Book that returns the weight of a book as the page count multiplied by 1.5 grams. 3816 | Create another extension, tornPages(), that takes the number of torn pages as an argument and changes the page count of the book. 3817 | Write a class Puppy with a method playWithBook() that takes a book as an argument, and removes a random number of pages from the book. 3818 | Create a puppy and give it a book to play with, until there are no more pages. 3819 | 3820 | Note: If you don’t want to give your puppy a book, then create a puzzle toy class and fill it with treats. 3821 | */ 3822 | 3823 | // Solution Code 3824 | fun Book.weight() : Double { return (pages * 1.5) } 3825 | 3826 | fun Book.tornPages(torn: Int) = if (pages >= torn) pages -= torn else pages = 0 3827 | 3828 | class Puppy() { 3829 | fun playWithBook(book: Book) { 3830 | book.tornPages(Random().nextInt(12)) 3831 | } 3832 | } 3833 | 3834 | val puppy = Puppy() 3835 | val book = Book("Oliver Twist", "Charles Dickens", 1837, 540) 3836 | 3837 | while (book.pages > 0) { 3838 | puppy.playWithBook(book) 3839 | println("${book.pages} left in ${book.title}") 3840 | } 3841 | println("Sad puppy, no more pages in ${book.title}. ") 3842 | 3843 | /////////////////////////////////////////////////////////////////////////////// 3844 | // My Code 3845 | package lesson_5 3846 | import kotlin.random.Random 3847 | 3848 | const val maximumBooks = 100 3849 | 3850 | object Constants { 3851 | const val BASE_URL = "www.library.com/" 3852 | } 3853 | 3854 | // Extension function 3855 | fun Book.getWeight(): Double { 3856 | return 1.5 * pages 3857 | } 3858 | 3859 | // Extension function 3860 | fun Book.tornPages(tornPages: Int) { 3861 | pages -= tornPages 3862 | } 3863 | 3864 | // Extension function 3865 | fun Book.printNumOfPages() { 3866 | println("Number of pages: $pages") 3867 | } 3868 | 3869 | class Puppy() { 3870 | fun playWithBook(book: Book) { 3871 | val randPagesToTorn = Random.nextInt(1, book.pages + 1) 3872 | book.tornPages(randPagesToTorn) 3873 | } 3874 | } 3875 | 3876 | class Book( 3877 | val title: String, 3878 | val author: String, 3879 | val year: Int, 3880 | val maxBooks: Int = 0, 3881 | var pages: Int = 0 3882 | ) { 3883 | 3884 | companion object BookURL { 3885 | const val BASE_URL = "www.library.com/" 3886 | } 3887 | 3888 | fun printUrl() { 3889 | println( 3890 | Constants.BASE_URL + "/" 3891 | + title + "/" 3892 | + author + "/" 3893 | + year + "/" 3894 | + ".html" 3895 | ) 3896 | } 3897 | 3898 | fun getTitleAuthor(): Pair { 3899 | return Pair(title, author) 3900 | } 3901 | 3902 | fun getTitleAuthorYear(): Triple { 3903 | return Triple(title, author, year) 3904 | } 3905 | 3906 | fun canBorrow(): Boolean { 3907 | return maxBooks < maximumBooks 3908 | } 3909 | } 3910 | 3911 | fun main() { 3912 | val book = Book("Elon Musk", "Ashlee Vance", 2012) 3913 | 3914 | val (title, author) = book.getTitleAuthor() 3915 | val (title1, author1, year) = book.getTitleAuthorYear() 3916 | 3917 | println("Here is your book ${book.title} written by ${book.author} in ${book.year}") 3918 | 3919 | // 9. Quiz: Practice Time 3920 | book.pages = 100 3921 | val pupy = Puppy() 3922 | while (book.pages > 0) { 3923 | pupy.playWithBook(book) 3924 | book.printNumOfPages() 3925 | } 3926 | } 3927 | ``` 3928 | 3929 | ## Generic Classes 3930 | ```kotlin 3931 | // With generics we can make the list generic so it can hold any type of object 3932 | // It's like you make the tyoe a wildcard(Joker) that will fit many types 3933 | package Aquarium.generics 3934 | 3935 | // GENERICS 3936 | // How to declare a generic class with an upper bound and use it 3937 | fun main() { 3938 | genericExample() 3939 | } 3940 | 3941 | open class WaterSupply(var needsProcessed: Boolean) 3942 | 3943 | class TapWater : WaterSupply(true) { 3944 | fun addChemicalCleaners() { 3945 | needsProcessed = false 3946 | } 3947 | } 3948 | 3949 | class FishStoreWater : WaterSupply(false) 3950 | 3951 | class LakeWater : WaterSupply(true) { 3952 | fun filter() { 3953 | needsProcessed = false 3954 | } 3955 | } 3956 | // To ensure that our parameter must be nonnull but can still be any type 3957 | // We remove the question mark "Aquarium" and just say "Aquarium" 3958 | // This makes it impossible to pass null 3959 | class Aquarium(val waterSupply: T) { 3960 | fun addWater() { 3961 | // Check throws an error if condition is not true, continues otherwise 3962 | check(!waterSupply.needsProcessed) { "water supply needs processed" } 3963 | println("Adding water from $waterSupply") 3964 | } 3965 | } 3966 | 3967 | // But we really want to ensure our type is a water supply 3968 | // We can be as specific as we want with the generic constraint and replace 3969 | // any with the top of any type hierarchy we want to use 3970 | 3971 | fun genericExample() { 3972 | // val aquarium = Aquarium(TapWater()) // Type inference 3973 | val aquarium = Aquarium(TapWater()) 3974 | aquarium.waterSupply.addChemicalCleaners() 3975 | 3976 | // We are able to pass a string in as a water supply 3977 | // This is because type T doesn't have any bounds 3978 | // So it can actually be set to any type, that could be a problem 3979 | // val aquarium2 = Aquarium("string") 3980 | // println(aquarium2.waterSupply) 3981 | 3982 | // Another unexpected example is passing in nulls this also works 3983 | // I didn't really want to let WaterSupply be null 3984 | // Because T can be any type including nullable 3985 | // val aquarium3 = Aquarium(null) 3986 | // println(aquarium3.waterSupply) 3987 | 3988 | val aquarium4 = Aquarium(LakeWater()) 3989 | aquarium4.waterSupply.filter() 3990 | aquarium4.addWater() 3991 | } 3992 | ``` 3993 | 3994 | ### Practice Time | Generics 3995 | ```kotlin 3996 | /* 3997 | Using type hierarchies with generic classes follows a pretty basic pattern that we introduced in the previous segment. There was a lot of material introducing generics, but basically, when you are building them, it boils down to the following steps: 3998 | 3999 | Create a type/class hierarchy. The parent is non-specific and the sub-types/subclasses are specializations. 4000 | There is at least one shared property between the classes/types, and it has a different value depending on the subtype (otherwise, having the sub-types is pointless). 4001 | We then have a class that uses all the subtypes, and performs different actions depending on what the values of the subtype’s properties are. 4002 | 4003 | Let’s put this into practice using building materials and a building that needs certain amounts of those materials. 4004 | 4005 | Create a new package and file and call them Buildings. 4006 | Create a class BaseBuildingMaterial with a property numberNeeded that is set to 1. You always need 1 of the base material. 4007 | Create two subclasses, Wood and Brick. For BaseBuildingMaterial you need 4 units of wood or 8 units of brick. Now you have a type hierarchy. 4008 | Create a generic class Building that can take any building material as its argument, and only building materials. 4009 | A building always requires 100 base materials. Add a property baseMaterialsNeeded and set it to 100. 4010 | Add another property, actualMaterialsNeeded and use a one-line function to calculate this from numberNeeded of the passed-in material. 4011 | 4012 | Add a method build() that prints the type and number of materials needed. 4013 | Hint: Use reflection to get the class and simple name: instance::class.simpleName 4014 | 4015 | Create a main function and make a building using Wood. 4016 | If you did this correctly, running main() will print something like “400 Wood required”. 4017 | */ 4018 | 4019 | // Solution Code 4020 | open class BaseBuildingMaterial() { 4021 | open val numberNeeded = 1 4022 | } 4023 | 4024 | class Wood : BaseBuildingMaterial() { 4025 | override val numberNeeded = 4 4026 | } 4027 | 4028 | class Brick : BaseBuildingMaterial() { 4029 | override val numberNeeded = 8 4030 | } 4031 | 4032 | class Building(val buildingMaterial: T) { 4033 | val baseMaterialsNeeded = 100 4034 | val actualMaterialsNeeded = buildingMaterial.numberNeeded * baseMaterialsNeeded 4035 | 4036 | fun build() { 4037 | println(" $actualMaterialsNeeded ${buildingMaterial::class.simpleName} required") 4038 | } 4039 | } 4040 | 4041 | fun main(args: Array) { 4042 | Building(Wood()).build() 4043 | } 4044 | 4045 | // Output: 400 Wood required 4046 | ``` 4047 | 4048 | ### Generics in & out 4049 | ```kotlin 4050 | // Out types are type parameters that only ever occur in return values of functions or on val properties 4051 | // In types can be used anytime the generic is only used as an argument to functions 4052 | 4053 | /* More specifically 4054 | -> IN TYPES CAN ONLY BE PASSED INTO AN OBJECT (Can be used as parameter) 4055 | -> OUT TYPES CAN ONLY BE PASS OUT OF AN OBJECT OR RETURNED (Can be used as return values) 4056 | 4057 | Constructors can take out types as arguments but functions never can 4058 | */ 4059 | 4060 | package Aquarium.generics 4061 | // GENERICS 4062 | // How to declare a generic class with an upper bound and use it 4063 | fun main() { 4064 | genericExample() 4065 | } 4066 | 4067 | fun addItemTo(aquarium: Aquarium) = println("item added") 4068 | 4069 | open class WaterSupply(var needsProcessed: Boolean) 4070 | 4071 | class TapWater : WaterSupply(true) { 4072 | fun addChemicalCleaners() { 4073 | needsProcessed = false 4074 | } 4075 | } 4076 | 4077 | class FishStoreWater : WaterSupply(false) 4078 | 4079 | class LakeWater : WaterSupply(true) { 4080 | fun filter() { 4081 | needsProcessed = false 4082 | } 4083 | } 4084 | 4085 | // To ensure that our parameter must be nonnull but can still be any type 4086 | // We remove the question mark "Aquarium" and just say "Aquarium" 4087 | // This makes it impossible to pass null 4088 | class Aquarium(val waterSupply: T) { 4089 | fun addWater(cleaner: Cleaner) { 4090 | // Check throws an error if condition is not true, continues otherwise 4091 | if (waterSupply.needsProcessed) { 4092 | cleaner.clean(waterSupply) 4093 | } 4094 | 4095 | println("Adding water from $waterSupply") 4096 | } 4097 | } 4098 | 4099 | // But we really want to ensure our type is a water supply 4100 | // We can be as specific as we want with the generic constraint and replace 4101 | // any with the top of any type hierarchy we want to use 4102 | 4103 | interface Cleaner { 4104 | fun clean(waterSupply: T) 4105 | } 4106 | 4107 | class TapWaterCleaner: Cleaner { 4108 | override fun clean(waterSupply: TapWater) { 4109 | waterSupply.addChemicalCleaners() 4110 | } 4111 | } 4112 | 4113 | fun genericExample() { 4114 | // val aquarium = Aquarium(TapWater()) // Type inference 4115 | val aquarium = Aquarium(TapWater()) 4116 | aquarium.waterSupply.addChemicalCleaners() 4117 | 4118 | // We are able to pass a string in as a water supply 4119 | // This is because type T doesn't have any bounds 4120 | // So it can actually be set to any type, that could be a problem 4121 | // val aquarium2 = Aquarium("string") 4122 | // println(aquarium2.waterSupply) 4123 | 4124 | // Another unexpected example is passing in nulls this also works 4125 | // I didn't really want to let WaterSupply be null 4126 | // Because T can be any type including nullable 4127 | // val aquarium3 = Aquarium(null) 4128 | // println(aquarium3.waterSupply) 4129 | 4130 | val cleaner = TapWaterCleaner() 4131 | val aquarium4 = Aquarium(TapWater()) 4132 | aquarium4.addWater(cleaner) 4133 | 4134 | // If we did not put this "out" -> "class Aquarium", it gives error! 4135 | addItemTo(aquarium) 4136 | } 4137 | ``` 4138 | 4139 | ### Practice Time 4140 | ```kotlin 4141 | /* 4142 | That was a lot of explanations. Fortunately, IntelliJ gives you hints as to whether something should be an in or out type in your current code. 4143 | 4144 | Look at the code from the previous practice and consider whether it can be an in type or an out type. 4145 | Notice that the parameter is underlined gray, and if you hover over T, IntelliJ will suggest to make it an “out” type. 4146 | */ 4147 | 4148 | class Building(val buildingMaterial: T) 4149 | ``` 4150 | 4151 | ### Practice Time | Generic Functions 4152 | ```kotlin 4153 | // We can use generic functions for methods too 4154 | package Aquarium.generics 4155 | 4156 | // GENERICS 4157 | // How to declare a generic class with an upper bound and use it 4158 | fun main() { 4159 | genericExample() 4160 | } 4161 | 4162 | // Generic Function Example 4163 | fun isWaterClean(aquarium: Aquarium) { 4164 | println("Aquarium water is clean ${aquarium.waterSupply.needsProcessed}") 4165 | } 4166 | 4167 | fun addItemTo(aquarium: Aquarium) = println("item added") 4168 | 4169 | open class WaterSupply(var needsProcessed: Boolean) 4170 | 4171 | class TapWater : WaterSupply(true) { 4172 | fun addChemicalCleaners() { 4173 | needsProcessed = false 4174 | } 4175 | } 4176 | 4177 | class FishStoreWater : WaterSupply(false) 4178 | 4179 | class LakeWater : WaterSupply(true) { 4180 | fun filter() { 4181 | needsProcessed = false 4182 | } 4183 | } 4184 | // To ensure that our parameter must be nonnull but can still be any type 4185 | // We remove the question mark "Aquarium" and just say "Aquarium" 4186 | // This makes it impossible to pass null 4187 | class Aquarium(val waterSupply: T) { 4188 | fun addWater(cleaner: Cleaner) { 4189 | // Check throws an error if condition is not true, continues otherwise 4190 | if (waterSupply.needsProcessed) { 4191 | cleaner.clean(waterSupply) 4192 | } 4193 | println("Adding water from $waterSupply") 4194 | } 4195 | // Declare a parameter type parameter R, but make it a real type 4196 | inline fun hasWaterSupplyOfType() = waterSupply is R 4197 | } 4198 | 4199 | // But we really want to ensure our type is a water supply 4200 | // We can be as specific as we want with the generic constraint and replace 4201 | // any with the top of any type hierarchy we want to use 4202 | 4203 | interface Cleaner { 4204 | fun clean(waterSupply: T) 4205 | } 4206 | 4207 | class TapWaterCleaner: Cleaner { 4208 | override fun clean(waterSupply: TapWater) { 4209 | waterSupply.addChemicalCleaners() 4210 | } 4211 | } 4212 | 4213 | fun genericExample() { 4214 | // val aquarium = Aquarium(TapWater()) // Type inference 4215 | val aquarium = Aquarium(TapWater()) 4216 | aquarium.waterSupply.addChemicalCleaners() 4217 | 4218 | // We are able to pass a string in as a water supply 4219 | // This is because type T doesn't have any bounds 4220 | // So it can actually be set to any type, that could be a problem 4221 | // val aquarium2 = Aquarium("string") 4222 | // println(aquarium2.waterSupply) 4223 | 4224 | // Another unexpected example is passing in nulls this also works 4225 | // I didn't really want to let WaterSupply be null 4226 | // Because T can be any type including nullable 4227 | // val aquarium3 = Aquarium(null) 4228 | // println(aquarium3.waterSupply) 4229 | 4230 | val cleaner = TapWaterCleaner() 4231 | val aquarium4 = Aquarium(TapWater()) 4232 | aquarium4.addWater(cleaner) 4233 | 4234 | // If we did not put this "out" -> "class Aquarium", it gives error! 4235 | addItemTo(aquarium) 4236 | 4237 | isWaterClean(aquarium) 4238 | 4239 | aquarium4.hasWaterSupplyOfType() // True 4240 | aquarium4.waterSupply.isType() // False 4241 | } 4242 | 4243 | inline fun WaterSupply.isType() = this is T 4244 | ``` 4245 | 4246 | ### Practice Time 4247 | ```kotlin 4248 | /* 4249 | Create a generic function for type BaseBuildingMaterial and call it isSmallBuilding, which takes a Building with a building material T as an argument. If the materials needed are less than 500, print "small building", otherwise, print "large building". 4250 | Note: For this function, IntelliJ recommends not to inline the function. Generally, when you create a generic function, follow the IDE's recommendation about inlining. 4251 | */ 4252 | 4253 | fun isSmallBuilding(building: Building) { 4254 | if (building.actualMaterialsNeeded < 500) println("Small building") 4255 | else println("large building") 4256 | } 4257 | isSmallBuilding(Building(Brick())) 4258 | ``` 4259 | 4260 | ## Annotations 4261 | ```kotlin 4262 | /* 4263 | Annotations are a means of attaching metadata to code, that is, the Annotations are read by the compiler, and used to generate code or even logic Annotations are not Kotlin specific but Kotlin offers some useful annotations 4264 | 4265 | // Annotations go right before the thing that is Annotated, and most things can be annotated: Classes, Functions, Methods, and even control structures 4266 | // Some annotations can even take arguments 4267 | // They're really useful if you are exporting Kotlin to Java, but otherwise you don't need them that often 4268 | */ 4269 | annotation class ImAPlant 4270 | 4271 | @Target(AnnotationTarget.PROPERTY_GETTER) 4272 | annotation class OnGet 4273 | 4274 | @Target(AnnotationTarget.PROPERTY_SETTER) 4275 | annotation class OnSet 4276 | 4277 | @ImAPlant class Plant { 4278 | fun trim() {} 4279 | fun fertilize() {} 4280 | 4281 | @get:OnGet 4282 | val isGrowing: Boolean = true 4283 | 4284 | @set:OnSet 4285 | var needsFood: Boolean = false 4286 | } 4287 | 4288 | fun reflections() { 4289 | val classObj = Plant::class 4290 | 4291 | // print all annotations 4292 | for (annotation in classObj.annotations) { 4293 | println(annotation.annotationClass.simpleName) 4294 | } 4295 | 4296 | // find one annotation, or null 4297 | val annotated = classObj 4298 | 4299 | annotated?.apply { 4300 | println("Found a plant annotation!") 4301 | } 4302 | } 4303 | ``` 4304 | 4305 | ## Labeled Breaks 4306 | ```kotlin 4307 | // Kotlin has several ways of controlling the flow 4308 | // Kotlin gives you additional control over loops with what's called a labeled break 4309 | // Any expression in Kotlin may be marked with a label 4310 | // Labeled break can be used (labeled form) to terminate the desired loop (can be an outer loop) 4311 | 4312 | fun main() { 4313 | for (i in 1..10) { 4314 | for (j in 1..10) { 4315 | if (i > 5) { 4316 | break 4317 | } 4318 | } 4319 | } 4320 | // Labeled Breaks 4321 | loop@ for (i in 1..10) { 4322 | for (j in 1..10) { 4323 | if (i > 5) { 4324 | println() 4325 | break@loop 4326 | } 4327 | } 4328 | } // break@loop will come here 4329 | // then ends the first loop 4330 | // different than just "break" keyword 4331 | } 4332 | ``` 4333 | 4334 | ### Lambdas Recap 4335 | ```kotlin 4336 | // A lambda is an anonymous function, a function without a name 4337 | data class Fish(val name: String) 4338 | fun main() { 4339 | // Lambda function 4340 | { println("Hello Lambda!") }() 4341 | 4342 | // We can assign lambda to a variable 4343 | val waterFilter = { dirty: Int -> dirty / 2 } 4344 | // Run lambda function 4345 | println(waterFilter(30)) 4346 | 4347 | val myFish = listOf(Fish("Flipper"), Fish("Moby Dick"), Fish("Dory")) 4348 | 4349 | // "joinToString" creates a string from all the names of the element 4350 | // in the list separated using this applied seperator 4351 | val list = myFish.filter { it.name.contains('i') }.joinToString(" ") { it.name } 4352 | println(list) 4353 | } 4354 | ``` 4355 | 4356 | ### Practice Time | Game 4357 | ```kotlin 4358 | /* 4359 | In this practice, you are going to write the the first part of a higher-order functions game. You will implement everything, except the higher-order functions. Let’s get started. 4360 | 4361 | Create a new file. 4362 | Create an enum class, Directions, that has the directions NORTH, SOUTH, EAST and WEST, as well as START, and END. 4363 | Create a class Game. 4364 | Inside Game, declare a var, path, that is a mutable list of Direction. Initialize it with one element, START. 4365 | Create 4 lambdas, north, south, east, and west, that add the respective direction to the path. 4366 | Add another lambda, end, that: 4367 | Adds END to path 4368 | Prints “Game Over” 4369 | Prints the path 4370 | Clears the path 4371 | Returns false 4372 | Create a main function. 4373 | Inside main(), create an instance of Game. 4374 | To test your code so far, in main() print the path, then invoke north, east, south, west, and end. Finally, print the path again. 4375 | 4376 | You should see this output: 4377 | 4378 | > [START] 4379 | Game Over: [START, NORTH, SOUTH, EAST, WEST, END] 4380 | [] 4381 | 4382 | You will finish your game as the last practice in this course. 4383 | */ 4384 | 4385 | //////////////////////////////////////////////////////////////////////////////////// 4386 | // Solution Code 4387 | enum class Direction { 4388 | NORTH, EAST, WEST, SOUTH, START, END 4389 | } 4390 | 4391 | class Game { 4392 | var path = mutableListOf(Direction.START) 4393 | val north = { path.add(Direction.NORTH) } 4394 | val south = { path.add(Direction.SOUTH) } 4395 | val east = { path.add(Direction.EAST) } 4396 | val west = { path.add(Direction.WEST) } 4397 | val end = { path.add(Direction.END); println("Game Over: $path"); path.clear(); false } 4398 | } 4399 | 4400 | fun main(args: Array) { 4401 | val game = Game() 4402 | println(game.path) 4403 | game.north() 4404 | game.south() 4405 | game.east() 4406 | game.west() 4407 | game.end() 4408 | println(game.path) 4409 | } 4410 | 4411 | /////////////////////////////////////////////////////////////////////////////////// 4412 | // My Code 4413 | package lesson_6 4414 | enum class Directions { 4415 | START, END, 4416 | NORTH, SOUTH, EAST, WEST 4417 | } 4418 | 4419 | fun main() { 4420 | val game = Game() 4421 | println(game.path) 4422 | game.east() // Don't forget the add parentheses "( )" at the end of Lambda function 4423 | game.north() 4424 | game.south() 4425 | game.west() 4426 | game.end() 4427 | println(game.path) 4428 | } 4429 | 4430 | class Game { 4431 | var path: MutableList = mutableListOf(Directions.START) 4432 | val north = { path.add(Directions.NORTH) } 4433 | val south = { path.add(Directions.SOUTH) } 4434 | val east = { this.path.add(Directions.EAST) } 4435 | val west = { path.add(Directions.WEST) } 4436 | val end = { 4437 | path.add(Directions.END) 4438 | println("Game Over: $path") 4439 | path.clear() 4440 | false 4441 | } 4442 | } 4443 | ``` 4444 | 4445 | ## Higher-order Functions 4446 | ```kotlin 4447 | /* 4448 | * WRITING HIGHER ORDER FUNCTIONS WITH EXTENSIONS LAMBDAS 4449 | * is the most advanced part of the Kotlin Language 4450 | * */ 4451 | 4452 | // There are tons of built in functions in the Kotlin standard library that use extension lambdas 4453 | // A higher-order function is a function that takes another function as parameter and/or returns a function. 4454 | data class Fish(var name: String) 4455 | 4456 | fun main() { 4457 | fishExamples() 4458 | } 4459 | 4460 | fun fishExamples() { 4461 | val fish = Fish("splashy") 4462 | 4463 | // "run" is an extension that works with all data types 4464 | // It takes one lambda as its argument and returns the result of executing the lambda 4465 | println(fish.run { "$name:)" }) 4466 | 4467 | // "apply" is similar to run and can also be used on all data types 4468 | // but unlike "run" which returns the result of the block function 4469 | // "apply" returns the object it's applied to, so if we applied it to a fish 4470 | // It will return the fish object 4471 | // It turns out that "apply" can be really useful for calling functions 4472 | // on a newly created object 4473 | println(fish.apply{}) 4474 | 4475 | val fish2 = Fish("Melo").apply { name = "Genesis" } 4476 | println(fish2.name) 4477 | 4478 | /* 4479 | * So the difference is that "run" returns the result of executing the lambda 4480 | * while "apply" returns the object after the lambda has been applied 4481 | * This is a really common patter for initializing objects 4482 | * */ 4483 | 4484 | // There is also "let" 4485 | // "let" returns a copy of the changed objects 4486 | // Let is particularly useful for chaining manipulations together 4487 | println(fish.let { it.name.capitalize() } 4488 | .let { it + "fish" } 4489 | .let { it.length } 4490 | .let { it + 35 }) 4491 | 4492 | // Here we're saying 4493 | // With fish.name call this.uppercase() 4494 | // Under the hood, with is a higher order function 4495 | with (fish.name) { 4496 | // We don't actually need this, because it's implicit 4497 | // this.uppercase() 4498 | println(capitalize()) // SPLASHY 4499 | // capitalize returns a copy of the pass in string 4500 | // It does not change the original string 4501 | } 4502 | 4503 | // We can replace "with" with "myWith" 4504 | // fish.name is our named argument and 4505 | // uppercase() is our block function 4506 | myWith(fish.name) { 4507 | // block function 4508 | println(uppercase()) // SPLASHY 4509 | // uppercase returns a copy of the pass in string 4510 | // It does not change the original string 4511 | } 4512 | println("Original fish name: ${fish.name}") 4513 | } 4514 | 4515 | // "block" is now an extension function on a string object 4516 | // And we can apply it to a string 4517 | fun myWith(name: String, block: String.() -> Unit) { 4518 | // We take name and call block on it 4519 | name.block() 4520 | } 4521 | ``` 4522 | 4523 | ### Practice Time 4524 | ```kotlin 4525 | /* 4526 | Create an extension on List using a higher order function that returns all the numbers in the list that are divisible by 3. Start by creating an extension function on List that takes an lambda on Int and applies it to each item in the list. When the lambda returns zero, include the item in the output. For example, this list: 4527 | 4528 | val numbers = listOf(1,2,3,4,5,6,7,8,9,0) 4529 | 4530 | Should return 4531 | > [3, 6, 9, 0] 4532 | */ 4533 | 4534 | // Solution Code 4535 | fun main() { 4536 | val numbers = listOf(1,2,3,4,5,6,7,8,9,0) 4537 | println(numbers.divisibleBy3()) 4538 | println(numbers.divisibleBy { it.rem(3) }) 4539 | } 4540 | 4541 | fun List.divisibleBy(block: (Int) -> Int): List { 4542 | val result = mutableListOf() 4543 | for (item in this) { 4544 | if (block(item) == 0) { 4545 | result.add(item) 4546 | } 4547 | } 4548 | return result 4549 | } 4550 | 4551 | // My Code 4552 | fun List.divisibleBy3(): List { 4553 | return this.filter { it % 3 == 0 } 4554 | } 4555 | ``` 4556 | 4557 | ## Inline 4558 | 4559 | ```kotlin 4560 | package Aquarium5 4561 | fun main() { 4562 | fishExamples2() 4563 | } 4564 | 4565 | data class Fish2(var name: String) 4566 | 4567 | fun fishExamples2() { 4568 | val fish = Fish("splashy") 4569 | 4570 | // PROBLEM HERE! 4571 | // Every time we call myWith, Kotlin will make a new lambda object! 4572 | // Which takes CPU time and memory! 4573 | // Lambdas are objects 4574 | // A Lambda expression is an instance of a function interface 4575 | // which is itself a subtype of object 4576 | myWith(fish.name) { 4577 | println(uppercase()) // SPLASHY 4578 | } 4579 | 4580 | // To help understand, we can write it out longhand like this 4581 | // Without inline an object is created every call to myWith 4582 | myWith(fish.name, object : Function1 { 4583 | override fun invoke(name: String) { 4584 | name.capitalize() 4585 | } 4586 | }) 4587 | // When the inline transform is applied, 4588 | // the call to the lambda is replaced with the contents of the 4589 | // function body of the lambda 4590 | // In our myWith example when we apply the transform 4591 | // capitalize is called directly on fish.name 4592 | // This is really important 4593 | // Kotlin let's us define Lambda-based APIs with zero overhead 4594 | // It won't even pay the cost of calling the function myWith 4595 | // since it gets inlined 4596 | // Inlining large functions does increase your code size 4597 | // so it's best used for simple functions like myWith 4598 | 4599 | // with inline no object is created and lambda body is inlined here 4600 | fish.name.capitalize() 4601 | } 4602 | 4603 | // To fix this problem, Kotlin let's us define myWith as inline 4604 | // That is a promise that every time myWith is called 4605 | // it will actually transform the source code to inLine, the function 4606 | // That is, the compiler will change the code to replace the Lambda 4607 | // with the instructions inside the Lambda, that means zero overhead 4608 | inline fun myWith2(name: String, block: String.() -> Unit) { 4609 | name.block() 4610 | } 4611 | ``` 4612 | 4613 | ### Practice Time 4614 | ```kotlin 4615 | /* 4616 | In this practice, you will finish your simple game using higher-order functions, that is, a function that takes functions as an argument. 4617 | 4618 | In the game class, create a function move() that takes an argument called where, which is a lambda with no arguments that returns Unit. 4619 | 4620 | Hint: Declaring a function that takes a lambda as its argument: 4621 | 4622 | fun move(where: () -> Boolean ) 4623 | 4624 | Inside move(), invoke the passed-in lambda. 4625 | In the Game class, create a function makeMove() that takes a nullable String argument and returns nothing. 4626 | 4627 | Inside makeMove, test whether the String is any of the 4 directions and invoke move() with the corresponding lambda. Otherwise, invoke move() with end. 4628 | 4629 | Hint: You can call the function like this: 4630 | 4631 | move(north) 4632 | 4633 | In main() add a while loop that is always true. 4634 | Inside the loop, print instructions to the player: 4635 | 4636 | print("Enter a direction: n/s/e/w:") 4637 | 4638 | Call makeMove() with the contents of the input from the user via readLine() 4639 | Remove the code for testing the first version of your game. 4640 | Run your program. 4641 | 4642 | Challenge: 4643 | 4644 | Create a simple “map” for your game, and when the user moves, show a description of their location. Consider the following: 4645 | 4646 | Use a Location class that takes a default width and height to track location. 4x4 is pretty manageable. 4647 | You can create a matrix like this: 4648 | 4649 | val map = Array(width) { arrayOfNulls(height) } 4650 | 4651 | Use an init block to initialize your map with descriptions for each location. 4652 | When you move() also updateLocation(). There is some math involved to prevent null-pointer exceptions and keep the user from walking off the map. rem() and absoluteValue come handy. 4653 | When you are done, zip up the code and send it to a friend to try your first Kotlin game. 4654 | */ 4655 | 4656 | // Solution Code 4657 | fun move(where: () -> Boolean ) { 4658 | where.invoke() 4659 | } 4660 | 4661 | fun makeMove(command:String?) { 4662 | if (command.equals("n")) move(north) 4663 | else if (command.equals("s")) move(south) 4664 | else if (command.equals("e")) move(east) 4665 | else if (command.equals("w")) move(west) 4666 | else move(end) 4667 | } 4668 | 4669 | while (true) { 4670 | print("Enter a direction: n/s/e/w: ") 4671 | game.makeMove(readLine()) 4672 | } 4673 | /////////////////////////////////////////////////////////////////////////////////// 4674 | 4675 | /////////////////////////////////////////////////////////////////////////////////// 4676 | // My Solution 4677 | enum class Directions { 4678 | START, END, 4679 | NORTH, SOUTH, EAST, WEST 4680 | } 4681 | 4682 | class Map(val width: Int = 5, val height: Int = 5) { 4683 | // 1 2 3 4 5 4684 | // 1 . . . . . 4685 | // 2 . . . . . 4686 | // 3 . . C . . 4687 | // 4 . . . . . 4688 | // 5 . . . . . 4689 | private var location = mutableListOf(3, 3) // Center of the map { x, y } 4690 | fun updateLocation(direction: String?): Boolean { 4691 | val newLocation = location 4692 | when (direction) { 4693 | "n" -> newLocation[0] += 1 4694 | "e" -> newLocation[1] += 1 4695 | "s" -> newLocation[0] -= 1 4696 | "w" -> newLocation[1] -= 1 4697 | } 4698 | if (isInside(newLocation[0], newLocation[1])) { 4699 | location = newLocation 4700 | printLocation() 4701 | return true 4702 | } 4703 | else { 4704 | println("Oops// You cannot move outside the map!") 4705 | return false 4706 | } 4707 | } 4708 | 4709 | private fun printLocation() { 4710 | println("X: ${location[0]}, Y: ${location[1]}") 4711 | } 4712 | 4713 | private fun isInside(x: Int, y: Int): Boolean { 4714 | if (x < 1 || x > 5 || y < 1 || y > 5) { 4715 | return false 4716 | } 4717 | return true 4718 | } 4719 | } 4720 | 4721 | fun main() { 4722 | val game = Game() 4723 | val map = Map() 4724 | var validMove = true 4725 | while (validMove) { 4726 | print("Enter a direction: n/s/e/w: ") 4727 | val direction = readLine() 4728 | validMove = map.updateLocation(direction) 4729 | if (validMove) 4730 | game.makeMove(direction) 4731 | } 4732 | game.end() 4733 | } 4734 | 4735 | class Game { 4736 | var path: MutableList = mutableListOf(Directions.START) 4737 | val north = { path.add(Directions.NORTH) } 4738 | val south = { path.add(Directions.SOUTH) } 4739 | val east = { path.add(Directions.EAST) } 4740 | val west = { path.add(Directions.WEST) } 4741 | val end = { 4742 | path.add(Directions.END) 4743 | println("Game Over: $path") 4744 | path.clear() 4745 | false 4746 | } 4747 | 4748 | private fun move(where: () -> Boolean) { 4749 | // where.invoke() // Alternative 4750 | where() 4751 | } 4752 | 4753 | fun makeMove(direction: String?) { 4754 | when (direction) { 4755 | "n" -> move(north) 4756 | "s" -> move(south) 4757 | "e" -> move(east) 4758 | "w" -> move(west) 4759 | else -> move(end) 4760 | } 4761 | } 4762 | } 4763 | ``` 4764 | 4765 | ## SAM - Single Abstract Method 4766 | ```kotlin 4767 | // You'll run into SAM all the time in APIs written in the Java 4768 | 4769 | ////////////////////////////////////// JAVA //////////////////////////////////// 4770 | package SAM; 4771 | 4772 | // Java Code 4773 | class JavaRun { 4774 | public static void runNow(Runnable runnable) { 4775 | runnable.run(); 4776 | } 4777 | } 4778 | ///////////////////////////////////////////////////////////////////////////////// 4779 | 4780 | /////////////////////////////////// KOTLIN ////////////////////////////////////// 4781 | package SAM 4782 | /* 4783 | * SAM - Single Abstract Method 4784 | You'll run into SAM all the time in APIs written in the Java 4785 | * */ 4786 | 4787 | /* 4788 | * Runnable and callable are two examples 4789 | * Basically, SAM just means an interface with one method on it, That's it 4790 | * In Kotlin, we have to call functions that take SAM 4791 | * as parameters all the time 4792 | * */ 4793 | //interface Runnable { 4794 | // fun run() 4795 | //} 4796 | // 4797 | //interface Callable { 4798 | // fun call(): T 4799 | //} 4800 | //interface Runnable { 4801 | // fun run() 4802 | //} 4803 | // 4804 | //interface Callable { 4805 | // fun call(): T 4806 | //} 4807 | 4808 | // Int Kotlin, we can pass a lambda in place of a SAM 4809 | fun example2() { 4810 | JavaRun.runNow { 4811 | println("Passing a lambda as a runnable") 4812 | } 4813 | } 4814 | 4815 | fun example() { 4816 | val runnable = object: Runnable { 4817 | override fun run() { 4818 | println("I'm a runnable") 4819 | } 4820 | } 4821 | JavaRun.runNow(runnable) 4822 | } 4823 | ``` 4824 | 4825 | Buy Me A Coffee 4826 | 4827 | _Your support means a lot to me to continue the development of open-source projects like this._ 4828 | 4829 | --------------------------------------------------------------------------------