Kotlin Beginners Notes
Kotlin Beginners Notes
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 :)
Table of Contents
Lesson 1 & 2 Introduction | Kotlin Basics
Lesson 3 | Functions
Lesson 4 | Classes
Lesson 5 | Kotlin Essentials: Beyond The Basics
import kotlin.text.*
// Main function
fun main(args: Array<String>) {
printHello ()
}
Operators, -, +, /, *
// Returns integer
println(1 + 1) // Prints: 2
println(53 - 3) // Prints: 50
println(50 / 10) // Prints: 5
println(1 / 2) // Prints: 0
println(6 * 50) // Prints: 300
// Returns double
println(1.0 / 2.0) // Prints: 0.5
println(1.0 / 2) // Prints: 0.5
println(2 / 2.0) // Prints: 1.0
Boxing
// Boxing describes the process of converting a primitive value to an object and unbox
// All numerical types in Kotlin have a supertype called Number
// Store value one in a variable of type Number
// It'll need to be placed in an object wrapper
// This is called boxing
val boxed: Number = 1
^ ^ ^
name type value
// With "var" you can assign a value, and then you can change it
var fish = 2
fish = 50
// Type is inferred meaning that compiler can figure out the type from the context
// Even so the type is inferred, it becomes fixed at compile time,
// So you cannot change a type of a varible in kotlin once it's type has been determin
fish = "Bubbles" // ERROR
// Number types won't implicitly convert to other types, so you can't assign
// A short value to a long variable or a byte to an int
val b: Byte = 1
val i: Int = b // ERROR Type Mismatch
// You can speficy long constants in a format that makes sense to you
// The type is inferred by Kotlin
Nullability
// Use the question mark operator to indicate that a variable can be null
var rocks: Int? = null
// You can allow for the list to be null, but if it is not null its elements cannot be
var evenMoreFish: List<String>? = null
var definitelyFish: List<String?>? = null
// Samples
// Creating List
var names5: List<String> = listOf("asd", "adsad3")
// To allow nulls, you can declare a variable as nullable string, written String?:
var b: String? = "abc" // can be set null
b = null // ok
val l = b.length // error: variable 'b' can be null
print(b)
// Option 2: Safe calls, Your second option is the safe call operator, written "?."
val a = "Kotlin"
val b: String? = null
println(b?.length)
println(a?.length) // Unnecessary safe call
// This returns "b.length" if "b is not null", and "null" otherwise. The type of this
// "b?.length" is equal to "if (b != null) b.length else -1"
/*If the expression to the left of ?: is not null, the elvis operator returns it, othe
/*Since throw and return are expressions in Kotlin, they can also be used on the right
*/
fun foo(node: Node): String? {
val parent = node.getParent() ?: return null
val name = node.getName() ?: throw IllegalArgumentException("name expected"
// ...
}
The " ! " Operator & Not-null Assertion Operator " !! "
/*This is unsafe nullable type (T?) conversion to a non-nullable type (T), !// will th
/*The third option is for NPE-lovers: the not-null assertion operator (!!) converts an
val l = b!!.length
/*Thus, if you want an NPE, you can have it, but you have to ask for it explicitly, an
Safe casts
/*Regular casts may result into a ClassCastException if the object is not of the targe
// Safe Casts
var a = "1"
var b = 5
var aInt: Int? = a as? Int
var bInt: Int? = b as? Int
print(aInt) // null
print(bInt) // 5
/*If you have a collection of elements of a nullable type and want to filter non-null
val nullableList: List<Int?> = listOf(1, 2, null, 4)
val intList: List<Int> = nullableList.filterNotNull()
// You can do some cool null testing with the question mark operator saving you the pa
// You can check if an object or variable is non null before accessing one of its meth
val fishFoodTreats = 5
return fishFoodTreats?.dec() ?: 0
// You can also chain null tests in an expression
/*If "fishFoodTreats" is not null use a treat and return a new value and otherwise ret
/*
Solve the following using the operator methods in one line of code.
If you start with 2 fish, and they breed twice, producing 71 offspring the first time,
// Solution Code
2.plus(71).plus(233).minus(13).div(30).plus(1)
// Bonus question: If you've noticed, all fish numbers above are prime.
/*Create a String variable rainbowColor, set its color value, then change it.
Create a variable blackColor whose value cannot be changed once assigned. Try changing
var rainbowColor: String = "green"
rainbowColor = "blue"
//Alternative
var rainbowColor = "green"
rainbowColor = "blue"
val blackColor = "black"
blackColor = "white" // Error
// Try to set rainbowColor to null. Declare two variables, greenColor and blueColor. U
// Create a list with two elements that are null; do it in two different ways.
// Next, create a list where the list is null.
// list with two null items
var list = listOf(null,null)
var list1: List<Int?> = listOf(null, null)
// Create a nullable integer variable called nullTest, and set it to null. Use a null-
// Hint: Use the Elvis operator.
Strings
"Hello Fish" // Hello Fish
// Concatenation
"hello" + "fish" // hello fish
val numberOfFish = 5
val numberOfPlants = 12
"I have $numberOfFish fish and $numberOfPlants plants" // I have 5 fish and 12 plants
// Here two numbers get added first then the result will be printed
"I have ${numberOfFish + numberOfPlants} fish and plants" // I have 17 fish and plants
val A = "A"
val B = "Z"
println(A < B) // true
println(A > B) // false
If-Else Blocks
val numberOfFish = 50
val numberOfPlants = 23
if (numberOfFish > numberOfPlants) {
println("Good Ratio!")
} else {
println("unhealthy ratio")
}
Ranges
val fish = 50
// .. -> inclusively 1 <= fish <= 50
if (fish in 1..50) {
println(fish.toString() + " is in the range 1 <= fish <= 50!")
}
// until -> exclusively 1 <= fish < 50
if (fish in 1 until 50) {
println(fish)
} else {
println(fish.toString() + " is not in the range 1 <= fish < 50!")
}
When
val numberOfFish = 50
when (numberOfFish) {
in 1..50 -> println("Full tank")
} // Output: Full tank
Practice Time
Practice Time
/*when statements in Kotlin are like case or switch statements in other languages.
Create a when statement with three comparisons:
// If val variable value is a reference, then you cannot assign it a different referen
// val only applies to the reference and it doesn't make the object it points to immut
// Here we cannot assign a different list in myList but we can manipulate the elemets
While loop
var x = 5
while (x > 0) {
print("$x ") // 5 4 3 2 1
x--
}
// Arrays work pretty much as you'd expect with some cool additions
// Good practice is to prefer using "lists" over "arrays" everywhere except for perfor
val a1 = arrayOf("a")
val a2 = arrayOf("a")
var y = (a1 == a2) // => false
listOf vs mutableListOf
/*
List: READ-ONLY
MutableList: READ/WRITE
You can modify a MutableList: change, remove, add... its elements.
In a List you can only read them.
// Create an array
val school = arrayOf("fish", "tuna", "salmon")
// This does not prints the all elements, it prints the array address instead
val mixedArray = arrayOf("fish", 2, 's', 0.0)
print(mixedArray) // [Ljava.lang.Object;@66d3c617
Nesting Arrays
// Mutablelists
val intList = mutableListOf<Int>(5, 12)
val stringList = mutableListOf<String>("1","2","3","4")
// Array
val intList = arrayOf<Int>(5, 12)
val stringList = arrayOf<String>("1","2","3","4")
// Sized array
var table = Array<String>(words.size) {""}
val literals = arrayOf<String>("January", "February", "March")
// Create 2D Array
val grid = Array(rows) { Array(cols) { Any() } }
// Arrays
val intArray = arrayOf(1,2,3)
val intArray2: Array<Int> = arrayOf(1,2,3)
val intArray3 = intArrayOf(1,2,3)
val charArray = charArrayOf('a', 'b', 'c')
val intArray = arrayOf(1,2,3)
val intArray2: Array<Int> = arrayOf(1,2,3)
val intArray3 = intArrayOf(1,2,3)
val charArray = charArrayOf('a', 'b', 'c')
val stringArray = arrayOf("genesis", "melo")
val stringArray2: Array<String> = arrayOf("genesis", "melo")
val stringOrNulls = arrayOfNulls<String>(5) // returns Array<String?>
val stringOrNulls2: Array<String?> = arrayOf("", null)// returns Array<String?>
var emptyStringArray: Array<String> = emptyArray()
var emptyStringArray2: Array<String> = arrayOf()
var sizedEmptyArray = Array(4){"s"} // {"s", "s", "s", "s"}
// You can read this as initialize an array of 5 elements, assign each item to its ind
val array = Array(5) {it * 2}
// OR -> val array = List(5) {it * 2}
println(array.joinToString()) // 0, 2, 4, 6, 8
for (i in 0..swarm.size - 1) {
print("$i ") // 0 1 2 3
}
for (i in swarm.indices) {
print("$i ") // 0 1 2 3
}
Quiz
// Read the code below, and then select the correct array initialization that will dis
val array = // initalize array here
val sizes = arrayOf("byte", "kilobyte", "megabyte", "gigabyte",
"terabyte", "petabyte", "exabyte")
for ((i, value) in array.withIndex()) {
println("1 ${sizes[i]} = ${value.toLong()} bytes")
}
// Output:
1 byte = 1 bytes
1 kilobyte = 1000 bytes
1 megabyte = 1000000 bytes
1 gigabyte = 1000000000 bytes
1 terabyte = 1000000000000 bytes
1 petabyte = 1000000000000000 bytes
1 exabyte = 1000000000000000000 bytes
Quiz
/* Which of these options are good reasons to explicitly make a list immutable? There
-> It reduces errors in general.
-> Prevents accidental changing of objects that were meant to be unchangeable.
-> In a multi-threaded environment, makes the variable thread safe, because once it ha
// Answer: Immutable variables are the safest option when you know that a variable wil
Practice Time
/*Looping over arrays and lists is a fundamental technique that has a lot of flexibili
Basic example
Create an integer array of numbers called numbers, from 11 to 15.
Create an empty mutable list for Strings.
Write a for loop that loops over the array and adds the string representation of each
Challenge example
How can you use a for loop to create (a list of) the numbers between 0 and 100 that ar
// Solution Code
var list3 : MutableList<Int> = mutableListOf()
for (i in 0..100 step 7) list3.add(i)
print(list3)
[0, 7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84, 91, 98]
// OR
// My Solution
val numbers = Array<Int>(5) { it + 11 }
println(numbers.asList())
var mutableList = mutableListOf<String>()
for (number in numbers) {
mutableList.add(number.toString())
}
println(mutableList)
// Challange Example
for (number in 7..100 step 7) {
println("$number ")
}
listOf(1, 2, 3)
// [1, 2, 3]
val l4 = "word-salad".toList()
// [w, o, r, d, -, s, a, l, a, d]
val m1 = mapOf(
1 to "Gold",
2 to "Silver",
3 to "Bronze"
).toList()
// [(1, Gold), (2, Silver), (3, Bronze)]
generateSequence {
Random.nextInt(100).takeIf { it > 30 }
}.toList()
// [45, 75, 74, 31, 54, 36, 63]
(0..5).toList()
// [0, 1, 2, 3, 4, 5]
mutList[0] = 5
mutList
// [5, 2, 3]
otherList
// [1, 2, 3]
myList.getOrNull(3)
val test = myList.getOrElse(3) {
println("There is no index $it")
":("
}
// There is no index 3
// :(
myList1.slice(0..3)
// [a, b, c, d]
myList1.slice(0..myList1.lastIndex step 2)
// [a, c, e]
myList1.slice(2 downTo 0)
// [c, b, a]
mutableListOf(1, 2, 3)
// [1, 2, 3]
(0..5).toMutableList()
// [0, 1, 2, 3, 4, 5]
listOf(1, 2, 3).toMutableList()
// [1, 2, 3]
val m = mutableListOf(1, 2, 3)
m.add(4)
m += 4
// [1, 2, 3, 4, 4]
m.add(2, 10)
// [1, 2, 10, 3, 4, 4]
mList.removeAt(1)
// [2]
mList[0] = 5
// [5]
list.sorted()
// [1, 1, 3, 4, 5, 9]
list.reversed()
// [9, 5, 1, 4, 1, 3]
// The following will do the operations "in-place" without creating a new list
val mm = list.toMutableList()
mm.shuffle()
// [5, 3, 4, 1, 1, 9]
mm.sort()
// [1, 1, 3, 4, 5, 9]
mm.reverse()
// [9, 5, 1, 4, 1, 3]
// There is only one list here, sub list is just a reference, a view of letters2
// And they are reflecting each other
letters2[1] = "Z"
println(sub)
// [Z, C, D]
sub[2] = "MM"
println(letters2)
// [A, Z, C, MM]
sub.fill("FF")
println(letters2)
// [A, FF, FF, FF]
sub.clear()
println(letters2)
// [A]
letters2.clear()
// println(sub) // ERROR// Because there is no more original list
println(smun)
// [4, 3, 2, 1]
nums[1] = 99
println(smun)
// [4, 3, 99, 1]
smun[2] = -1
println(nums)
// [1, -1, 3, 4]
Lesson 3 | Functions
// A function like main returns a type "UNIT" which is Kotlin's way of saying no value
fun main(args: Array<String>) {
println("Hello, world!")
println(test()) // kotlin.Unit
}
fun test() {
}
Practice Time
/*Basic Task
Extended Task
In the body of the dayOfWeek() function, answer the question by printing the current d
Hints
You can use Java libraries (java.util) from Kotlin. For example, to get the day of the
Calendar.getInstance().get(Calendar.DAY_OF_WEEK)
Type in the code, then press Option + Enter in Mac, or Alt + Enter in Windows, over th
Use a when statement*/
// Answer:
import java.util.*
fun main(args: Array<String>) {
dayOfWeek()
}
fun dayOfWeek() {
println("What day is it today?")
val day = Calendar.DAY_OF_WEEK
println(when(day) {
1 -> "Monday"
2 -> "Tuesday"
3 -> "Wednesday"
4 -> "Thursday"
5 -> "Friday"
6 -> "Saturday"
7 -> "Sunday"
else -> "Time has stopped"
})
}
// Run -> Edit COnfigurations -> Program Args: Kotlin
fun main(args: Array<String>) {
println("Hello, ${ args[0] }") // Hello, Kotlin
}
val temperature = 10
val isHot = if (temperature > 50) true else false
println(isHot) // false
val message = "You are ${ if (temperature > 50) "fried" else "safe" } fish"
println(message) // You are safe fish
/*Create a main() function that takes an argument representing the time in 24-hour for
(values between and including 0 -> 23).
In the main() function, check if the time is before midday (<12), then print "Good mor
Notes:
Remember that all main() function arguments are Strings, so you will have to convert t
Advanced
Try to use Kotlin's string templates to do this in 1 line.*/
// Your reflection
fun main(args: Array<String>) {
println(if (args[0].toInt() < 12) "Good morning, Kotlin" else "Good night, Kotlin"
}
// OR
println("${if (args[0].toInt() < 12) "Good morning, Kotlin" else "Good night, Kotlin"
Practice Time
import kotlin.random.Random
fun main(args: Array<String>) {
val str = "*".repeat(10) // **********
println(str)
// Repeat an action 10 times
repeat (10) { index ->
println("${Random.nextInt(7)} index: $index")
}
feedTheFish()
}
fun feedTheFish() {
val day = randomDay()
val food = "pellets"
println("Today is $day and the fish eat $food")
}
Practice Time
/*Create a program with a function that returns a fortune cookie message that you can
// Solution Code
fun main(args: Array<String>) {
println("\nYour fortune is: ${getFortuneCookie()}")
}
// Extra Practice
fun main(args: Array<String>) {
var fortune: String
for (i in 1..10) {
fortune = getFortuneCookie()
println("\nYour fortune is: $fortune")
if (fortune.contains("Take it easy")) break
}
}
// My Solution
import kotlin.random.Random
fun main(args: Array<String>) {
// OR -> for (i in 1..10) { ... }
repeat (10) {
val fortune = getFortuneCookie()
println("Your fortune is: ${fortune.first}")
if (fortune.second == 5) {
return
}
}
}
fun feedTheFish() {
val day = randomDay()
val food = fishFood(day)
println("Today is $day and the fish eat $food")
}
Practice Time
// Solution Code
fun getBirthday(): Int {
print("\nEnter your birthday: ")
return readLine()?.toIntOrNull() ?: 1
}
fun getFortune(birthday: Int): String {
val fortunes = listOf("You will have a great day!",
"Things will go well for you today.",
"Enjoy a wonderful day of success.",
"Be humble and all will turn out well.",
"Today is a good day for exercising restraint.",
"Take it easy and enjoy life!",
"Treasure your friends, because they are your greatest fortune."
val index = when (birthday) {
in 1..7 -> 4
28, 31 -> 2
else -> birthday.rem(fortunes.size)
}
return fortunes[index]
}
// My Code
import kotlin.random.Random
fun main() {
for (i in 1..10) {
val birthday = getBirthday()
val fortune: Pair<String, Int> = getFortuneCookie(birthday)
println("Your fortune is: ${fortune.first}")
if (fortune.second == 5) {
break
}
}
}
Parameters
// Parameters kotlin can have a default value, this means when you call a function, yo
// If the value is missing, the default value is used
fun main(args: Array<String>) {
swim()
swim("Slow") // Specify the default argument positionally
swim(speed = "Slow") // Or Specify the argument by name
}
// Wrong example
shouldChangeWaterWRONG("Monday") // Error!
// We have to specify that Monday is the day
shouldChangeWaterWRONG(day = "Monday")
// You can define a function where the default variables are listed first or
// mixed in others, but this easily leads to mistakes
// If you forget to list all arguments by name
fun shouldChangeWaterWRONG(temperature: Int = 22, dirty: Int = 20, day: String
}
/*Create a function that checks if we can add another fish into a tank that already ha
How many fish in a tank?
The most widely known rule for stocking a tank is the one-inch-per-fish-per-gallon-of-
Typically, a tank with decorations can contain a total length of fish (in inches) less
For example:
A 10 gallon tank with decorations can hold up to 8 inches of fish, for example 4 x 2-i
A 20 gallon tank without decorations can hold up to 20 inches of fish, for example 6 x
fitMoreFish function
Create a function that takes these arguments:
Output
Make sure you test your code against the following calls, and that you get the correct
Notice how you can use the .sum() function in the list? This is a way to add up all el
// Solution Code
fun main(args: Array<String>) {
println(canAddFish(10, listOf(3,3,3))) // ---> false
println(canAddFish(8, listOf(2,2,2), hasDecorations = false)) // ---> true
println(canAddFish(9, listOf(1,1,3), 3)) // ---> false
println(canAddFish(10, listOf(), 7, true)) // ---> true
}
Practice Time
// My Code
fun main(args: Array<String>) {
println(whatShouldIDoToday("sad"))
}
// Way 1
// return when {
// temperature > 30 -> true
// dirty > 30 -> true
// day == "Sunday" -> true
// else -> false
// }
// Sometimes you might be tempted to use expensive functions to initialize default par
// Examples of expensive operations include reading files or allocating a lot of memor
// BE CAREFUL WITH THIS, They can affect the performance of your code quite a bit
// Because Default parameters are evaluated at call time by Kotlin
fun getDirtySensorReading() = 20
fun shouldChangeWater(dirty: Int = getDirtySensorReading()): Boolean {
// ...
}
///////////////////////////////////////////////////////////////////
fun main(args: Array<String>) {
aquariumStatusReport()
aquariumStatusReport("sfg")
}
/*
* Every time you call aquariumStatusReport() without passing a value for the aquarium
* a new aquarium will be made which is costly
* */
fun makeNewAquarium() = println("Building a new aquarium.....")
fun aquariumStatusReport(aquarium: Any = makeNewAquarium()) {
// Any can hold any type of object
}
///////////////////////////////////////////////////////////////////
Practice Time
fun isHappySunny (mood: String, weather: String) = mood == "happy" && weather ==
// My Code
fun main() {
print("Enter mood: ")
val mood = readLine().orEmpty() // OR -> readLine() ?: ""
// Double Bang operator does the following line;
// if (readLine() != null) readLine() else throw NullPointerException("Expressi
println(whatShouldIDoToday(mood))
}
// Using repeat:
fun main(args: Array<String>) {
var fortune: String = ""
repeat (10) {
fortune = getFortune(getBirthday())
println("\nYour fortune is: $fortune")
if (fortune.contains("Take it easy")) break;
}
}
Filters
// By default, filter analyst is EAGER that means everytime you call filter, it create
// EAGER example
fun main() {
val list = listOf("rock", "pagoda", "plastic", "tur")
// Decoration EAGER here// We'll hold a new list
// containing strings that starts with 'p'
val decorations = list.filter { it[0] == 'p' }
println(decorations)
}
// If you want LAZY Behaviour, you can use SEQUENCES// A sequence is a collection that
// When you return the filter results as a sequence, our filtered variable won't hold
// Ehenever you access elements of the sequence, the filter is applied and the results
// Of course, if we want to turn our sequence back into the list, we can call "toList(
fun main() {
val list = listOf("rock", "pagoda", "plastic", "tur")
// Apply filter LAZILY
val decorations = list.asSequence().filter { it[0] == 'p' }
println(decorations) // kotlin.sequences.FilteringSequence@1fb3ebeb
println(decorations.toList()) // Ignite the filter!: [pagoda, plastic]
}
// Let's use the function map and tell it to print every item, since it's lazy, callin
// Let's use the function map and tell it to print every item
val lazyMap = decorations.asSequence().map {
println("map $it")
it
}
println(lazyMap) // kotlin.sequences.TransformingSequence@53d8d10a
// When I take the first element however, you can see that the map operation reads the
fun main() {
val list = listOf("rock", "pagoda", "plastic", "tur")
// Apply filter LAZILY
val decorations = list.asSequence().filter { it[0] == 'p' }
// Let's use the function map and tell it to print every item
val lazyMap = decorations.asSequence().map {
println("map $it") // map pagoda
it
}
println(lazyMap) // kotlin.sequences.TransformingSequence@53d8d10a
println("first: ${lazyMap.first()}") // first: pagoda
}
// Of course taking the full list will iterate over all the values
fun main() {
val list = listOf("rock", "pagoda", "plastic", "tur")
// Apply filter LAZILY
val decorations = list.asSequence().filter { it[0] == 'p' }
// Let's use the function map and tell it to print every item
val lazyMap = decorations.asSequence().map {
println("map $it")
it
}
println("all: ${lazyMap.toList()}")
}
// map pagoda
// map plastic
// all: [pagoda, plastic]
Practice Time
Create a filter that gets all the curries and sorts them by string length.
Hint: After you type the dot (.), IntelliJ will give you a list of functions you can a
Filter the list of spices to return all the spices that start with 'c' and end in 'e'.
Take the first three elements of the list and return the ones that start with 'c'.
Note: We will be able to do a lot more interesting stuff with filters after you learn
fun main() {
val spices = listOf("curry", "pepper", "cayenne", "ginger", "red curry",
// 2
println(spices.filter { s -> s.contains("curry") }.sortedBy { s -> s.length })
// 3
println(spices.filter { s -> s.startsWith('c') && s.endsWith('e') })
println(spices.filter { s -> s.first() == 'c' && s.last() == 'e' })
println(spices.filter { s -> s[0] == 'c' && s[s.length - 1] == 'e' })
// 4
println(spices.take(3).filter { s -> s.first() == 'c' })
// Solution Code
// Sorting curries by string length
spices.filter { it.contains("curry") }.sortedBy { it.length }
// Filtering by those that start with 'c' and end with 'e'
spices.filter{it.startsWith('c')}.filter{it.endsWith('e')}
> [cayenne]
// OR
spices.filter { {it.startsWith('c') && it.endsWith('e') }
> [cayenne]
Kotlin Labmdas
// Lambda functions are used when you need a function FOR A SHORT PERIOD OF TIME.
// A LAMBDA is an expression that makes a function, instead of declaring a named funct
fun main() {
// Lambda function
{ println("Hello") }()
}
Higher-Order Functions
/*
* When you combine higher-order functions with lambdas
* Kotlin has a special syntax
* it's called the last parameter called syntax
* */
fun dirtyProcessor() {
dirty = updateDirty(dirty, waterFilter)
println("1: $dirty")
// since feedFish is a named function and not a lambda
// you'll need to use a double colon to pass it
// This way Kotlin know you're not trying to call feedFish
// and it will let you pass a REFERENCE
// So here we don't call the function but we pass it to another function and then
// that function will run the function passed it to
dirty = updateDirty(dirty, ::feedFish)
// " :: " means, it creates a member reference or a class reference.
println("2: $dirty")
// Above method is similar as the following
dirty = feedFish(dirty)
println("22: $dirty")
// Here we call updateDirty again, but this time
// we pass a lambda as an argument for the parameter operation
/*
* What's really interesting here, a lambda is an argument to updateDirty
* but since we're passing it as the last parameter
* we don't have to put it inside the function parentheses
* */
dirty = updateDirty(dirty) { dirty ->
dirty + 50
}
/*
* To really show you what is going on,
* you can put the parentheses back in, here you can see we're just
* passing the lambda as an argument updateDirty
* */
dirty = updateDirty(dirty, { dirty ->
dirty + 50
})
/*
* Using this syntax we can define functions that look like they're built-in to the
* Actually, we've already used a few higher-order functions from the standard libr
* */
val list = listOf(1, 2, 3)
list.filter {
it == 2
}
/*
* The filter function we used in the last section, takes a lambda and
* uses it to filter a list,
* repeat is also just a function that takes a repeat count and a lambda that is re
* */
}
Practice Time
// Solution Code
val rollDice = { Random().nextInt(12) + 1}
val rollDice = { sides: Int ->
Random().nextInt(sides) + 1
}
val rollDice0 = { sides: Int ->
if (sides == 0) 0
else Random().nextInt(sides) + 1
}
val rollDice2: (Int) -> Int = { sides ->
if (sides == 0) 0
else Random().nextInt(sides) + 1
}
// My Code
import kotlin.random.Random
fun main() {
val rollDice6Sides = { Random.nextInt(12) + 1 }
val rollDice = { sides: Int ->
if (sides == 0) 0
else Random.nextInt(sides) + 1
}
repeat(10) {
println("${rollDice(0)}")
}
val rollDice2: (Int) -> Int = { sides: Int ->
if (sides == 0) 0
else Random.nextInt(sides) + 1
}
}
// Why would you want to use the function type notation instead of just the lambda?
// Create a function gamePlay() that takes a roll of the dice as an argument and print
// Solution Explanation
// Function type notation is MORE READABLE, which REDUCES ERRORS, clearly showing the
// Solution Code
gamePlay(rollDice2(4))
fun gamePlay(diceRoll: Int){
// do something with the dice roll
println(diceRoll)
}
// My Code
import kotlin.random.Random
fun main() {
// Why would you want to use the function type notation instead of just the lambda
// -> We might want to know what type we pass in the function
// this will reduce errors related to parameters
val dice = { sides: Int ->
Random.nextInt(sides) + 1
}
val rollDice2: (Int) -> Int = { sides: Int ->
if (sides == 0) 0
else Random.nextInt(sides) + 1
}
gamePlay(dice, rollDice2)
}
Lesson 4 | Classes
/*
Classes are blue prints for objects
-> Methods are the functionality of the class, class function, what the object could d
( fillWithWater() )
Practice Time
/*Earlier, we created and filtered a list of spices. Spices are much better represente
To recap, let's make a simple Spice class. It doesn't do much, but it will serve as th
// My Code
class Spice {
var name: String = "curry"
var spiciness: String = "mild"
fun heat(): Int {
return when (spiciness) {
"mild" -> 5
else -> 6
}
}
}
fun main() {
// Create spice class instance
val mySpice = Spice()
println("name: ${mySpice.name}, heat: ${mySpice.heat()}")
}
// Solution Code
class SimpleSpice() {
val name = "curry"
val spiciness = "mild"
val heat: Int
get() {return 5 }
}
// In main
val simpleSpice = SimpleSpice()
println("${simpleSpice.name} ${simpleSpice.heat}")
Package Visibility
// In kotlin everything is public by default, that means all of your variables and cla
// For members declared inside the class, again by default they are public
Class Visibility
// Public means that any client who sees the class can also see it's public members
// Private means members are only visible inside the class, importantly subclasses can
// Protected means the same as private but members are also visible to subclasses
// Class members can have a visibility of internal as well
Class Examples
// Sample Code
// FILE: Main
package Aquarium
fun main() {
// Create spice class instance
// val mySpice = Spice()
// println("name: ${mySpice.name}, heat: ${mySpice.heat}")
buildAquarium()
}
// We don't have to chance " myAquarium " to a var because, we're not changing the
// It's the same object we're modifying its properties
myAquarium.height = 80
println("New Height: ${myAquarium.height} cm")
println("Volume: ${myAquarium.volume} liter")
}
// FILE: Aquarium
package Aquarium
class Aquarium {
var length = 100
var width = 20
var height = 40
// Constructor
class Test(id: Int, name: String, val testVal: String) {
/*
* The primary constructor cannot contain any code.
* Initialization code can be placed in initializer blocks,
* which are prefixed with the init keyword.
* */
// As the name says,
// "also" expressions does some additional processing on the object it was invoked
// Unlike let, it returns the original object instead of any new return data.
var firstProperty = "First property: $name".also(::println)
// Alternative of " also "
// val b = "SDads: $name".also { println(it) }
init {
// ....
println("First this block will be executed")
}
// The class can also declare secondary constructors, which are prefixed with construc
class Person(val pets: MutableList<Pet> = mutableListOf())
class Pet {
// Secondary constructor
constructor(owner: Person) {
// Add this pet to he list of its owner's
owner.pets.add(this)
}
}
/*
* A class in Kotlin can have a primary constructor and one or more secondary construct
* The primary constructor is part of the class header:
* it goes after the class name (and optional type parameters).
* */
class Person constructor(firstName: String) {
// If the primary constructor does not have any annotations or visibility modifiers,
// the constructor keyword can be omitted:
class Person2 (firstName: String) {
fun main() {
val test = Test(24, "Melo")
println("Age: ${test.firstProperty}, Name: ${test.secondProperty}")
test.firstProperty = "123"
println("${test.firstProperty}")
test.testVal
}
// Kotlin has a concise syntax for declaring properties and initializing them from the
class Person(val firstName: String, val lastName: String, var age: Int)
// Such declarations can also include default values of the class properties:
class Person(val firstName: String, val lastName: String, var isEmployed: Boolean
// You can use a trailing comma when you declare class properties:
class Person(
val firstName: String,
val lastName: String,
var age: Int, // trailing comma
) { /*...*/ }
/*
VISIBILITY
PACKAGE:
public - default. Everywhere
private - file
internal - module
CLASS:
sealed - only subclass in same file
INSIDE CLASS:
public - default. Everywhere.
private - inside class, not subclasses
protected - inside class and subclasses
internal - module
*/
Practice Time
Sample Code
package Aquarium
// Constructor
class Test(id: Int, name: String) {
/*
* The primary constructor cannot contain any code.
* Initialization code can be placed in initializer blocks,
* which are prefixed with the init keyword.
* */
// As the name says,
// "also" expressions does some additional processing on the object it was invoked.
// Unlike let, it returns the original object instead of any new return data.
var firstProperty = "First property: $name".also(::println)
// Alternative of " also "
// val b = "SDads: $name".also { println(it) }
init {
// ....
println("First this block will be executed")
}
init {
println("Second initializer block that prints ${name.length}")
}
/*
* Accessing the Backing Field
* Every property we define is backed by a field
* that can only be accessed within its get() and set() methods
* using the special field keyword.
* The field keyword is used to access or modify the property’s value.
* This allows us to define custom logic within the get() and set() methods
* */
var rating: Int = 5
get() {
if (field < 5) {
println("Warning This is a Terrible Book!")
}
return field
}
set(value) {
field = when {
value > 10 -> 10
value < 0 -> 0
else -> value
}
}
/*
* If we want to be able to modify a property’s value,
*we mark it with the var keyword.
* If we want an immutable property, we mark it with a val keyword.
* The main difference is that val properties can’t have setters.
* */
val isWorthReading: Boolean get() = this.rating > 5
// set(value) { // A 'val'-property cannot have a setter!
// // ERROR
// }
// In this sense, the property acts as a method when using a custom getter.
/*
* Now any consumers of the book class can read the inventory property
* but only the Book class can modify it.
* */
var inventory: Int = 0
private set
/*
* Note that the default visibility for properties is public.
* The getter will always have the same visibility as the property itself.
* For example, if the property is private, the getter is private.
* */
// Backing Fields
/*
In Kotlin, a field is only used as a part of a property
to hold its value in memory. Fields can not be declared directly.
However, when a property needs a backing field, Kotlin provides it automatically.
This backing field can be referenced in the accessors using the "field" identifier:
* */
var counter = 0 // the initializer assigns the backing field directly
set(value) {
if (value >= 0)
field = value
// counter = value // ERROR StackOverflow: Using actual name 'counter' would m
}
/*
* A class in Kotlin can have a primary constructor and one or more secondary construct
* The primary constructor is part of the class header:
* it goes after the class name (and optional type parameters).
* */
class PersonTest constructor(firstName: String) {
// If the primary constructor does not have any annotations or visibility modifiers,
// the constructor keyword can be omitted:
class Person2 (firstName: String) {
// The class can also declare secondary constructors, which are prefixed with construc
class Person(val pets: MutableList<Pet> = mutableListOf())
class Pet {
// Secondary constructor
constructor(owner: Person) {
// Add this pet to he list of its owner's
owner.pets.add(this)
}
}
fun main() {
val test = Test(24, "Melo")
println("Age: ${test.firstProperty}, Name: ${test.secondProperty}")
test.firstProperty = "123"
println("First Author: ${test.author}")
test.author = "Melo Genesis"
println("Second Author: ${test.author}")
}
Practice Time
/*Earlier, we created and filtered a list of spices. Spices are much better represente
To recap, let's make a simple Spice class. It doesn't do much, but it will serve as th
class SimpleSpice(){
val name = "curry"
val spiciness = "mild"
val heat: Int
get() {return 5 }
}
val simpleSpice = SimpleSpice()
println("${simpleSpice.name} ${simpleSpice.heat}")
///////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////
// Solution Code
class Spice(val name: String, val spiciness: String = "mild") {
///////////////////////////////////////////////////////////////////////////////////
// My Code
package Aquarium
class Spice(val name: String, val spiciness: String = "mild") {
val heat: Int
get() {
return when (spiciness) {
"mild" -> 1
"medium" -> 3
"spicy" -> 5
"very spicy" -> 7
"extremely spicy" -> 10
else -> 0
}
}
init {
println("Name. $name, Spiciness: $spiciness, Heat: $heat")
}
}
fun main() {
val spices = listOf<Spice>(
Spice("curry", "mild"),
Spice("pepper", "medium"),
Spice("cayenne", "spicy"),
Spice("ginger", "mild"),
Spice("red curry", "medium"),
Spice("green curry", "mild"),
Spice("hot pepper", "extremely spicy")
)
val spice = spices.filter {
it.heat < 5
}
fun makeSalt() = Spice("Salt")
}
///////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////
package Aquarium
class Aquarium (var length: Int = 100, var width: Int = 20, var height: Int =
var volume: Int
get() = width * height * length / 1000
// By convention, the name of the setter parameter is " value "
private set(value) { height = (value * 1000) / (width * length) }
// If we need to have another constructor than the default one for our class, we c
// For example, instead of specifying the dimensions when we create the aquarium
// we might want to specify the number of fish when we create an aquarium in build
constructor(numberOfFish: Int): this() {
val water = numberOfFish * 2000 // cm3
val tank = water + water * 0.1
height = (tank / (length * width)).toInt()
}
// Note that we can't mix constructor arguments, so we cannot create an aquarium p
// The arguments have to match exactly with one of the available constructors
}
Inheritance
package Aquarium
import kotlin.math.PI
// It doesn't say explicitly but this class actually inherits from the top level class
/*
* The first thing we have to do to be able to inherit from a class
* is make the class " open ", by default classes are not subclassible
* We have to explicitly allow it
* */
open class Aquarium (var length: Int = 100, var width: Int = 20, var height:
// We could add "Any()" but it's not required and doesn't give anything extra
open var volume: Int
get() = width * height * length / 1000
set(value) { height = (value * 1000) / (width * length) }
// Private setters are not allowed for open properties!
open var water = volume * 0.9
// All classes in Kotlin have a common superclass Any, that is the default superclass
// Any has three methods: equals(), hashCode() and toString(). Thus, they are defined
// By default, Kotlin classes are final: they can’t be inherited. To make a class inhe
// To declare an explicit supertype, place the type after a colon in the class header:
// Overriding Methods
// Kotlin requires explicit modifiers for overridable members and overrides:
open class Shape {
open fun draw() { /*...*/ }
fun fill() { /*...*/ }
}
// Inheritance Explanation
/*
* Let's say we want to have a different type of aquarium such as cylindrical tower
* Tower tanks are a lot like regular aquariums
* But they are also different in some ways
* So we couldn't inherit a lot of stuff from our basic aquarium
* and change the things that are different
* Now, int the same file is okay, we can create a tower tank that inherits from aquari
* We specify the inheritance or the parent class, after the colon
* */
class TowerTank(): Aquarium() {
// We need to change how the volume is calculated
// And we don't want to fill as much water into the tall tank
// We are doing this by overriding the water property in tower tank
override var water = volume * 0.8
//////////////////////////////////////////////////////////////////////////////////// /
open class Book(val title: String, val author: String) {
private var currentPage = 1
open fun readPage() {
currentPage++
}
}
class eBook(title: String, author: String, var format: String = "text") : Book(title,
private var wordsRead = 0
override fun readPage() {
wordsRead = wordsRead + 250
}
}
///////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////
// My Code
package Aquarium
open class Book(val title: String, val author: String) {
private var currentPage = 1
open fun readPage() {
currentPage++
}
}
// Subclass
class eBook(title: String, author: String, var format: String = "text"): Book(title, a
var wordCount = 0
override fun readPage() {
wordCount += 250
}
}
///////////////////////////////////////////////////////////////////////////////////
Interfaces
/* Different types of fish have lots in common, and they do similar things in somewhat
For example;
All fish have a color and all fish have to eat
So we want to make sure that all our fish that we create do that
Kotlin offers two ways of doing that
1) Abstract Classes
2) Interfaces
Both are classes that cannot be instantiated on their own which means you cannot creat
The difference is that ABSTRACT CLASSES HAVE CONSTRUCTORS while Interfaceses don't hav
A final thing you can do in Kotlin, when using classes that implement interfaces is cr
// Add a comma and then the FishAction interface without "()" and implement eat
// You have to implement interface methods!
class Plecostomus: AquariumFish(), FishAction {
override val color = "gold"
override fun eat() {
println("much on algae")
}
}
// We don't have to chance " myAquarium " to a var because, we're not changing the
// It's the same object we're modifying its properties
myAquarium.height = 80
println("New Height: ${myAquarium.height} cm")
println("Volume: ${myAquarium.volume} liters")
// To make this more readable, let's pass in name parameters
val smallAquarium = Aquarium(length = 20, width = 15, height = 30)
val smallAquarium2 = Aquarium(numberOfFish = 9)
println("Small Aquarium: Volume: ${smallAquarium2.volume} " +
"liters with length ${smallAquarium2.length} " +
"width ${smallAquarium2.width} " +
"height ${smallAquarium2.height}")
}
/*
* This function creates a shark and a pleco and prints out their colors
* */
fun makeFish() {
val shark = Shark()
val pleco = Plecostomus()
shark.eat()
pleco.eat()
}
/*
* When a fish gets the food, it eats it, we don't care what kind of fish it is
* as long as it can eat the food. "Eat" is defined in fish action, So every fish we pa
* to feed fish needs to implement fish action, we don't care about any other propertie
* As long as it implements fish action, we can use it.
* Only fish that implement fish action can be passed into "feedFish"
* This is a simplistic example but when you have a lot of classes
* this can help you keep clearer and more organized
* */
fun feedFish(fish: FishAction) {
fish.eat()
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
package Aquarium
// Simple Abstract Class
/*
* Because AquariumFish is abstract we can't make instances of AquariumFish directly!
* We need to provide sub classes that implement its missing functionality
* */
abstract class AquariumFish {
abstract val color: String
}
// Add a comma and then the FishAction interface without "()" and implement eat
// You have to implement Interface methods!
class Plecostomus: AquariumFish(), FishAction {
override val color = "gold"
override fun eat() {
println("much on algae")
}
}
////////////////////////////////////////////////////////////////////////////////
Difference Between Abstract Classes And Interfaces
// There is really only one syntax difference in Kotlin between abstract classes and i
-> Abstract classes can have constructors and interfaces cannot
// Use an interface if you have a lot of methods and one or two defalt implementations
interface AquariumAction {
fun eat()
fun jump()
fun clean()
fun catchFish()
fun swim() {
println("swim")
}
}
// Making all aquarium fish implement "FishActionTest", we can provide a default imple
// But really Kotlin provides us a better tool for this than abstract classes
// INTERFACE DELEGATION let's you add features to a class via composition
// Composition is when you use an instance of another class as opposed to inheriting f
// Instead of requiring the caller's sublass' giant abstract class, give them a small
// How do we do composition ?
///////////////////////////////////////////////////////////////////////////////////
package Aquarium
/*
* Interface delegation is really powerful
* and you should generally consider how to use it whenever you
* might use an abstract class in another language
* It let's you use composition to plug-in behaviours
* instead of requiring a lot of sub classes each specialized in a different way
* */
fun main() {
delegate()
}
fun delegate() {
val pleco = Plecostomus2()
println("Fish has color ${pleco.color}")
pleco.eat()
}
//
interface FishColor {
val color: String
}
/*
* We can remove inheritance from aquarium fish
* because we get all the functionality from the interfaces
* and we don't even have to change the code in the body of plecostomus
* */
// Fish color could have been implemented by a class instead of object
// But this time, we would have had to create many unnecessary same class objects
// Here, FishColor interface is implemented by GoldColor object which will be only one
// object at all
/* This means implement the interface fish color,
by deferring all calls to the object, gold color
So everytime you call the color property on this class, it will actually
call the color property on gold color
* */
/*
* Of course there are different colors of plecostomi in the world
* So we can make the fish color object a constructor parameter
* with a default of gold color and defer calls to the color property whatever
* fish color we get passed in
* */
// Now Plecostomus2 doesn't have a body, all its overrides are handled by
// interface delegation
class Plecostomus2(fishColor: FishColor = GoldColor):
FishAction2 by PrintingFishAction("a lot of algae"),
FishColor by GoldColor
/*
* It doesn't really make sense to make multiple instances of
* gold color as they would all do the exact same thing
* Kotlin let's us declare a class where we can only have one instance by using
* the keyword "object" instead of "class"
* */
// This will declare a class and make exactly one instance of it
// The instance will be called gold color and there's no way
// to make another instance of this class but that's okay we don't need to
// If you're familiar with the Singleton Pattern this is how to implement it in Kotlin
/*
* In software engineering, the singleton pattern is a software design pattern
* that restricts the instantiation of a class to one "single" instance.
* This is useful when exactly one object is needed to coordinate actions across the sy
* The term comes from the mathematical concept of a singleton.
* */
object GoldColor : FishColor {
override val color = "gold"
}
// If we were passed in a red color, then fish color would be by red color and return
object RedColor : FishColor {
override val color = "red"
}
// Instead of printing a fixed string, we print our whatever food we were passed
// Since we have a member variable food, we can't make PrintingFishAction an object
// We want a different instance for each food that we passed in
// Constructors are not allowed for "object"
class PrintingFishAction(val food: String) : FishAction2 {
override fun eat() {
println(food)
}
}
///////////////////////////////////////////////////////////////////////////////////
Delegation Design Pattern
package Delegation
/*
* KOTLIN DELEGATION
*
* Delegation is an object oriented design pattern
* And Kotlin supports it natively
* Delegation Pattern means delegating the responsibilities
* to other objects.
* */
/*
* Here, we will be delegating the responsibility of
* "download()" and "play()" interfaces to
* "Downloader" and "Player" objects that we pass in
* So the class is just forwarding the responsibility
* */
class MediaFile(
private val downloader: Downloader,
private val player: Player
) : Downloader by downloader, Player by player {
/*
* We don't need to write following two methods
* because Kotlin already supports delegation natively
* This is boilerplate code
* */
// override fun download() {
// downloader.download()
// }
//
// override fun play() {
// player.play()
// }
}
fun main() {
val file = "FileGenesis1.mp4"
val mediaFile = MediaFile(FileDownloader(file), FilePlayer(file))
mediaFile.download()
mediaFile.play()
}
interface Downloader {
fun download()
}
interface Player {
fun play()
}
/*When you want to inherit Coder from Person you have to make Person open, so it is av
When you don't need to make objects from parent class(in our case it's Person) or you
It works the same way as open does. But the main difference is that you cannot make ob
Abstract class cannot be instantiated and must be inherited, abstract classes are open
Open modifier on the class allows inheriting it. If the class has not open modifier it
/*Let's go back to your spices. Make Spice an abstract class, and then create some sub
It's easiest (organizationally) if you make a new package, Spices, with a file, Spice,
Copy/paste your Spice class code into that new file.
Make Spice abstract.
Create a subclass, Curry. Curry can have varying levels of spiciness, so we don't want
Spices are processed in different ways before they can be used. Add an abstract method
Curry is ground into a powder, so let's call a method grind(). However, grinding is so
Then add the Grinder interface to the Curry class.*/
// Delegation
// Using the provided code from the lesson for guidance, add a yellow color to Curry.
fun main (args: Array<String>) {
delegate()
}
fun delegate() {
val pleco = Plecostomus()
println("Fish has has color ${pleco.color}")
pleco.eat()
}
interface FishAction {
fun eat()
}
interface FishColor {
val color: String
}
// Interface
/*Create an interface, SpiceColor, that has a color property. You can use a String for
Create a singleton subclass, YellowSpiceColor, using the object keyword, because all i
Add a color property to Curry of type SpiceColor, and set the default value to YellowS
Add SpiceColor as an interface, and let it be by color.
Create an instance of Curry, and print its color. However, color is actually a propert
Change your code so that the SpiceColor interface is added to the Spice class and inhe
// Solution Code
abstract class Spice(val name: String, val spiciness: String = "mild", color: SpiceCol
abstract fun prepareSpice()
}
interface Grinder {
fun grind()
}
interface SpiceColor {
val color: String
}
Data Classes
package Decorations
/*
* DATA CLASSES
* Often, we have classes that mostly act as data containers
* In Kotlin, for classes that mostly hold data,
* there is a class with benefits
* */
fun main() {
makeDecoration()
}
fun makeDecoration() {
// Create instance of Decorations class
val d1 = Decorations("granite")
/*
* With a data class printing the object
* prints the values of properties
* instead of just an address of the object
* that is the object pointer
* basically it creates toString for us to print the properties
* */
println(d1) // Decorations(rocks=granite)
/*
* Data class also provides an equals method to compare two
* instances of a data class
* */
val d2 = Decorations("slate")
println(d2) // Decorations(rocks=slate)
val d3 = Decorations("slate")
println(d3) // Decorations(rocks=slate)
// Comparison
println(d1 == d2) // false
println(d3 == d2) // true
// Another Decoration
val d5 = Decorations2("crystal", "wood", "diver")
println(d5)
/*
* DECOMPOSITION
* To get at the properties and assign them to variables
* Kotlin let's us use a process called decomposition
* */
Practice Time
// Solution Code
data class SpiceContainer(var spice: Spice) {
val label = spice.name
}
// My Code
package DataClasses
abstract class Spice(val name: String, val spiciness: String = "mild", ) {
}
fun main() {
val spiceCabinet = listOf(SpiceContainer(Curry("Yellow Curry", "mild")),
SpiceContainer(Curry("Red Curry", "medium")),
SpiceContainer(Curry("Green Curry", "spicy")))
/*
* SINGLETONS - "Object"
*
* To create singleton, use the "object" keyword
* when you declare you class
*
* Anytime you're defining a class that
* shouldn't be instantiated multiple times
* you can use the "object" keyword in place of class
*
* Kotlin will instantiate exactly one instance of the class
*
* Since there can be only one MobyDick, we declare it as an object
* instead of a class
* */
object MobyDickWhale {
val author = "Herman Melville"
fun jump () {
// ...
}
}
Enums
/*
* ENUMS
*
* which lets you enumerate items
* enums actually define a class
* and you can give them properties or even methods
*
* Enums are like singletons, Kotlin will make
* exactly one red, exactly one green and exactly one blue
* there is no way to create more than one color object
* And, there is not any way to define more colors
* other then where the enum is declared
* */
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
// Each enum constant is an object. Enum constants are separated with commas.
// Since each enum is an instance of the enum class, it can be initialized as:
Sealed Classes
/*
* SEALED CLASS
*
* It's a class that can be subclassed
* but only inside the file which it's declared
* If you try to subclass it in a different file, you'll get an error
* This makes sealed classes a safe way to represent a fixed number of types
*
* They're great for returning success or error from a network API
*
* */
sealed class Seal {
/*
* I can use a "when" statement to check
* what type of seal I have
* And If I don't match all of the types of seal
* Kotlin will give me a compiler error!
* */
fun matchSeal(seal: Seal): String {
return when (seal) {
is Walrus -> "walrus"
is SeaLion -> "seaLion"
}
}
Practice Time
// In SpiceColor, change the type of color from String to the Color class, and set the
// Hint: The color code for yellow is YELLOW(0xFFFF00)
// Solution Code
interface SpiceColor {
val color: Color
}
// Answer Explanation:
// Making Spice a sealed class helps keep all the spices together in one file.
Pairs
package Collections
fun main() {
// Sample Generic Pair
val equipment = "fishnet" to "catching"
println(equipment.first)
println(equipment.second)
// deconstructing
val fishnet = "fishnet" to "catching fish"
val (tool, use) = fishnet
val (first, second, third) = Triple(1, 2 ,3)
// We can use them to return more than one variable from a function
// and we can destruct it and use
val (tool2, use2) = giveMeATool()
}
Practice Time
/*
Let's go through an example of getting information about a book in the format of a Pai
Let’s create a basic book class, with a title, author, and year. Of course, you could
Create a method that returns both the title and the author as a Pair.
Create a method that returns the title, author and year as a Triple. Use the documenta
Create a book instance.
Print out the information about the book in a sentence, such as: “Here is your book X
*/
// My Code
package lesson_5
class Book(
val title: String,
val author: String,
val year: Int
) {
fun getTitleAuthor(): Pair<String, String> {
return Pair(title, author)
}
fun main() {
val book = Book("Elon Musk", "Ashlee Vance", 2012)
// Solution Code
class Book(val title: String, val author: String, val year: Int) {
Practice Time
/*
One book is rarely alone, and one author rarely writes just one book.
Create a Set of book titles called allBooks, for example, by William Shakespeare.
Create a Map called library that associates the set of books, allBooks, to the aut
Use the collections function any() on library to see if any of the books are “Haml
Create a MutableMap called moreBooks, and add one title/author to it.
Use getOrPut() to see whether a title is in the map, and if the title is not in th
Hints:
any() is applied to a collection and takes a lambda as its argument, for examp
myList.any {it.contains(“name”)}
getOrPut() is a handy function that will check whether a key is in a map, and
mapOf() may come in handy.
*/
// Solution Code
val allBooks = setOf("Macbeth", "Romeo and Juliet", "Hamlet", "A Midsummer Night's Dre
val library = mapOf("Shakespeare" to allBooks)
println(library.any { it.value.contains("Hamlet") })
// My Code
val allBooksOfOneAuthor = setOf("A", "B", "C")
// { set of books, author }
val library = mapOf(
"by William Shakespeare" to allBooksOfOneAuthor,
"Genesis" to setOf("M", "H", "N")
)
Constants
// We can make top level constants and assign them a value at compile time using "cons
// We have "val" and "const val" now, What is the difference?
// Top Level, Compile Time Constant Variable
// The value is always determined at compile time
// const value set it compile time so we cannot call and execute a function
// to get its value set
const val num = 5
// However, const val only works at the top level and in classes declared with object
// Not with regular classes declared with class
/*
* So we can use this to create a file or object that
* contains only constants and import them one-by-one
* */
const val CONSTANT = "top-level constant"
object Constants {
const val CONSTANT2 = "top-level constant"
}
fun main() {
// Difference between val and const val
// With val, the value that is assigned can be determined during program execution
val number = 5
// ERROR!
// Modifier 'const' is not applicable to 'local variable'
// This should be Top Level!
const val num = 5
}
// There are 3 different ways in which you can create constants in Kotlin. It’s not th
// For each situation, decide when you would use a CONSTANT, an ENUM, and a DATA CLASS
Quiz Question
/*Let’s continue with our books setup to practice creating constants in Kotlin. There
For each situation, decide when you would use a constant, an enum, and a data class.
1) Storing simple values without any functionality. For example, a URL or a numeric co
2) They are objects that store groups of values that are related. They offer type safe
Practice Time
Practice Time
/*Create a top-level constant for the maximum number of books a person could borrow.
Inside the Book class, create a method canBorrow() that returns true or false dependin
Create a Constants object that provides constants to the book. For this example, provi
The base URL is really of interest to the Book class. As such, it makes sense to limit
// Solution Code
const val MAX_NUMBER_BOOKS = 20
object Constants {
const val BASE_URL = "http://www.turtlecare.net/"
}
fun printUrl() {
println(Constants.BASE_URL + title + ".html")
}
companion object {
val BASE_URL = "http://www.turtlecare.net/"
}
// My Code
package lesson_5
const val maximumBooks = 100
object Constants {
const val BASE_URL = "www.library.com/"
}
class Book(
val title: String,
val author: String,
val year: Int,
val maxBooks: Int = 0
) {
fun printUrl() {
println(
Constants.BASE_URL + "/"
+ title + "/"
+ author + "/"
+ year + "/"
+ ".html"
)
}
fun main() {
val book = Book("Elon Musk", "Ashlee Vance", 2012)
Extension Functions
/*
Extension Functions allow you to add functions to an existing class without having acc
*/
// Extension Functions are great way to add helpful functionality to classes that you
fun main() {
println("Does it have spaces?".hasSpaces())
println(1232.hasZero())
}
// You can also use it to separate the core API from helper methods on classes you do
class AquariumPlant(val color: String, private val size: Int)
// This extension function is just a helper
// Extension functions are defined outside of the class they extend
fun AquariumPlant.isRed() = color == "Red"
// So, they cannot access to private variables!
fun AquariumPlant.isBig() = size > 50
// ERROR// Cannot access 'size': it is private in 'AquariumPlant'
// You should think of them as helper functions that rely only on the public API
// xtension functions are always resolved statically based on the variable they're app
fun propertyExample() {
val plant = AquariumPlant("Green", 50)
println(plant.isGreen) // true
}
Extension Function Examples
package lesson_5
// We merely make a new function callable
// with the dot-notation on variables of this type
fun String.hasSpaces(): Boolean {
val found = this.find { it == ' ' }
return found != null
}
fun propertyExample() {
val plant = AquariumPlant("Green", 50)
println(plant.isGreen) // true
}
// We can also make the class we are extending which is sometimes called the receiver
// If we do that then the "this" variable used in the body can be null
// The object on which the extension function is called can be null
// We indicate this with a question mark after "AquariumPlant?" but before the dot
fun AquariumPlant?.pull() {
// Inside the body we can test for null by using "?.apply"
// If object is not null, the apply body will be executed
this?.apply {
println("removing $this")
}
}
/*
* You would want to take a nullable receiver if you expect
* the callers will want to call your extension function on nullable variables
* */
fun nullableExample() {
val plantNull: AquariumPlant? = null
plantNull.pull() // ok
fun main() {
val plant = GreenLeafyPlant(50)
plant.print() // GreenLeafyPlant
/*
* Compiler just looks at the type of the variable
* So at compile time, AquariumPlant is an AquariumPlant
* So it will print "AquariumPlant"
* */
val aquariumPlant: AquariumPlant = plant // Type is not "GreenLeafyPlant" anymore
aquariumPlant.print() // AquariumPlant
propertyExample() // true
nullableExample()
}
Practice Time
/*
It can be useful to know the weight of a book, for example, for shipping. The weight o
Note: If you don’t want to give your puppy a book, then create a puzzle toy class and
*/
// Solution Code
fun Book.weight() : Double { return (pages * 1.5) }
fun Book.tornPages(torn: Int) = if (pages >= torn) pages -= torn else pages =
class Puppy() {
fun playWithBook(book: Book) {
book.tornPages(Random().nextInt(12))
}
}
///////////////////////////////////////////////////////////////////////////////
// My Code
package lesson_5
import kotlin.random.Random
object Constants {
const val BASE_URL = "www.library.com/"
}
// Extension function
fun Book.getWeight(): Double {
return 1.5 * pages
}
// Extension function
fun Book.tornPages(tornPages: Int) {
pages -= tornPages
}
// Extension function
fun Book.printNumOfPages() {
println("Number of pages: $pages")
}
class Puppy() {
fun playWithBook(book: Book) {
val randPagesToTorn = Random.nextInt(1, book.pages + 1)
book.tornPages(randPagesToTorn)
}
}
class Book(
val title: String,
val author: String,
val year: Int,
val maxBooks: Int = 0,
var pages: Int = 0
) {
fun printUrl() {
println(
Constants.BASE_URL + "/"
+ title + "/"
+ author + "/"
+ year + "/"
+ ".html"
)
}
fun main() {
val book = Book("Elon Musk", "Ashlee Vance", 2012)
// With generics we can make the list generic so it can hold any type of object
// It's like you make the tyoe a wildcard(Joker) that will fit many types
package Aquarium.generics
// GENERICS
// How to declare a generic class with an upper bound and use it
fun main() {
genericExample()
}
fun genericExample() {
// val aquarium = Aquarium(TapWater()) // Type inference
val aquarium = Aquarium<TapWater>(TapWater())
aquarium.waterSupply.addChemicalCleaners()
/*
Using type hierarchies with generic classes follows a pretty basic pattern that we int
Let’s put this into practice using building materials and a building that needs certai
Add a method build() that prints the type and number of materials needed.
Hint: Use reflection to get the class and simple name: instance::class.simpleN
// Solution Code
open class BaseBuildingMaterial() {
open val numberNeeded = 1
}
fun build() {
println(" $actualMaterialsNeeded ${buildingMaterial::class.simpleName}
}
}
// Out types are type parameters that only ever occur in return values of functions or
// In types can be used anytime the generic is only used as an argument to functions
/* More specifically
-> IN TYPES CAN ONLY BE PASSED INTO AN OBJECT (Can be used as parameter)
-> OUT TYPES CAN ONLY BE PASS OUT OF AN OBJECT OR RETURNED (Can be used as return
Constructors can take out types as arguments but functions never can
*/
package Aquarium.generics
// GENERICS
// How to declare a generic class with an upper bound and use it
fun main() {
genericExample()
}
// To ensure that our parameter must be nonnull but can still be any type
// We remove the question mark "Aquarium<T: Any?>" and just say "Aquarium<T: Any>"
// This makes it impossible to pass null
class Aquarium<out T: WaterSupply>(val waterSupply: T) {
fun addWater(cleaner: Cleaner<T>) {
// Check throws an error if condition is not true, continues otherwise
if (waterSupply.needsProcessed) {
cleaner.clean(waterSupply)
}
fun genericExample() {
// val aquarium = Aquarium(TapWater()) // Type inference
val aquarium = Aquarium<TapWater>(TapWater())
aquarium.waterSupply.addChemicalCleaners()
// If we did not put this "out" -> "class Aquarium<out T: WaterSupply>", it gives
addItemTo(aquarium)
}
Practice Time
/*
That was a lot of explanations. Fortunately, IntelliJ gives you hints as to whether so
Look at the code from the previous practice and consider whether it can be an in type
Notice that the parameter is underlined gray, and if you hover over T, IntelliJ will s
*/
// GENERICS
// How to declare a generic class with an upper bound and use it
fun main() {
genericExample()
}
fun genericExample() {
// val aquarium = Aquarium(TapWater()) // Type inference
val aquarium = Aquarium<TapWater>(TapWater())
aquarium.waterSupply.addChemicalCleaners()
// If we did not put this "out" -> "class Aquarium<out T: WaterSupply>", it gives
addItemTo(aquarium)
isWaterClean<TapWater>(aquarium)
aquarium4.hasWaterSupplyOfType<TapWater>() // True
aquarium4.waterSupply.isType<LakeWater>() // False
}
Practice Time
/*
Create a generic function for type BaseBuildingMaterial and call it isSmallBuilding, w
Note: For this function, IntelliJ recommends not to inline the function. Generally, wh
*/
Annotations
/*
Annotations are a means of attaching metadata to code, that is, the Annotations are re
// Annotations go right before the thing that is Annotated, and most things can be ann
// Some annotations can even take arguments
// They're really useful if you are exporting Kotlin to Java, but otherwise you don't
*/
annotation class ImAPlant
@Target(AnnotationTarget.PROPERTY_GETTER)
annotation class OnGet
@Target(AnnotationTarget.PROPERTY_SETTER)
annotation class OnSet
@get:OnGet
val isGrowing: Boolean = true
@set:OnSet
var needsFood: Boolean = false
}
fun reflections() {
val classObj = Plant::class
annotated?.apply {
println("Found a plant annotation!")
}
}
Labeled Breaks
fun main() {
for (i in 1..10) {
for (j in 1..10) {
if (i > 5) {
break
}
}
}
// Labeled Breaks
loop@ for (i in 1..10) {
for (j in 1..10) {
if (i > 5) {
println()
break@loop
}
}
} // break@loop will come here
// then ends the first loop
// different than just "break" keyword
}
Lambdas Recap
/*
In this practice, you are going to write the the first part of a higher-order function
> [START]
Game Over: [START, NORTH, SOUTH, EAST, WEST, END]
[]
You will finish your game as the last practice in this course.
*/
////////////////////////////////////////////////////////////////////////////////////
// Solution Code
enum class Direction {
NORTH, EAST, WEST, SOUTH, START, END
}
class Game {
var path = mutableListOf<Direction>(Direction.START)
val north = { path.add(Direction.NORTH) }
val south = { path.add(Direction.SOUTH) }
val east = { path.add(Direction.EAST) }
val west = { path.add(Direction.WEST) }
val end = { path.add(Direction.END); println("Game Over: $path"); path.clear();
}
///////////////////////////////////////////////////////////////////////////////////
// My Code
package lesson_6
enum class Directions {
START, END,
NORTH, SOUTH, EAST, WEST
}
fun main() {
val game = Game()
println(game.path)
game.east() // Don't forget the add parentheses "( )" at the end of Lambda functio
game.north()
game.south()
game.west()
game.end()
println(game.path)
}
class Game {
var path: MutableList<Directions> = mutableListOf(Directions.START)
val north = { path.add(Directions.NORTH) }
val south = { path.add(Directions.SOUTH) }
val east = { this.path.add(Directions.EAST) }
val west = { path.add(Directions.WEST) }
val end = {
path.add(Directions.END)
println("Game Over: $path")
path.clear()
false
}
}
Higher-order Functions
/*
* WRITING HIGHER ORDER FUNCTIONS WITH EXTENSIONS LAMBDAS
* is the most advanced part of the Kotlin Language
* */
// There are tons of built in functions in the Kotlin standard library that use extens
// A higher-order function is a function that takes another function as parameter and/
data class Fish(var name: String)
fun main() {
fishExamples()
}
fun fishExamples() {
val fish = Fish("splashy")
/*
* So the difference is that "run" returns the result of executing the lambda
* while "apply" returns the object after the lambda has been applied
* This is a really common patter for initializing objects
* */
Practice Time
/*
Create an extension on List using a higher order function that returns all the numbers
Should return
> [3, 6, 9, 0]
*/
// Solution Code
fun main() {
val numbers = listOf(1,2,3,4,5,6,7,8,9,0)
println(numbers.divisibleBy3())
println(numbers.divisibleBy { it.rem(3) })
}
// My Code
fun List<Int>.divisibleBy3(): List<Int> {
return this.filter { it % 3 == 0 }
}
Inline
package Aquarium5
fun main() {
fishExamples2()
}
// PROBLEM HERE!
// Every time we call myWith, Kotlin will make a new lambda object!
// Which takes CPU time and memory!
// Lambdas are objects
// A Lambda expression is an instance of a function interface
// which is itself a subtype of object
myWith(fish.name) {
println(uppercase()) // SPLASHY
}
Practice Time
/*
In this practice, you will finish your simple game using higher-order functions, that
In the game class, create a function move() that takes an argument called where, w
Inside makeMove, test whether the String is any of the 4 directions and invoke mov
move(north)
Call makeMove() with the contents of the input from the user via readLine()
Remove the code for testing the first version of your game.
Run your program.
Challenge:
Create a simple “map” for your game, and when the user moves, show a description of th
Use a Location class that takes a default width and height to track location. 4x4
You can create a matrix like this:
Use an init block to initialize your map with descriptions for each location.
When you move() also updateLocation(). There is some math involved to prevent null
When you are done, zip up the code and send it to a friend to try your first Kotli
*/
// Solution Code
fun move(where: () -> Boolean ) {
where.invoke()
}
fun makeMove(command:String?) {
if (command.equals("n")) move(north)
else if (command.equals("s")) move(south)
else if (command.equals("e")) move(east)
else if (command.equals("w")) move(west)
else move(end)
}
while (true) {
print("Enter a direction: n/s/e/w: ")
game.makeMove(readLine())
}
///////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////
// My Solution
enum class Directions {
START, END,
NORTH, SOUTH, EAST, WEST
}
fun main() {
val game = Game()
val map = Map()
var validMove = true
while (validMove) {
print("Enter a direction: n/s/e/w: ")
val direction = readLine()
validMove = map.updateLocation(direction)
if (validMove)
game.makeMove(direction)
}
game.end()
}
class Game {
var path: MutableList<Directions> = mutableListOf(Directions.START)
val north = { path.add(Directions.NORTH) }
val south = { path.add(Directions.SOUTH) }
val east = { path.add(Directions.EAST) }
val west = { path.add(Directions.WEST) }
val end = {
path.add(Directions.END)
println("Game Over: $path")
path.clear()
false
}
// You'll run into SAM all the time in APIs written in the Java
// Java Code
class JavaRun {
public static void runNow(Runnable runnable) {
runnable.run();
}
}
/////////////////////////////////////////////////////////////////////////////////
/*
* Runnable and callable are two examples
* Basically, SAM just means an interface with one method on it, That's it
* In Kotlin, we have to call functions that take SAM
* as parameters all the time
* */
//interface Runnable {
// fun run()
//}
//
//interface Callable<T> {
// fun call(): T
//}
//interface Runnable {
// fun run()
//}
//
//interface Callable<T> {
// fun call(): T
//}
fun example() {
val runnable = object: Runnable {
override fun run() {
println("I'm a runnable")
}
}
JavaRun.runNow(runnable)
}