Kotlin
This is more of a reference style notes similar to learnxiny.com. The formatting is not nice since I wrote this in Notion and copied it here as well. It’s cool to see the similarities between Swift and Kotlin.
Class
Constructor / Init
A constructor runs when you instantiate an object. It’s used to define properties and initialize them
// 1. properties in constructor is initialised first
class Dog(val name_param: String, var weight_param: Int, val breed_param: String) {
// properties in main body
val name = name_param
val weight = weight_param
val breed = breed_param
lateinit var temperament: String
// extra code need to run, more complex property initializer
init {
// 2. this init code runs after constructor
}
// 3. created after 2.
var activities = arrayOf("walks")
init {
// 3. can have another init to run after 3
}
}
Defining properties in the main body:
- flexibility
- no need initialize with a parameter value
initialize properties before you use them lateinit → lazy (can’t initialize from constructor)
- needs to be var
- can’t use: Byte, Short, Int, Long, Double, Float, Char, or Boolean
empty constructor - compiler writes one for you
class Duck() { }
var duck = Duck()
Properties
custom getter/setters
val weightInKgs = Double
get() = weight/2.2 //gettter - no parameter function
// setter - runs each time you try to set the value
set(value) {
if (value>0) field = value // if value<0 value won't be set
}
/* ---------------------------------------------------------------------- */
var myProperty: String
// compiler secretly adds the ff:
var myProperty: String
get() = field
set(value) {
field = value
}
// data hiding - always calls getter & setter when using dot operator
field
- property’s backing field
- reference to underlying value of property
- use in getter & setter methods
- stop getting stuck in an endless loop
Inheritance
Don’t use inheritance if the IS-A test fails (just so you can reuse code)
open
- prefix explicitly tell compiler class can be used as a superclass
- prefix on properties that can be overriden
open class Animal(type: String) {
open val image = ""
val food = ""
open fun makeNoise() { ... }
// final stops override
final fun sleep() { ... }
}
class Hippo(type: String): Animal(type) {
override val image = "hippo.jpg"
override fun makeNoise() { ... }
}
- subclass must call superclass primary constructor in header
- override val in superclass with var in subclass
- override a property’s type with its superclass
Abstract classes
- prevents class from being instantiated
- can contain abstract & non-abstract properties and functions
- make properties abstract so they need to be overriden
- abstract properties & functions → common protocol (polymorphism)
- first concrete class below abstract superclass must implement all abstract properties & functions
Interface
- protocol for common behaviour
- benefit from polymorphism without inheritance
- can add concrete functions to interfaces (provide a function body)
- properties can’t store state
- don’t have backing fields
- class can inherit from a single direct superclass, but can have multiple interfaces
- override interface methods & properties in concrete class
When to use which
- class - new class doesn’t pass the IS-A test for any other type.
- subclass - make a more specific version of a class and need to override or add new behaviors.
- abstract class - template for a group of subclasses. Guarantee that nobody can make objects of that type.
- interface - define common behavior, or a role that other classes can play, regardless of where these classes are in the inheritance tree.
Any
- mother of all classes
- ensures every class inherits the same behaviour
- equals(any: Any): Boolean
- hashCode(): Int
- toString(): String
- can use polymorphism with any object
data class
- swift struct
- object equality based on values of each object’s properties
- equal object has same hashCode
- toString value of each property
- ===
- reference to the same object
- best practice use val properties
- can use var but not recommended
- can’t be abstract or open
- only include properties in primary constructor
- compiler generated equality & copy
data class Recipe(val title: String,
val mainIngredient: String,
val difficulty: String = "Easy") { //default parameter values
//secondary constructor
constructor(is_my_specialty: Boolean): this("Aglio Olio", "Prawn") {
//code runs when secondary constructor is called
}
}
// pass values in declaration
val r = Recipe("Spaghetti", "Beef")
// named arguments
val r2 = Recipe(title = "Spaghetti",
mainIngredient = "Beef",
difficulty = "Medium")
Enum classes
- create a set of values that represent the only valid values for a variable
- each value defined by the enum class is an instance of that class
- each value defined in an enum class can override the properties and functions it inherits from the class definition
// instrument is a property each BandMember has
enum class BandMember(val instrument: String) {
JERRY("lead guitar") {
override fun sings() = "plaintively"
},
BOBBY("rhythm guitar") {
override fun sings() = "hoarsely"
},
PHIL("base");
open fun sings() = "occasionally"
}
var selectedBandMember = BandMember.JERRY
println(selectedBandMember.instrument)
println(selectedBandMember.sings())
Sealed class
- restrict your class hierarchy to a specific set of subtypes
- sealed
- similar to swift enum with associated value
- sealed class lets you create multiple instances of each subtype
sealed class MessageType
class MessageSuccess(var msg: String): MessageType()
class MessageFailure(var msg: String, var e: Exception): MessageType()
fun main(args: Array<String>) {
val messageSuccess = MessageSuccess("Yay!")
val messageSuccess2 = MessageSuccess("It worked!")
val messageFailure = MessageFailure("Boo!", Exception("Gone wrong."))
var myMessage: MessageType = messageFailure
}
Nested and inner class
- Nested
- class that’s defined inside another class
- can’t access a nested class from an instance of the outer class without first creating a property of that type inside the outer class
- val nested = Outer().Nested() //won’t compile
- class doesn’t have access to an instance of the outer class
- inner
- access properties & functions defined by its outer class
- an access an inner class by creating an instance of the outer class, and then using this to create an instance of the inner class
class Outer {
val x = "x in outer class"
val myInner = Inner()
class Nested {
val y = "y in the nested class"
fun myFun() = "this is nested function"
//fun getX() = "value of x is $x" //this line won't compile
}
inner class Inner {
val y = "y in the nested class"
fun myFun() = "this is nested function"
fun getX() = "value of x is $x"
}
}
val nested = Outer.Nested()
val inner = Outer().Inner()
Singleton
- object
- only a single instance of a given type can be created
- Add an object declaration to a class to create a single instance of that type which belongs to the class
- companion
- A companion object is like a class object, except that you can omit the object’s name
- Any functions you add to a companion object are shared by all class instances
object DuckManager {
fun herdDucks() { }
}
DuckManager.herdDucks()
class Duck {
object DuckFactory {
fun create(): Duck = Duck()
}
companion object {
fun create(): Duck = Duck()
}
}
val duckFromFactory = Duck.DuckFactory.create()
val duckCompanion = Duck.create()
val duckCompanion2 = Duck.Companion.create()
Object expression
- object
- similar to Swift tuple
- anonymous object with no predefined type
val startingPoint = object {
val x = 0
val y = 0
}
println("starting point is ${startingPoint.x}, ${startingPoint.y}")
is
- check for types
val animal: Animal = Wolf()
if (animal is Wolf) {
animal.doWolfThings()
}
as
- explicit cast
- both hold reference to same object (but knowledge of what it is might be different)
var wolf = r as Wolf
wolf.doWolfThings()
when
- Swift Switch statement
when (x) {
0 -> println("x is 0")
1,2 -> println("x is 1,2")
else -> {
println("x is others")
}
}
Nullability
- similar to swift
- ?. (safe call operator)
- can be chained
- ?.let
- similar to if let (swift)
- allows you to run code for a value that’s not null
- use {} (curly braces) for the body
- ?: (Elvis operator)
- if value is not null return value, else return value on the right
- !! (not-null assertion operator)
- throws NullPointerException
var w: Wolf? = Wolf()
// safe call operator
w?.doWolfThings()
// if let equivalent
w?.let {
println(it.hunger)
}
// elvis
w?.hunger ?: -1
Try Catch Finally
try {
turnOvenOn()
x.bake()
} catch (e: BakingException) {
println("baking experiment failed")
} finally {
turnOvenOff()
}
Exception
- Every exception is of type: Exception
- throw use to throw an exception
- type Nothing
val h = w?.hunger ?: throw AnimalException()
Collections
Array
- can’t change size
- val numbers = arrayOf(1,2,3)
- can be updated
List
- When sequence matters
- Iterable
- (Might be similar to array swift?)
- Allows duplicate values
val shopping = listOf("Tea", "Eggs", "Milk")
// iterate
for (item in shopping) println(item)
// check existence
if (shopping.contains("Milk")) {
println(shopping.indexOf("Milk"))
}
val updateableList = mutableListOf("Tea")
updateableList.add("Bread")
if (updateableList.contains("Bread")) {
updateableList.remove("Bread")
}
val toAdd = listOf("Cookies", "Sugar")
updateableList.addAll(toAdd)
val toRemove = listOf("Milk", "Sugar")
updateableList.removeAll(toRemove)
Set
- When uniqueness matters
- Iterable
- Steps for determining uniqueness
- Gets the object’s hash code and uses that to compare with other objects in the set
- hash code as “bucket”
- matching hash codes go to step 2
- === operator to compare the new value against any objects it contains with the same hash code (bucket)
- == operator to compare the new value against any objects it contains with matching hash codes
- Gets the object’s hash code and uses that to compare with other objects in the set
- Same value
- same object
- equal to a value it already contains
val friendSet = setOf("Jim", "Sue", "Sue", "Nick", "Nick")
val mFriendSet = mutableSetOf("Jim", "Sue")
mFriendSet.add("Nick")
mFriendSet.remove("Nick")
Map
- Key value pair
- NOT iterable!
- Similar to dictionary in swift
val ri = Recipe("Chicken soup")
val r2 = Recipe("Thai Curry")
val recipeMap: Map<String, Recipe> = mapOf("recipe1" to r1, "recipe2" to r2)
recipeMap.containsKey("recipe1")
recipeMap.containsValue(r1)
if (recipeMap.containsKey("recipe1") {
val recipeMap.getValue("recipe1")
}
for ((key, value) in recipeMap) {
println("Key is $key, value is $value")
}
val mRecipeMap = mutableMapOf("recipe1" to r1, "recipe2" to r2)
val r4 = Recipe("Jambalaya")
val r5 = Recipe("Sausage Rolls")
val recipesToAdd = mapOf("Recipe4" to r4, "Recipe5" to r5)
mRecipeMap.putAll(recipesToAdd)
mRecipeMap.values
mRecipeMap.keys
Generics
- similar definition in swift?
- val x: MutableList< String>
- Compiler can infer generic type
interface MutableList<E> : List<E>, MutableCollection<E> { }
abstract class Pet(var name: String)
class Cat(name: String): Pet(name)
class Dog(name: String): Pet(name)
class Fish(name: String): Pet(name)
class Contest<T: Pet> { }
fun <T: Pet> listPet...
// to call function, specify type
val catList = listPet<Cat>(Cat("Zazzles"))
// can define multiple generic types
class MyMap<K, V> { }
- A Retailer
variable will only accept a Retailer object - val petRetailer: Retailer
= CatRetailer() //this wont' compile
- val petRetailer: Retailer
out (covariant)
- generic subtype object in a place of a generic supertype
- generic type is covariant
- can use a subtype in place of a supertype.
- can’t use if the class has function parameters or var properties of that generic type.
- used for collections
interface Retailer<T> {
fun sell(): T
}
// val petRetailer: Retailer<Pet> = CatRetailer() //this wont' compile
interface Retailer<out T> { .. }
val petRetailer: Retailer<Pet> = CatRetailer()
in (contravariant)
- lets you use a generic supertype in place of a subtype
- can use a supertype in place of a subtype. This is the opposite of covariance.
- can’t use if class functions use it as a return type, or if that type is used by any properties, irrespective of whether they’re defined using val or var
class Vet<in T: Pet> {
fun treat(t: T) {
}
}
val catContest = Contest<Cat>(Vet<Pet>())
// can be defined locally
class Vet<T: Pet> {
fun treat(t: T) {
}
}
class Contest<T: Pet>(var vet: Vet<in T>) { }
invariant
- generic type has no in or out prefix
- can only accept references of that specific ty
Lambdas
- type of object that holds a block of code
- (parameters) -> return_type
- { x: Int, y: Int → x + y }
- →
- separate parameters from the body
- invoke
- call lambda
- it
- lambda with 1 parameter that the compiler can infer
- Unit
- similar to return Void (in swift)
- lambda has no return value
- () → Unit
val addInts = { x: Int, y: Int -> x + y }
val result = addInts.invoke(6, 7)
val result2 = addInts(6, 7)
val greeting: () -> String = { "Hello!" }
val addFive: (Int) -> Int = { it + 5 }
- Higher order function
- uses a lambda as a parameter or return value
- can move the lambda outside ()
- similar to swift
- function can return a lambda
typealias
- provide different name for an existing type
- typealias DoubleConversion = (Double) → Double
- typealias DuckArray = Array
higher order functions
- filter
- minBy
- maxBy
- If you call minBy or maxBy on a collection that contains no items, the function will return a null value.
- map
- returns a List and not Map
- forEach
- similar to for loop
- arrays, Lists, Sets, and on a Map’s entries, keys and values properties.
- Not Map
- has no return value
- groupBy
- group your items in a collection according to some criteria
- doesn’t work on Map
- returns a Map with a List as the value
- Map<String, List
>
- Map<String, List
- fold
- similar to reduce
- specify an initial value, and perform some operation on it for each item in a collection
- doesn’t work on Map
- default left to right
- use foldRight
- reduce
- similar to fold but no need to specify initial value
- default left to right
- use reduceRight
- can chain higher order functions
Coroutines
- write code that runs asynchronously
- coroutine
- like a lightweight thread
- run on a shared pool of threads
- same thread can run many coroutines
- launching is like starting a separate thread of execution, or thread
- run on a shared pool of threads by default, and the same thread can run many coroutines
- GlobalScope.launch
- separate coroutine in the background by enclosing the code
-
kotlinx.coroutines library
fun main() { GlobalScope.launch { playBeats("x-x-x-") } playBeats("x----x----") }
- runBlocking
- code to run in the same thread but in separate coroutines
- blocks the current thread until the code that’s passed to it finishes running
- blocks the current thread
- launch
- launch a new coroutine
- launch a coroutine that runs in the same thread
- use the same scope as runBlocking
fun main() {
runBlocking {
launch { playBeats("x-x-x-") }
playBeats("x----x----")
}
}
- delay
- pauses the current coroutine
- allows other code to run
- similar to Thread.sleep
- suspend
-
call a suspendable function (such as delay) from another function, that function must be marked with suspend
suspend fun playBeats(beats: String) { delay(..) }
-
Test
JUnit
- Each test is held in a function, prefixed with the annotation @Test
- assertEquals
- use back ticks for function names
- wrap function names in back-ticks (`)
- fun
should be able to add 3
()
KotlinTest
- String Specification—or StringSpec
- use rows to test your code against entire sets of data
"should be able to add 3 and 4 and it musn't go wrong" {
val totaller = Totaller()
totaller.add(3) should be 7
}
Package and import
- Package
- organize your code
- prevents name conflicts
- Each source file can only have one package declaration
- import
- can refer to the class without typing the fully qualified name each time
Visibility Modifiers
- public
- visible everywhere
- default behaviour
- private
- visible to code inside its source file
- internal
- visible inside the same module, but invisible elsewhere
- module - set of Kotlin files that are compiled together
- protected
- applied to properties, functions & other members
- Makes the member visible inside the class, and any of its subclasses.
Extensions
- add new functions and properties to an existing type without you having to create a whole new subtype.
// add function to Double
fun Double.toDollar(): String {
return "$$this"
}
let dbl = 42.25
println(dbl.toDollar())
// add property to String
val String.halfLength
get() = length/2.0
val test = "This is a test"
println(test.halfLength)