Skip to content

Kotlin language

Posted on:September 23, 2022 at 03:22 PM

Basics

Note: exitProcess is a Kotlin standard library function that terminates the running instance of the JVM.

Behaviour of == operator

open class Weapon(val name: String, val type: String)
println(Weapon("ebony kris", "dagger") == Weapon("ebony kris", "dagger")) // False

Build tools in Kotlin

Running a Kotlin program

$ kotlinc main.kt -include-runtime -d main.jar
$ java -jar main.jar

Package and class naming conventions

Conditional expressions, if-else, while

Exceptions

Ranges in Kotlin

Extracting a code to function

File level variables

Compile-Time Constants

Naming convention

Functions in Kotlin

// Compile time constant
const val FIRST_NAME = "Saurabh"
const val MAX_COUNT = 8
val USER_NAME_FIELD = "UserName"

// Function with single expression as body
fun sayHello(name: String) = println("Hello, $name!")

// Function with variable no. of arguments
fun printString(vararg names:String) {
for (name in names) {
println(name)
  }
// if we want to pass this "names" to another function
// we need to use "spread operator",  "*" , i.e. "*names"
}

Functions, properties and local variables naming conventions

Single expression functions short form

// A higher order function, runMyRunnable,
// taking a function parameter
fun runMyRunnable(runnable: () -> Unit) { runnable() }
// Invoke and pass a lambda
runMyRunnable { println("Hello world") }

// runMyRunnable1 is a higher order function,
// taking a function parameter and returning
// a lambda containing that function
fun runMyRunnable1(runnable: () -> Unit) = { runnable() }
runMyRunnable1 { println("Hello world") }()

Unit functions

Functions with nothing as return type

// Always throws [NotImplementedError] stating
// that operation is not implemented.
public inline fun TODO(): Nothing = throw NotImplementedError()

Use of TODO()

Function overloading

Function names using backticks

fun `**~prolly not a good idea!~**`() {
...
}

Anonymous function

{// Defining and invoking anonymous function
val currentYear = 2018
"Welcome to SimVillage, Mayor! (copyright $currentYear)"
}()

fun main(args: Array<String>) {
  // Notice, no parenthesis around parameter in definition
  val greetingFunction: (String) -> String = { playerName ->
    val currentYear = 2018
    "Welcome to SimVillage, $playerName! (copyright $currentYear)"
  }
  println(greetingFunction("Guyal"))
}

The it keyword

fun main(args: Array<String>) {

  val greetingFunction: (String) -> String = {
    val currentYear = 2018
    "Welcome to SimVillage, $it! (copyright $currentYear)"
  }
  println(greetingFunction("Guyal"))
}

Lambdas vs anonymous function

Lambda Expression in Kotlin

Lambda functions usage

val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.maxBy { it.age })
Person(name=Bob, age=31)

Syntax for lambda expressions

Running a Lambda function

Breaking down syntax

Lambda, capturing variables from the context

fun printProblemCounts(responses: Collection<String>) {
  var clientErrors = 0
  var serverErrors = 0
  responses.forEach {
    if (it.startsWith("4")) {
      clientErrors++
    } else if (it.startsWith("5")) {
      serverErrors++
    }
  }
  println("$clientErrors client errors, $serverErrors server errors")
}

Member references, a feature that lets you easily pass references to existing functions

Anonymous functions

Local functions: function with in a function

Anonymous functions Vs lambda expression

Lambda: Return and local return

Tail recursion

Lambda extension: lambda with receivers

This help in creating a DSL like code. You can access the class properties in the lambda function.

Invoking instances in Kotlin

Function Inlining

Infix functions

Function References

Using Java functional interfaces

Kotlin’s Lambdas Are Closures

Kotlin vs Java: Functional programming

public interface Runnable {
  public abstract void run();
}

fun runMyRunnable(runnable: () -> Unit) = { runnable() }
runMyRunnable { println("hey now") }()

Nullability

val languageName: String = null //Invalid code, fails to compile
val languageName: String? = null //Valid code

Use of ?, let and !! with nullable values

Null coalescing/Elvis operator, ?:

Precondition functions

String functions

Destructuring

String Templates

Strings in kotlin are immutable.

Using replace method of string

fun toDragonSpeak(phrase: String) =
  phrase.replace(Regex("[aeiou]")) {
    when (it.value) {
      "a" -> "4"
      "e" -> "3"
      "i" -> "1"
      "o" -> "0"
      "u" -> "|_|
      "else -> it.value
      }
  }

Specifying unicode characters

Iterating over a String

"Dragon's Breath".forEach {
  println("$it\n")
  }

Numbers in Kotlin

TypeBitsMax ValueMin Value
Byte8127-128
Short1632767-32768
Int322147483647-2147483648
Long649223372036854775807-9223372036854775808
Float323.4028235E381.4E-45
Double641.7976931348623157E3084.9E-324

Every number type supports the following conversions:

No implicit conversions of types while assignment

Implicit conversion of types on arithmetic operation

val l = 1L + 3 // Long + Int => Long

Literal constants

// Use of underscore in number constants
val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010

Boxing/unboxing/autoboxing

Note: In JVM, the numbers are internally stored as primitive types, unless they are nullable or generics is involved.

Arithmetic operations

Converting a String to a Numeric Type

Format double values

Converting doubles to Int

Bit manipulation on Int

val x = (1 shl 2) and 0x000FF000

Standard Functions in Kotlin

Apply function

val menuFile = File("menu-file.txt")
menuFile.setReadable(true)
menuFile.setWritable(true)
menuFile.setExecutable(false)

// Above can be written as
val menuFile = File("menu-file.txt").apply {
  setReadable(true) // Implicitly, menuFile.setReadable(true)
  setWritable(true) // Implicitly, menuFile.setWritable(true)
  setExecutable(false) // Implicitly, menuFile.setExecutable(false)
}

Run function

fun nameIsLong(name: String) = name.length >= 20
// run with a function reciever
"Madrigal".run(::nameIsLong)  // False

// Less common use of run
val status = run {
          if (healthPoints == 100) "perfect health" else "has injuries"
      }

Let function

Also function

Let vs apply

With function

val nameTooLong = with("Polarcubis, Supreme Master of NyetHack") {
          length >= 20
}

TakeIf function

// with takeIf
val fileContents = File("myfile.txt")
.takeIf { it.canRead() && it.canWrite() }
?.readText()

// Without takeIf
val file = File("myfile.txt")
val fileContents = if (file.canRead() && file.canWrite()) {
    file.readText()
} else {
    null
}

TakeUnless function

FunctionPasses receiver to lambda as argument?Provides relativescoping?Returns
letYesNoLambda result
applyNoYesReceiver
runNoYesLambda result
withNoYesLambda result
alsoYesNoReceiver
takeIfYesNoNullable version of receiver
takeUnlessYesNoNullable versionof receiver

Collections, List, Set, and Map

parameterized type

List

Immutable list creation

Accessing a list’s elements

Accessing a list’s elements safely

val patronList = listOf("Eli", "Mordoc", "Sophie")
patronList.getOrElse(4) { "Unknown Patron" }

val fifthPatron = patronList.getOrNull(4) ?: "Unknown Patron"

Checking the contents of a list

Mutable list

Iterting a list

val patronList = listOf("Alex", "Tony", "Rocky", "Tina")
  // Normal iteration
  for (patron in patronList) {
      println("Good evening, $patron")
  }
  // functional style iteration
  patronList.forEach { println("Good evening, $it!")}
  // Iterating with index
  patronList.forEachIndexed {index, s -> println("$s you're $index")}

Changing between mutable and immutable list

What is an iterable

Reading a File into a List

// readText() returns the contents of the file
// as a String. Then split on newline
val fileList = File("Data/tavern-menu-data.txt")
        .readText()
        .split('\n').forEach { println(it) }

Destructuring a list

val (type, name, price) = menuData.split(',')

// Using _ to skip unwanted elements
val (type, _, price) = menuData.split(',')

Sets in Kotlin

Creating a set

//Empty Set, notice type specification
val stars = setOf<String>()

val planets = setOf("Mercury", "Venus", "Earth")

Set methods

Accessing Set elements

MutableSet

Mutable set mutator functions

Using while loops to iterate over collection

Use break to break out of any loop

Collection Conversion

Arrays in Java and Kotlin

Array typeCreation function
IntArrayintArrayOf
DoubleArraydoubleArrayOf
LongArraylongArrayOf
ShortArrayshortArrayOf
ByteArraybyteArrayOf
FloatArrayfloatArrayOf
BooleanArraybooleanArrayOf
ArrayarrayOf

Note: Array compiles to a primitive array that holds any reference type.

Creating empty collection

Maps in Kotlin

Specifying type of a map

Creating a Map

// Using to keyword
val patronGold = mapOf("Eli" to 10.5, "Mordoc" to 8.0, "Sophie" to 5.5)

// Using Pair() method
val patronGold = mapOf(Pair("Eli", 10.75),
  Pair("Mordoc", 8.00),
  Pair("Sophie", 5.50))

To keyword in kotlin

Tuples

Adding a duplicate key in map

Map accessor functions (Accessing Map Values)

Mutable map mutator functions

Kotlin collections summary

Collection typeOrdered?Unique?StoresSupports destructuring?
ListYesNoElementsYes
SetNoYesElementsNo
MapNoKeysKey-value pairsNo

Classes in Kotlin

Defining Classes

Property vs field

Properties

Concept of backing properties

class C {
    private val _elementList = mutableListOf<Element>()

    val elementList: List<Element>
         get() = _elementList
}

Constructing Instances

// Code below is valid
fun main(args: Array<String>) {
    class Player
    val player = Player()
}

Visibility and Encapsulation

ModifierDescription
public(default)The function or property will be accessible by code outside of the class. By default, functions and properties without a visibility modifier are public.
privateThe function or property will be accessible only within the same class.
protectedThe function or property will be accessible only within the same class or its subclass.
internalThe function or property will be accessible within the same module

Note: Unlike Java, package private visibility level is not included in Kotlin.

Top level declarations:

  1. private: Available anywhere inside the file containing declaration
  2. internal: Available anywhere in the same module.

Class declaration options

// Overriding default getter & setter
fun main(args: Array<String>) {
    class Player {
        var name = "madrigal"
            get() = field.capitalize()
            private set(value) {
                field = value.trim()
            }
    }
}

Computed properties

// Computed property
fun main(args: Array<String>) {
    class Dice() {
        val rolledValue
            get() = (1..6).shuffled().first()
    }
}

Using Packages in Kotlin

Guarding Against Race Conditions

// gaurding against race condition
fun main(args: Array<String>) {
    class Weapon(val name: String)
    class Player {
        var weapon: Weapon? = Weapon("Ebony Kris")
        fun printWeaponName() {
            if (weapon != null) {
                // Compiler error in line below
                // Smart cast to weapon is impossible as
                // it is a mutable property and could change
                // after the null check
                println(weapon.name)
            }
        }

        // This will not give compile error
        // as "it" is a local variable, accessible
        // only within the lambda scope.
        fun printWeaponName1() {
            weapon?.also { println(it.name) }
        }
    }

    fun main(args: Array<String>) {
        Player().printWeaponName()
    }
}

Private visibility vs internal visibility

Initialization in Kotlin

Constructors

Primary constructors

// Class with "constructor" keyword due to annotation
// and visibility modifier
class Customer public @Inject constructor(name: String) { /*...*/ }

Why prepend variable names with underscores

fun main(args: Array<String>) {
  // Properties defined in the class body
  class Player(_name: String,
            _healthPoints: Int,
            _isBlessed: Boolean,
            _isImmortal: Boolean) {
    var name = _name
        get() = field.capitalize()
        private set(value) { field = value.trim() }
      var healthPoints = _healthPoints
      val isBlessed = _isBlessed private
      val isImmortal = _isImmortal
    }
}

Defining properties in a primary constructor

fun main() {
  // Properties defined in primary constructor
  // Primary constructor with default argument
  class Player(
      _name: String,
      var _healthPoints: Int = 100,
      val _isBlessed: Boolean,
      private val _isImmortal: Boolean
  ) {
      var name = _name
        get() = field.capitalize()
        private set(value) {
            field = value.trim()
        }

      // Init block for argument validation
        init {
          require(_healthPoints > 0) { "healthPoints must be greater than zero." }
          require(name.isNotBlank()) { "Player must have a name." }
        }

      // Secondary constructor with
      // initialization logic
      constructor(name: String) : this(
        name,
        _isBlessed = true,
        _isImmortal = false ) {
          if (name.toLowerCase() == "kar") _healthPoints = 40
        }
    }
}

Secondary constructors

class Person(val name: String) {
    var children: MutableList<Person> = mutableListOf<>()
    constructor(name: String, parent: Person) : this(name) {
        parent.children.add(this)
    }
}

Initializer Blocks

Property initialization

Initialization Order

  1. The primary constructor’s inline properties.
  2. Required class-level property assignments.
  3. init block property assignments and function calls.
  4. secondary constructor property assignments and function calls.
  5. Initialization order of the init block (item 3) and the class-level property assignments (item 2) depends on the order they are specified in.

Delaying Initialization

Late initialization

Lazy initialization

// Use of lazy property
fun main() {
  class Player(
      _name: String,
      var healthPoints: Int = 100,
      val isBlessed: Boolean,
      private val isImmortal: Boolean
  ) {
      var name = _name
        get () = "${field.capitalize()} of $hometown"
        private set(value) { field = value.trim() }

      val hometown by lazy { selectHometown() }

      private fun selectHometown() = File("towns.txt")
          .readText()
          .split("\n")
          .shuffled()
          .first()
    }
}

Inheritance

Creating a Subclass

// Example of extandable class
fun main() {
  open class Room(val name: String) {
    protected open val dangerLevel = 5
    fun description() = "Room: $name"
    open fun load() = "Nothing much to see here..."
  }

  // Create subclass
  class TownSquare : Room("Town Square")

  // subclass another version
  open class TownSquare1 : Room("Town Square") {
    override val dangerLevel = super.dangerLevel - 3
    final override fun load() = "The villagers rally and cheer as you enter!"
  }

  // Room can hold TownSquare type
  // the functions called will be from
  // TownSquare, this is polymorphism
  var currentRoom: Room = TownSquare1()
  println (currentRoom.description())
  println (currentRoom.load())

  // true
  currentRoom is Room
}

Polymorphism

Type Checking

Type casting

fun main() {
  fun printIsSourceOfBlessings(any: Any) {
    val isSourceOfBlessings = if (any is Player) {
      // smart casting done by conpiler
      any.isBlessed
    } else {
        (any as Room).name == "Fount of Blessings"
    } println ("$any is a source of blessings: $isSourceOfBlessings")
  }
}

Smart casting

Any class

Objects

The object Keyword

Object declarations(Singleton)

// Name of class is the name of the singleton object (for eg. Global)
object Global {
  val PI = 3.14
}

Object expressions (Anonymous inner class)

fun main() {
  // subclass of TownSquare, object expression
    val abandonedTownSquare = object : TownSquare() {
        override fun load() = "You anticipate applause, but no one is here..."
    }
}

Companion objects

fun main() {
    class PremadeWorldMap {
        companion object {
            private const val MAPS_FILEPATH = "nyethack.maps"
            fun load() = File(MAPS_FILEPATH).readBytes()
        }
    }

    // call method of companion object
    PremadeWorldMap.load()
}

Nested Classes and Inner classes

Data Classes

// create a new instance of Player that has all of the same
// property values as another player except for isImmortal
val mortalPlayer = player.copy(isImmortal = false)

// Destructuring in data class
val (x, y) = Coordinate(1, 0)

// Copying with some properties changed
customer2 = customer1.copy(name="saurabh")

Destructuring declarations in data classes

Limitations and requirements on data classes

Enumerated Classes

enum class Direction(private val coordinate: Coordinate) {
    NORTH(Coordinate(0, -1)),
    EAST(Coordinate(1, 0)),
    SOUTH(Coordinate(0, 1)),
    WEST(Coordinate(-1, 0));

    fun updateCoordinate(playerCoordinate: Coordinate) =
        Coordinate(playerCoordinate.x + coordinate.x,
            playerCoordinate.y + coordinate.y)
}

data class Coordinate(val x: Int, val y: Int) {
    val isInBounds = x >= 0 && y >= 0
    // Operator Overloading
    operator fun plus(other: Coordinate) = Coordinate(x + other.x,
                                                    y + other.y)
}

fun main() {
    Direction.EAST
    Direction.EAST.updateCoordinate(Coordinate(1, 0))
}

Operator Overloading

Algebraic Data Types

Sealed classes

Interfaces and Abstract Classes

Interface

fun main() {
// Interface
  interface Fightable {
    var healthPoints: Int
    val diceCount: Int
    val diceSides: Int
    val damageRoll: Int
    fun attack(opponent: Fightable): Int
  }

  // Implementing interface
  class Player(
    _name: String,
    override var healthPoints: Int = 100,
    var isBlessed: Boolean = false,
    private var isImmortal: Boolean
  ) : Fightable { ... }
}

Implementing an Interface

Default Implementations in interface

Abstract Classes

Generics

Defining Generic Types

class LootBox<T>(item: T) {
  private var loot: T = item
}

interface repository<T> {
  fun getById(id:Int):T
  fun getAll():List<T>
}

interface Repo {
  fun<T> getById(id:Int):T
}

Generic type parameter naming convention

Restrict T to a specific supertype

// Restrict T to Pet or its subclasses
class Contest<T: Pet> {

}

Use of in and out for generic parameters

// Use of "out" keyword fot generics
class Barrel<out T>(val item: T)

fun main() {
  var fedoraBarrel = Barrel(Fedora("a generic-looking fedora", 15))
  var lootBarrel = Barrel(Coin(15))

  // Allowed because of "out"
  lootBarrel = fedoraBarrel

  val myFedora: Fedora = lootBarrel.item
}

// Use of "in" keyword fot generics
class Barrel<in T>(var item: T)

fun main() {
  var fedoraBarrel = Barrel(Fedora("a generic-looking fedora", 15))
  var lootBarrel = Barrel(Coin(15))

  // Not Allowed because of "in"
  lootBarrel = fedoraBarrel

  // Allowed because of "in"
  fedoraBarrel = lootBarrel

  val myFedora: Fedora = lootBarrel.item
}

The reified Keyword

Kotlin Extensions

Extension Properties

Kotlin annotations

Strong reference in Java/Kotlin

Weak Reference in Java/Kotlin

Soft Reference in Java/Kotlin

Delegated properties use cases

Note: The convention is, if the last, or in this case the only, parameter of a function is another function, it doesn’t need to go into brackets.

Extension functions

// Extension function to add function to string class
fun String.hello() {
  println("Hi there!!")
}

Interoperability with Java

Working with nulls from java

Talking Kotlin in Java

Top level functions and properties in Kotlin (how to access them from Java):

Accessing top level function from Java

Interoperatbility with Java7 and 8

Kotlin standard library

Hashmap creation

Filtering, Mapping, Flatmapping in Kotlin

Concept of map and flatmap

Lazy evaluation with sequences in Kotlin

Functional constructs in Kotlin

Type aliases in Kotlin

Delegation

Problems with inheritance

Delegation of properties

Delegating member function in kotlin

Syntax: Class xyz(repository: Repository): Repository by repository {}

Now xyz can call the methods of Repository directly, without dereferencing . This is possible because the repository instance is passed to the constructor. We should not inject too many dependencies, at most two.

Metaprogramming:

Coroutines and reactive extensions in kotlin

Kotlin coroutines

What is a coroutine:

onButtonClicked() {
    scope.launch {
    //suspend function
  }
}

Structured concurrency:

Coroutine scope:

How to create coroutine scope

How to cancel all coroutines created in a scope. After this you cannot launch any coroutines from this scope.

How to launch a coroutine within a scope.

Handling exceptions in a scope

Scope with a job

Asynchronous programming

Async await in kotlin

These are std library functions and not keywords.

Yields in kotlin

DSL : Domain specific language.

Java Bean