├── 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 | 
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 |
4826 |
4827 | _Your support means a lot to me to continue the development of open-source projects like this._
4828 |
4829 |
--------------------------------------------------------------------------------