Kotlin programming language
Kotlin programming language
Object-Oriented Programming

Kotlin programming language

Object-Oriented Programming

A programming paradigm based on the representation of a
program as a set of objects and interactions between them

Kotlin programming language

Class and Object

Class – A set of attributes (fields, properties) and related methods (functions, procedures) that together represent some abstract entity

  • Attributes store state
  • Methods express behavior

Object – An instance of a class,
which has its own specific state.

class Person:
    String attribute name
    Boolean attribute busy
    Method greet

Person p:
    name = "Oleg"
    busy = false

p.greet()
Kotlin programming language

Object (class/type) invariant

Invariants place constraints on the state of an object, maintained by its methods right from construction

It is the object’s own responsibility to ensure that the invariant is being maintained

Corollaries:

  • Public fields are nasty
  • If a field does not participate in the object’s invariant, then it is not clear how it belongs to this object at all, which is evidence of poor design choices
Kotlin programming language

Abstraction

Objects are data abstractions with internal representations, along with methods to interact with those internal representations

There is no need to expose internal implementation details, so those may stay “inside” and be hidden

Kotlin programming language

Encapsulation

Encapsulation – Bundling data with methods operating on said data
Allows you to hide the implementation details from the user

  • An object is a black box. It accepts messages and replies
  • Encapsulation and the interface of a class are intertwined:
    Anything that is not part of the interface is encapsulated
Kotlin programming language

Abstraction vs Encapsulation

Abstraction is about what others see and how they interact with an object

Encapsulation is about how an object operates internally and how it responds to messages

Kotlin programming language

Encapsulation

Most programming languages provide special keywords for modifying the accessibility or visibility of attributes and methods

In Kotlin:

  • publiс – Accessible to anyone
  • private – Accessible only inside the class
  • protected – Accessible inside the class and its inheritors
  • internal – Accessible in the module
Kotlin programming language

Inheritance

Inheritance – The possibility to define a new class based on existing one, keeping the base class functionality (state/behavior)

  • The class that is being inherited from is called a base or parent class
  • The new class is called a derived class, a child, or an inheritor
  • The derived class fully satisfies the specification of the base class, and it may add some features (state/behavior)
Kotlin programming language

Inheritance #2

  • "General concept – specific concept"
    • Is-A relationship
  • Motivation
    • Keep shared code separate – in the base class – and reuse it
    • Type hierarchy, subtyping
    • Incremental design

Inheritance is often redundant and can be replaced with composition

Kotlin programming language

Subtyping

A class can extend another class or implement interfaces — when it does, it becomes a subtype of those types

A subtype inherits the interface and expected behaviors of its supertype, and may define additional ones of its own

open class Person(val name: String)
class Student(name: String, val major: String) : Person(name)

Student is a subtype of Person. Every Student is a Person — but not every Person is a Student

Kotlin programming language

Subtyping #2

Because a class can implement multiple interfaces, an object can belong to several types at once:

interface Swimmer
interface Developer

class ClubMember(name: String) : Person(name), Swimmer, Developer

ClubMember is simultaneously a Person, a Swimmer, and a Developer

These types are determined by the class definition — they are fixed and known
at compile time, not something that changes at runtime

Kotlin programming language

Subtyping #3

Polygon Quadrangle Triangle Rectangle Square Rhombus Parallelogram IsoscelesTriangle RightTriangle
Kotlin programming language

Polymorphism

Polymorphism – A core OOP concept that refers to working with objects through their interfaces without knowledge about their specific types and internal structure

  • Inheritors can override and change the inherited ancestral behavior
  • Objects can be used through their parents’ interfaces
    • The client code does not know (or care) if it is working with the base class or some child class, nor does it know what exactly happens “inside”

Liskov Substitution Principle (LSP) – If for each object o1 of type S, there is an object o2 of type T, such that for all programs P defined in terms of T the behavior of P is unchanged when o1 is substituted for o2, then S is a subtype of T

Kotlin programming language

OOP in Kotlin

class UselessClass

fun main() {
    val uselessObject = UselessClass() // () here is constructor invocation
}
Kotlin programming language

Constructors

class Person()
{
    var name: String    // ❌ Error: Property must be initialized or be abstract
}
class Person(name: String)      // Primary ctor with parameter - name
{
    var name: String = name     // Mutable property initialized
}

val person = Person("John")
//...
//...
person.name = ""           // ✅ Allowed: 'var' can be reassigned
Kotlin programming language

Constructors #2

class Person(name: String)      // Primary ctor with parameter - name
{
    val name: String = name     // Immutable property initialized
}
val person = Person2("John")
//...
//...
person.name = ""           // ❌ Error: 'val' cannot be reassigned
Kotlin programming language

Constructors #3

class Person(name: String)      // Primary ctor with parameter - name
{
    val name: String = name
}

Shorthand, idiomatic way to declare and initialize a property in one go:

class Person(val name: String)   // property — declared + initialized automatically
Kotlin programming language

Constructors #4

class Person(val name: String, val age: Int) { // 1. primary constructor
    val isAdult: Boolean

    init { // 2. runs as part of the primary constructor
        println("Person created: $name")
        isAdult = age >= 18
    }

    constructor(name: String) : this(name, 0) { // 3. secondary constructor
        println("Age not provided")
    }
}
Person("Alice", 30) // Output: Person created: Alice
Person("Alice")     // Output: Person created: Alice, Age not provided
Kotlin programming language

Constructors #5

open class Point(val x: Int, val y: Int) {
    constructor(other: Point) : this(other.x, other.y) { ... }
    constructor(circle: Circle) : this(circle.centre) { ... }
}

Constructors can be chained, but they should always call the primary constructor in the end

A secondary constructor’s body will be executed after the object is created with the primary constructor. If it calls other constructors, then it will be executed after the other constructors’ bodies are executed

Inheritor class must call parent’s constructor:

class ColoredPoint(val color: Color, x: Int, y: Int) : Point(x, y) { ... }
Kotlin programming language

Init block

class Example(val value: Int, info: String) {
    val anotherValue: Int
    var info = "Description: $info"

    init {
        this.info += ", with value $value"
    }

    val thirdValue = compute() * 2

    private fun compute() = value * 10

    init {
        anotherValue = compute()
    }
}

There can be multiple init blocks

Init block can access only values declared above it

If value has the same name as constructor parameter, prefix it with this

Kotlin programming language

Abstraction

interface RegularCat {
    fun pet()
    fun feed(food: Food)
}

interface SickCat {
    fun checkStomach()
    fun giveMedicine(pill: Pill)
}

Interfaces cannot have a state

VS

abstract class RegularCat {
    abstract val name: String

    abstract fun pet()
    abstract fun feed(food: Food)
}

abstract class SickCat {
    abstract val location: String

    abstract fun checkStomach()
    fun giveMedicine(pill: Pill) {}
}

Abstract classes cannot have an instance, but can have a state

Kotlin programming language

Encapsulation

class Poop {}                       class Food {}

abstract class RegularCat {
    protected abstract val isHungry: Boolean
    private fun poop(): Poop { /* do the thing */ }
    abstract fun feed(food: Food)
}

class MyCat : RegularCat() {
    override val isHungry: Boolean = false
    override fun feed(food: Food) { 
        if (isHungry) { /* do the thing */ }
        else { poop() } // MyCat cannot poop
    }
}
Kotlin programming language

Inheritance

class SickDomesticCat : RegularCat(), CatAtHospital {
    override var isHungry: Boolean = false
        get() = field
        set(value) {...}

    override fun pet() {...}

    override fun feed(food: Food) {...}

    override fun checkStomach() {...}

    override fun giveMedicine(pill: Pill) {...}
}

To allow a class to be inherited by other classes, the class should be marked with the open keyword (Abstract classes are always open)

In Kotlin you can inherit only from one class, and from as many interfaces as you like

When you’re inheriting from a class, you have to call its constructor, just like how secondary constructors have to call the primary

Kotlin programming language

Polymorphism revisited

interface DomesticAnimal {
    fun pet()
}

class Dog: DomesticAnimal {
    override fun pet() {...}
}

class Cat: DomesticAnimal {
    override fun pet() {...}
}

fun main() {
    val homeZoo = listOf<DomesticAnimal>(Dog(), Cat())
    homeZoo.forEach { it.pet() }
}
Kotlin programming language

Properties

import kotlin.math.max

class PositiveAttitude(startingAttitude: Int) {
    var attitude = max(0, startingAttitude)
        set(value) =
            if (value >= 0) {
                field = value
            } else {
                println("Only positive attitude!")
                field = 0
            }

    var hiddenAttitude: Int = startingAttitude
        private set
        get() {
            if (field < 0) {
                println("Don't ask this!")
                field += 10
            }
            return field
        }

    val isSecretlyNegative: Boolean
        get() = hiddenAttitude < 0
}

Properties can optionally have an initializer, getter, and setter

Use the field keyword to access the values inside the getter or setter, otherwise you might encounter infinite recursion

Properties may have no backing field at all

Kotlin programming language

Properties #2

open class OpenBase(open val value: Int)

interface AnotherExample {
    /* abstract */ val anotherValue: OpenBase
}

open class OpenChild(value: Int) : OpenBase(value), AnotherExample {
    override var value: Int = 1000
        get() = field - 7
    override val anotherValue: OpenBase = OpenBase(value)
}

open class AnotherChild(value: Int) : OpenChild(value) {
    final override var value: Int = value
        get() = super.value // default get() is used otherwise
        set(value) { field = value * 2 }
    final override val anotherValue: OpenChild = OpenChild(value)  // Notice that we use OpenChild here, not OpenBase
}

Properties may be open or abstract, which means that their getters and setters might or must be overridden by inheritors, respectively. Interfaces can have properties, but they are always abstract. You can prohibit further overriding by marking a property final

Kotlin programming language

Operator Overloading

class SomeType()

class Example {
    operator fun plus(other: Example): Example = Example()
    operator fun dec() = this // return type has to be a subtype
    operator fun get(i: Int, j: Int): SomeType = SomeType()
    operator fun get(x: Double?, y: String) = this
    operator fun <T> invoke(l: List<T>): SomeType = SomeType()
}

operator fun Example.rangeTo(other: Example): Iterable<Example> = listOf(this, other)

fun main() {
    var ex1 = Example()
    val ex2 = ex1 + --ex1 // -- reassigned ex1, so it has to be var
    for (ex in ex1..ex2) {
        ex[23, 42]
        ex[null, "Wow"](listOf(1,2,3))
    }
}
Kotlin programming language

Extensions

Kotlin provides the ability to extend a class or an interface with new functionality
without need to inherit from the class or use forbidden magic (reflection)

fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this' is the given MutableList<T>
    this[index1] = this[index2]
    this[index2] = tmp
}

If the extended class already has the new method with the same name and signature, the original one will be used

Kotlin programming language

Extensions under the hood

Extended class does not change at all. Extensions is a new function that can be called like a method. It cannot access private members, for example.

Extensions have static dispatch, rather than virtual dispatch by receiver type. An extension function being called is determined by the type of the expression on which the function is invoked, not by the type of the result from evaluating that expression at runtime.

open class Shape
class Rectangle: Shape()

fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"

fun printClassName(s: Shape) {
    println(s.getName())
}

printClassName(Rectangle())                      //      "Shape", not Rectangle       
Kotlin programming language

Infix functions

data class Person(val name: String, val surname: String)

infix fun String.with(other: String) = Person(this, other)

fun main() {
    val realHero = "Ryan" with "Gosling"
    val (real, bean) = realHero
}
Kotlin programming language

ComponentN operator

class SomeData(val list: List<Int>) {
    operator fun component1() = list.first()
    operator fun component2() = SomeData(list.subList(1, list.size))
    operator fun component3() = "This is weird"
}

fun main() {
    val sd = SomeData(listOf(1, 2, 3))
    val (head, tail, msg) = sd
    val (h, t) = sd
    val (onlyComponent1) = sd
}

Class can overload any number of componentN methods for use in destructive declarations
Data classes have these methods by default

Kotlin programming language

Data classes

class Person(val name: String, val ssn: String)

val p1 = Person("John", "123-45-6789")
val p2 = Person("John", "123-45-6789")

println(p1 == p2) // false, because they are different objects 
data class Person(val name: String, val ssn: String)

val p1 = Person("John", "123-45-6789")
val p2 = Person("John", "123-45-6789")

println(p1 == p2) // true, because they are structurally equal
Kotlin programming language

Data classes #2

data class User(val name: String, val age: Int)

The compiler automatically derives:

  • equals() and hashCode()
  • toString() of the form User(name=John, age=42)
  • componentN() functions corresponding to the properties in their order of declaration
  • copy() to copy an object, allowing you to alter some of its properties while keeping the rest unchanged

The standard library provides the Pair and Triple classes, but named data classes are a much better design choice

Kotlin programming language

Data classes #3

data class User(val name: String, val age: Int)

fun main() {
    val user = User("John", 23)

    val (name, age) = user // destructing declaration calls componentN()

    val (onlyName) = user

    val olderUser = user.copy(age = 42)
}
Kotlin programming language

Enum classes

enum class Direction {
    NORTH, SOUTH, WEST, EAST
}

Each enum constant is an object
Each enum is an instance of the enum class, thus it can be initialized as:

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}

Enum classes can have methods or even implement interfaces

Kotlin programming language

Enum classes #2

val g = Color.valueOf("green".uppercase())

when(g) {
    Color.RED -> println("blood")
    Color.GREEN -> println("grass")
    Color.BLUE -> println("sky")
}
Kotlin programming language

Sealed classes

sealed class Shape
data class Circle(val radius: Double) : Shape()
data class Rectangle(val w: Double, val h: Double) : Shape()

fun main() {
    val shapes: List<Shape> = listOf(Circle(5.0), Rectangle(3.0, 4.0))

    for (shape in shapes) {
        val description = when (shape) {
            is Circle -> "Circle with radius ${shape.radius}"                            // Smart casting
            is Rectangle -> "Rectangle ${shape.w} x ${shape.h}"
        }
        println(description)                                            
    }
}

Sealed = closed set - all inheritors known at compilation + defined in the same file/package
Exhaustiveness checking - the compiler forces you to handle every variant
Discriminated Union - a type that is exactly one of a fixed set of named variants, and you always know which one

Kotlin programming language

Functional Interfaces (SAM)

Single Abstract Method (SAM) interface - has one abstract method
Kotlin allows us to use a lambda instead of a class definition to implement a SAM

fun interface IntPredicate {
   fun accept(i: Int): Boolean
}
val isEven = object : IntPredicate {
    override fun accept(i: Int): Boolean {
        return i % 2 == 0
    }
}

VS

val isEven = IntPredicate { it % 2 == 0 }
fun main() {
    println("Is 7 even? - ${isEven.accept(7)}")
}
Kotlin programming language

Kotlin singleton

object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        // ...
    }

    val allDataProviders: Collection<DataProvider>
        get() = // ...
}

DataProviderManager.registerDataProvider(...)
Kotlin programming language

Companion objects

  • Singleton tied to the class

  • Accessed via MyClass.Companion or MyClass

  • Can implement interfaces - unlike static members

  • Holds state and behavior

  • Enables the class to be its own factory

also runs the incrementing side effect, then returns the newly created MyClass instance

interface Factory {
    fun create(): MyClass
}

class MyClass {
    companion object : Factory {
        var counter: Int = 0
        override fun create(): MyClass =
            MyClass().also {
                it.announce()
                counter += 1
            }
    }

    fun announce(): Unit = println("created!")
}

val f: Factory = MyClass//.Companion
val instance1 = f.create()
val instance2 = f.create()

fun main() {
    println(MyClass/*.Companion*/.counter)
}
Kotlin programming language

Kotlin Type Hierarchy

Any Int String List<T> Parent MutableList<T> Child Nothing Any? Int? String? List<T>? Parent? MutableList<T>? Child? Nothing?
Kotlin programming language

Any & Nothing

Any is unified sypertype

fun printAnything(value: Any) {
    println(value)
}

fun main() {
    printAnything(42)                                 // accepts any type of argument
}

Nothing is an uninhabited subtype of every type - the evaluation never completes normally

fun fail(message: String): Nothing {
    throw IllegalStateException(message)
}

val someNullable: String? = null
val name: String = someNullable ?: fail("name was null")                        // compiles
Kotlin programming language

Thanx!

Polygon (pentagon)

Quadrangle (trapezoid)

Triangle

Arrows: level 1 → Polygon

Rectangle

Square

Rhombus (diamond)

Parallelogram

IsoscelesTriangle

RightTriangle

Arrows: level 2 → Quadrangle

Arrows: level 2 → Triangle

LEFT: paths behind boxes

LEFT: boxes

RIGHT: paths behind boxes

RIGHT: boxes (gray fill)