Published on [Permalink]
Reading time: 38 minutes
Posted in:

Kotlin Object Oriented Programming

Interfaces

Note this blog post contains my notes from the Object Oriented section of the book Atomic Kotlin. Consider buying a copy of the book for a more detailed reference.

An interface describes the concept of a type. It is the prototype for all classes that implement the interface.

Interfaces describe what a class should do, but not how they should do it. (think protocol in swift)

an API is a set of clearly defined communication paths between various software components.

In OOP the API of an object is the set of public members it uses to interact with other objects.

Example:

package interfaces
import atomictest.*


interface Computer {
  fun prompt(): String
  fun calculateAnswer(): Int
}


class Desktop: Computer {
  override fun prompt() = "Hello"
  override fun calculateAnswer() = 11
}


class DeepThought: Computer {
  override fun prompt() = "Thinking..."
  override fun calculateAnswer() = 42
}


class Quantum: Computer {
  override fun prompt() = "Probably..."
  override fun calculateAnswer() = -1
}


fun main() {
  val computers = listOf(
    Desktop(), DeepThought(), Quantum()
  )
  computers.map(Computer::calculateAnswer) eq
    "[11, 42, -1]"
  computers.map(Computer::prompt) eq
    "[Hello, Thinking..., Probably...]"
}

You must use override when implementing a member of an interface. This includes properties.

Single Abstract Method (SAM) interface example:

package interfaces


fun interface ZeroArg {
  fun f(): Int
}


fun interface OneArg {
  fun g(n: Int): Int
}


fun interface TwoArg {
  fun h(i: Int, j: Int): Int
}

Example implementing SAM interface with and without a SAM conversion

package interfaces
import atomictest.eq


class VerboseZero: ZeroArg {
  override fun f() = 11
}


val verboseZero = VerboseZero()
val samZero = ZeroArg { 11 }


class VerboseOne: OneArg {
  override fun g(n: Int) = n + 47
}


val verboseOne = VerboseOne()
val samOne = OneArg { it + 47 }


class VerboseTwo: TwoArg {
  override fun h(i: Int, j: Int): Int) = i + j
}


val verboseTwo = VerboseTwo()
val samTwo = TwoArg { i: Int, j: Int -> i + j }


fun main() {
  verboseZero.f() eq 11
  samZero.f() eq 11
  verboseOne.g(92) eq 139
  samOne.g(92) eq 139
  verboseTwo.h(11, 47) eq 58
  samTwo.h(11, 47) eq 58
}

Complex Constructors

Code inside the init section is executed during object creation:

package complexconstructors
import atomictest.eq


private val counter = 0


class Message(text: String) {
  private val content: String
  init {
    counter += 10
    content = "[$counter] $text"
  }
  
  override fun toString() = content
}


fun main() {
  val m1 = Message("Hello!")
  m1 eq "[10] Hello!"
  val m2 = Message("Humza")
  m2 eq "[20] Humza"
}

Secondary Constructors

When you require several ways to construct an object, named and default args are usually the easiest approach. Sometimes, however, you must create multiple overloaded constructors

Overloaded constructors are called secondary constructors

The constructor parameter list with primary inits are the primary constructors

Example creating a secondary constructor

package secondaryconstructors
import atomictest.*


class WithSecondary(i: Int) {
  init {
    trace("Primary: $i")
  }
  constructor(c: Char): this(c - 'A') {
    trace("Secondary: '$c'")
  }
  constructor(s: String):
    this(s.first()) {
    trace("Secondary: \"$s\"")
  }
}


fun main() {
  fun sep() = trace("-".repeat(10))
  WithSecondary(1)
  sep()
  WithSecondary('D')
  sep()
  WithSecondary("Last Constructor")
  trace eq """
    Primary: 1
    ----------
    Primary: 3
    Secondary: 'D'
    ----------
    Primary: 11
    Secondary: "Last Constructor"
  """
}

A secondary constructor must call another secondary constructor or the primary constructor

An init is not required when using a secondary constructor. That is the primary constructor doesn’t need an init.

Only properties of the primary constructor can be declared as val or var

Inheritance

Inheritance is the mechanism for creating a new class by reusing and modifying an existing class.

package inheritance


open class Base


class Derived: Base()

A base class must be open

Classes are closed or “final” by default:

package inheritance


open class Parent


class Child: Parent()


final class Single


//Same as above
class AnotherSingle

Another basic inharitance example:

package inheritance.ape1
import atomictest.eq


open class GreatApe {
  val weight = 100.0
  val age = 12
}


open class Bonobo: GreatApe()
class Chimpanzee: GreatApe()
class BonoboB: Bonobo()


fun GreatApe.info() = "wt: $weight age: $age"


fun main() {
  GreatApe().info() eq "wt: 100.0 age: 12"
  Bonobo().info() eq "wt: 100.0 age: 12"
  Chimpanzee().info() eq "wt: 100.0 age: 12"
  BonoboB().info eq "wt: 100.0 age: 12"
}

This time we modify member functions in sublcasses

package inheritance.ape2
import atomictest.eq


open class GreatApe {
  protected var energy = 0
  open fun call() = "Hoo!"
  open fun eat() {
    energy += 10
  }
  fun climb(x: Int) {
    energy -= x
  }
  fun energyLevel() = "Energy: $energy"
}


class Bonobo: GreatApe() {
  override fun call() = "Eep!"
  override fun eat() {
    //Modify base class var
    energy += 10
    super.eat()
  }
  //Add a function
  fun run() = "Bonobo run"
}


class Chimpanzee: GreatApe() {
  //New property
  val additionalEnergy = 20
  override fun call() = "Yawp!"
  override fun eat() {
    energy += additionalEnergy
    super.eat()
  }
  //Add a function
  fun jump() = "Chimp jump"
}


fun talk(ape: GreatApe): String {
  //ape.run() //Not an ape function
  //ape.jump() //Not an ape function
  ape.eat()
  ape.climb(10)
  return "${ape.call()} ${ape.energyLevel()}"
}


fun main() {
  //Cannot access energy
  //GreatApe().energy
  talk(GreatApe()) eq "Hoo! Energy: 0"
  talk(Bonobo()) eq "Exp! Energy: 10"
  talk(Chimpanzee()) eq "Yawp! Energy: 20"
}

Derived class can’t access private members of the base class

protected access level means a member is closed to the outside world, but can be accessed or overridden in subclasses

Functions must also be open like in the above example to be overridden

To use the base class version of a function like eat() in the above example you must use the super keyword.

Polymorphism is when the correct version of a function is called of a subclass when treating it as a member of its base class.

Base Class Initalization

When a class inherits from another class, Kotlin gurantees that both classes are properly initialized.

Kotlin creates valid objects by ensuring that constructors are called:

If a base class does have constructor parameters, a derived class must provide these arguments during construction.

Example of GreatApe rewritten with constructor parameters:

package baseclassinit
import atomictest.eq


open class GreatApe(
  val weight: Double
  val age: Int
)


open class Bonobo(weight: Double, age: Int):
  GreatApe(weight, age)


class Chimpanzee(weight: Double, age: Int):
  GreatApe(weight, age)


class BonoboB(weight: Double, age: Int):
  Bonobo(weight, age)
  
fun GreatApe.info() = "wt: $weight age: $age"


fun main() {
  GreatApe(100.0, 12).info() eq "wt: 100.0 age: 12"
  Bonobo(110.0, 13).info() eq "wt 110.0 age: 13"
  Chimpanzee(120.0, 14).info() eq "wt: 120.0 age: 14"
  BonoboB(130.0, 15).info() eq "wt: 130.0 age: 15"
}

After Kotlin creates memory for your object, it calls the base-class constructor first, then the constructor for the next-derived class until it reaches the furthest down Derived class.

When inheriting from a class you must provide arguments to the base-class constructor after the base class name.

package baseclassinit


open class Superclass1(val i: Int)
class SublClass1(i: Int): SuperClass1(i)


open class SuperClass2
class SubClass2: SuperClass2()

() are required after the Base class name even if their are no constructor parameters.

You can call a secondary constructor in the Base class if desired:

package baseclassinit
import atomictest.eq


open class House(
  val address: String,
  val state: String,
  val zip: String
) {
  constructor(fullAddress: String):
    this(fullAddress.substringBefore(", "),
      fullAddress.subStringAfter(", ")
        .substringBefore(" "),
      fullAddress.substringAfterLast(" ")
  val fullAddress: String
    get() = "$address, $state $zip"
}


class VacationHouse(
  address: String,
  state: String,
  zip: String
  val startmonth: String,
  val endMonth: String
): House(address, state, zip) {
  override fun toString() =
    "vacation house at $fullAddress " +
    "from $startMonth to $endMonth"
}


class TreeHouse(
  val name: String
): House("Tree Street, TR 00000") {
  override fun toString() =
    "$name tree house at $fullAddress"
}


fun main() {
  val vacationHouse = VacationHouse(
    address = "8 Target St.",
    state = "KS",
    zip = "66632",
    startMonth = "May",
    endMonth = "September"
  )
  
  vacationHouse eq
    "Vacation hosue at 8 Target St., " +
    "KS 66632 from May to September"
  TreeHouse("Oak") eq
    "Oak tree house at Tree Street, TR 00000"
}

You call an overloaded base-class constructor by passing the matching constructor arguments in the base0class constructor call. You see this in the definitions of VacationHouse and TreeHouse. Each calls a different base-class constructor.

Inside a secondary constructor of a derived class you can either call the base-class constructor or a different derived-class constructor:

package baseclassinit
import atomictest.eq


open class Base(val i: Int)


class Dervied: Base {
  constructor(i: Int): super(i)
  constructor(): this(9)
}


fun main() {
  val d1 = Derived(11)
  d1.i eq 11
  val d2 = Derived()
  d2.i eq 9
}

In the above example, to call the base-class constructor use the super keyword. Use this to call another constructor of the same class.

Abstract Classes

An abstract class is like an ordinary class except one or more functions or properties is incomplete.

A function lacks a definition or a property is declared without initialization.

An interface is like an abstract class but without state

You must use the abstract modifier to mark class members that have missing definitions.

You must also mark the entire class as abstract:

package abstract classes


abstract class WithProperty {
  abstract val x: Int
}


abstract class WithFunctions {
  abstract fun f(): Int
  abstract fun g(n: Double)
}

All functions and properties declared in an interface are abstract by default which makes an interface similar to an abstract class.

These two are equivalent:

package abstractclasses


interface Redundant {
  abstract val x: Int
  abstract fun f(): Int
  abstract fun g(n: Double)
}


interface Removed {
  val x: Int
  fun f(): Int
  fun g(n: Double)
}

Example of a class storing state:

package abstractstate
import atomictest.eq


class IntList(val name: String) {
  val list = mutableListOf<Int>()
}


fun main() {


val ints = intList("numbers")
ints.name eq "numbers
ints.list += 7
ints.list = listOf(7)

Both interfaces and abstract classes can contain functions with implementations. You can call other abstract members from such functions:

package abstractclasses
import atomictest.eq


interface Parent {
  val ch: Char
  fun f(): Int
  fun g() = "ch = $ch; f() = ${f()}"
}


class Actual(
  override val ch: Char
): Parent {
  override fun f() = 17
}


class Other: Parent {
  override val ch
    get() = 'B'
  override fun f() = 34
}


fun main() {
  Actual('A').g() eq "ch = A; f() = 17"
  Other().g() eq "ch = B; f() = 34"

In the above:

An interface can also contain property accessors if the corresponding property doesn’t change state:

package abstratclasses
import atomictest.eq


interface PropertyAccessor {
  val a: Int
    get() = 11
}


class Imp1: PropertyAccessor


fun main() {
  Imp1().a eq 11
}

In Kotlin, a class can only inherit from a single Base Class

Kotlin does however allow multiple Interface Inheritance

package multipleinheritance


interface Animal
interface Mammal: Animal
interface AquaticAnimal: Animal


class Dolphin: Mammal, AquaticAnimal

Interfaces like classes can inherit from one another

When inheriting from several interfaces, it’s possible to simultaneously override two or more functions with the same signature. If signatures collide you must resolve collisions manually like in class C below:

package collision
import atomoictest.eq


interface A {
  fun f() = 1
  fun g() = "A.g"
  val n: Double
    get() = 1.1
}


interface B {
  fun f() = 2
  fun g() = "B.g"
  val n: Double
    get() = 2.2
}


class C: A, B {
  override fun f() = 0
  override fun g() = super<A>.g()
  override val n: Double
    get() = super<A>.n + super<B>.n
}


fun main() {
  val c = C()
  c.f() eq 0
  c.g() = "A.g"
  c.n eq 3.3
}

Collisions where the identifier is the same but the type is different are not allowed in Kotlin and cannot be resolved.

Upcasting

Taking an object reference and treating it as a reference to its base type is called upcasting.

Kotlin has stand-alone functions so not everything needs to be an object. Extension functions allow us to add functionality without inheritance.

Example of upcasting:

package upcasting
import atomictest.*


interface Shape {
  fun draw(): String
  fun erase(): String
}


class Circle: Shape {
  override fun draw() = "Circle.draw"
  override fun erase() = "Circle.erase"
}


class Square: Shape {
  override fun draw() = "Square.draw"
  override fun erase() = "Square.erase"
  fun color() = "Square.color"
}


class Triangle: Shape {
  override fun draw() = "Triangle.draw"
  override fun erase() = "Triangle.erase"
  fun rotate() = "triangle.rotate"
}


fun show(shape: Shape) {
  trace("Show: ${shape.draw()")
}


fun main() {
  listOf(Circle(), Square(), Triangle())
    .forEach(::show)
  trace eq """
    Show: Circle.draw
    Show: Square.draw
    Show: Triangle.draw
  """
}

Liskov Substitution Principle - says that after upscaling, the derived type can be treated exactly like the base type. Meaning extra members of derived classes are unavailable within show in the example above.

Polymorphism

Polymorphism means having many forms

Example:

package polymorphism
import atomictest.eq


open class Pet {
  open func speak() = "Pet"
}


class Dob: Pet() {
  override fun speak() = "Bark!"
}


class Cat: Pet() {
  override fun speak() = "Meow"
}


fun talk(pet: Pet) = pet.speak() // [1]


fun main() {
  talk(Dog()) eq "Bark!"
  talk(Cat()) eq "Meow"
}

In [1] Dog and Cat are upcast to type Pet

Polymorphism occurs when a a parent class reference contains a child class instance. When you call a member on that parent class reference, polymorphism produces the correct override from the child class.

Connecting a function call to a function body is called binding.

Polymorphism usies dynamic binding to create the appropriate behavior. This is AKA late binding or dynamic dispatch.

Another example:

package polymorphism
import atomictest.eq


abstract class Character(val name: String) {
  abstract fun play(): String
}


interface Fighter {
  fun fight() = "Fight"
}


interface Magician {
  fun doMagic() = "Magic!"
}


class Warrior: Character("Warrior"),
  Fighter {
  override fun play() = fight() 
}


open class Elf(name: "Elf"):
  Character(name), Magician {
  override fun play() = doMagic() 
}


class FightingElf:
  Elf("FightingElf"), Fighter {
  override fun play() = super.play() + fight() 
}


fun Character.playTurn() =
  trace(name + ": " + play())
  
fun main() {
  val characters: List<Character> = listOf(
    Warrior(), Elf(), FightingElf()
  )
  characters.forEach { it.playTurn }
  trace eq """
    Warrior: Fight
    Elf: Magic!
    FightingElf: Magic!Fight!
  """
}

Composition

Composition is when your new class is composed of objects of existing classes. You’re reusing the functionality of the code, not its form.

Example:

“A house is a building and has a kitchen”

package composition1


interface Building
interface Kitchen
interface House: Building {
  val kitchen: Kitchen
}

To allow any number of kitchens use a collection:

package composition2


interface Building
interface Kitchen


interface House: Building {
  val kitchens: List<Kitchen>
}

Composition provides the functionality of an existing class, but not it’s interface. You can embed an object to use its features in your new class, but the user sees the interface you’ve defined for the new class rather than the interface of the embedded object.

To hide the object completely make it private:

package composition


class Features {
  fun f1() = "feature1"
  fun f2() = "feature2"
}


class Form {
  priate val features = Features()
  fun operation() = features.f2() + features.f1()
  fun operation2() = features.f1() + features.f2()
}

Inheritance and Extensions

Prefer extensions over inheritance if possible

An extension function can be thought of as creating an interface containing a single function:

package inheritanceextensions


class X


fun X.f() {}


class Y


fun Y.f() {}


fun callF(x: X) = x.f()
fun callF(y: Y) = y.f()


fun main() {
  val x = X()
  val y = Y()
  x.f()
  y.f()
  callF(x)
  callF(y)
}

A library often defines a type and provides functions that accept parameters of that type and or return that type:

package usefullibrary


interface LibType {
  fun f1()
  fun f2()
}


fun utility1(lt: LibType) {
  lt.f1()
  lt.f2()
}


fun utility2(lt: LibType) {
  lt.f2()
  lt.f1()
}

To use this library you must convert your existing class to LibType. Below we inherit from an existing class to produce MyClassAdaptedForLib which implements LibType and thus can be passed to the functions in usefullibrary

package inheritanceextensions
import usefullibrary.*
import atomictest.*


open class MyClass {
  fun g() = trace("g()")
  fun h() = trace("h()")
}


fun useMyClass(mc: MyClass) {
  mc.g()
  mc.h()
}


class MyClassAdaptedForLib:
  MyClass(), LibType {
  override fun f1() = h()
  override fun f2() = g() 
}


fun main() {
  val mc = MyClassAdaptedForLib()
  utility1(mc)
  utility2(mc)
  useMyClass(mc)
  trace eq "h() g() g() h() g() h()"
}

We could alternatively use composition:

package inheritanceextensions2
import usefullibrary.*
import atomictest.*


class MyClass { // Not open
  fun g() = trace("g()")
  fun h() = trace("h()")
}


fun useMyClass(mc: MyClass) {
  mc.g()
  mc.h()
}


class MyClassAdaptedForLib: LibType {
  val field = MyClass()
  override fun f1() = field.h()
  override fun f2() = field.g()
}


fun main() {
  val mc = MyClassAdaptedForLib()
  utillity(mc)
  utility2(mc)
  useMyClass(mc.field) // [1]
  trace eq "h() g() g() h() g() h()"
}

Above you must explicitly access the MyClass object seen in [1].

If a function must access a private member you have no choice but to make it a member function rather than an extension.

Extension functions cannot be overridden. So polymorphism would not work with extension functions.

Class Delegation

Composition and inheritance place subobjects inside your new class. With composition the subobject is explicit and with inheritance it is implicit.

Composition uses the functionality of an embedded object but does not expose its interface.

For a class to reuse an existing implementation and interface you must use inheritance or delegation.

Example:

package classdelegation


interface Controls {
  fun up(velocity: Int): String
  fun down(velocity: Int): String
  fun left(velocity: Int): String
  fun right(velocity: Int): String
  fun forward(velocity: Int): String
  fun back(velocity: Int): String
  fun turboBoost(): String
}


class SpaceShipControls: Controls {
  override fun up(velocity: Int) =
    "up $velocity"
  override fun down(velocity: Int) =
    "down $velocity"
  override fun left(velocity: Int) =
    "left $velocity"
  override fun right(velocity: Int) =
    "right $velocity"
  override fun forward(velocity: Int) =
    "forward $velocity"
  override fun back(velocity: Int) =
    "back $velocity"
  override fun turboBoost() = "turbo boost"
}

To expose member functions in Controls you can create an instance of SpaceShipControls as a property and explicitly delegate all the exposed member functions to that instance:

package classdelegation
import atomictest.eq


class ExplicitControls: Controls {
  private val controls = SpaceShipControls()
  //Delegation by hand
  override fun up(velocity: Int) =
    contorls.up(velocity)
  override fun down(velocity: Int) =
    controls.down(velocity)
  // ...
  //Modified implementation
  override fun turboBoost(): String =
    controls.turboBoost() + "boooooost!"
}


fun main() {
  val controls = ExplicitControls()
  controls.forward(100) eq "forward 100"
  controls.turboBoost() eq 
    "turbo boost... boooooost!"  
}

Kotlin automates the process of delegation so instead of writing explicit function implementations you can specify an object to be a delegate"

package classdelegation


interface AI
class A: AI


class B(val a: A): AI by a

You can only delegate to an interface. The delegate object must be a constructor argument.

Now we can rewrite the previous example:

package classdelegation
import atomictest.eq


class DelegatedControls(
  private val controls: SpaceShipControls =
    SpaceShipControls()
): Controls by controls {
  override fun turboBoost(): Stirng =
    "${controls.turboBoost} + boooooost!"
}


fun main() {
  val controls = DelegatedControls()
  controls.forward(100) eq "forward 100"
  controls.turboBoost eq
    "turbo boost... boooooost!"
}

You can simulate multiple inheritance using class delegation.

package classdelegation
import atomictest.eq


interface Rectangle {
  fun paint(): String
}


class ButtonImage(
  val width: Int
  val height: Int
}: Rectangle {
  override fun paint() = 
    "painting ButtonImage($width, $height)"
}


interface MouseManager {
  fun clicked(): Boolean
  fun hovering(): Boolean
}


class UserInput: MouseManager {
  override fun clicked() = true
  override fun hovering() = true
}


class Button(
  val width: Int
  val height: Int
  var image: Rectangle = ButtonImage(width, height)
  private var input: MouseManager =
    UserInput()
): Rectangle by image, MouseManager by input


fun main() {
  val button = Button(10, 5)
  button.paint() eq
    "painting ButtonImage(10, 5)"
  button.clicked() eq true
  button.hovering() eq true
  //Can upcast to both delegated types
  val rectangle: Rectangle = button
  val mouseManager: MouseManager = button
}

In the above example image in the constructor arg list is both public and var. This allows the client programmer to replace the ButtonImage dynamically

Downcasting

Downcasting discovers the specific type of a previously-upcast object

Downcasting is aka run-time type identification (RTTI)

Example without downcasting:

package downcasting


interface Base {
  fun f()
}


class Derived1: Base {
  override fun f() {}
  fun g() {}
}


class Derived2: Base {
  override fun f() {}
  fun h() {}
}


fun main() {
  val b1: Base = Derived1) // Upcast
  b1.f1() // Part of Base
  val b2: Base = Derived2() // Upcast
  b2.f() // Part of base
  // b2.h() NOT part of Base
}

Use the is keyword to check whether an object is a particular type:

import downcasting.*


fun main() {
  val b1: Base = Derived1() // Upast
  if (b1 is Derived1) {
    b1.g() // Within scope of is check
  }
  
  val b2: Base = Derived2()
  if (b2 is Derived2)
    b2.h() //Within scope of is check
}

Example using when expression with is:

package downcasting
import atomictest.eq


interface Creature


class Human: Creature {
  fun greeting() = "I'm Human"
}


class Dog: Creature {
  fun bark() = "Yip!"
}


class Alien: Creature {
  fun mobility() = "Three legs"
}


fun what(c: Creature): String =
  when(c) {
    is Human -> c.greeting()
    is Dog -> c.bark()
    is Alien -> c.mobility()
    else -> "Something else"
  }
  
fun main() {
  val c: Creature = Human()
  what(c) eq "I'm Human"
  what(Dog()) eq "Yip!"
  what(Alien()) eq "Three legs"
  class Who: Creature
  what(Who()) eq "Something else"
}

Automatic downcasts are subject to a special constraint.

If the base-class reference to the object is modifiable ( a var) then there’s a possibility that this reference could be assigned to a different object between the instant that the type is detected and the instant when you call a specific function on the downcast object.

You must explicitly shadow the var with a val like so if using it:

package downcasting


class SmartCast1(val c: Creature) {
  fun contact() {
    when (c) {
      is Human -> c.greeting()
      is Dog -> c.bark()
      is Alien -> c.mobility()
  }
}


class SmartCast2(var c: Creature) {
  fun contact() {
    when(val c = c) {
      is Human -> c.greeting()
      is Dog -> c.bark()
      is Alien -> c.mobility()
    }
  }
}

The potential change typically happens through concurrency.

Properties that are open for inheritance can’t be smart-cast because the value might be overridden in subclasses.

The as keyword forcefully casts a general type to a specific type:

package downcasting
import atomictest.*


fun dogBarkUnsafe(c: Creature) =
  (c as Dog).bark()
  
fun dogBarkUnsafe2(c: Creature): String {
  c as Dog // [1]
  c.bark()
  return c.bark(). + c.bark()
}


fun main() {
  dogBarkUnsafe(Dog()) as "Yip!"
  dogBarkUnsafe2(Dog()) as "Yip!Yip!"
  (capture {
    dogBarkUnsafe(Human())
  }) contains listOf("ClassCastException")
}

In [1] c is treated as Dog for the rest of the scope.

A failed cast throws a ClassCastException

You can try a safe cast as? that returns null:

package downcasting
import atomictest.eq


fun dogBarkSafe(c: Creature) =
  (c as? Dog)?.bark() ?: "Not a Dog"
  
fun main() {
  dogBarkSafe(Dog()) eq "Yip!"
  dogBarkSafe(Human()) eq "Not a Dog"
}

Example discovering types in Lists:

package downcasting
import atomictest.eq


val group: List<Creature> = listOf(
  Human(), Human(), Dog(),
  Alien(), Dog()
}


fun main() {
  val dog = group
    .find { it is Dog } as Dog? // [1]
  dog?.bark() eq "Yip!" // [2]
}

[1] find returns type Creature? however we explicitly cast to type Dog? with as

[2] We use a safe call to use bark()

You can use filterIsInstance() which produces all elements of a specific type:

import downcasting.*
import atomictest.eq


fun main() {
  val humans1: List<Creature> =
    group.filter { it is Human }
  humans1.size eq 2
  val humans2: List<Human> =
    group.filterIsInstance<Human>()
  humans2 eq humans1
}

Sealed Classes

To constrain a class hierarchy declare the superclass sealed

Example without sealing:

package withoutsealedclasses
import atomictest.eq


open class Transport


data class Train(
  val line: String
): Transport()


data class Bus(
  val number: String,
  val capacity: Int
): Transport()


fun travel(transport: Transport) =
  when(transport) {
    is train ->
      "Train ${transport.line}"
    is Bus ->
      "Bus ${transport.number}: " +
      "size ${transport.capacity}"
    else ->
      "$transport is in limbo!"
  }


fun main() {
  listOf(Train("S1"), Bus("11", 90))
    .map(::travel) eq
    "[Train S1, Bus 11: size 90]"
}

This has the problem that if you add more classes that inherit Transport you may forget to change the when expression in travel() to accommodate the new class.

Example using sealed class:

package sealedclasses
import atomictest.eq


sealed class Transport


data class Train(
  val line: String
): Transport()


data class Bus(
  val number: String,
  val capacity: Int
): Transport()


fun travel(transport: Transport) =
  when (transport) {
    is Train ->
      "Train ${transport.line}"
    is Bus ->
      "Bus ${transport.number}: " +
      "size ${transport.capacity}"
  }
  
fun main() {
  listOf(Train("S1"), Bus("11", 90))
    .map(::travel) eq
    "[Train S1, Bus 11: size 90]"
}

All direct subclasses of a sealed class must be in the same file as the base class

A sealed class is basically an abstract class with the extra constraint that all direct subclasses must be within the same file.

Indirect subclasses of a sealed class can be defined in a separate file.

sealed interfaces don’t exist in Kotlin because Java cannot be prevented from implementing the same interface.

When a class is sealed you can easily iterate through its subclasses:

package sealedclasses
import atomictest.eq


sealed class Top
class Middle1: Top()
class Middle2: Top()
open class Middle3: Top()
class Bottom3: Middle3()


fun main() {
  Top::class.sealedSubclasses
    .map { it.simpleName } eq
    "[Middle1, Middle2, Middle3]"
}

::class produces a class object that contains properties and member functions to discover info and create objects of that class.

Only the immediate subclasses are available through the sealedSubclasses property

sealedSubclasses uses reflection which requires that the dependency kotlin-reflection.jar be in the classpath.

Reflection is a way to dynamically discover and use characteristics of a class.

Type Checking

Example of type checking:

package typechecking
import atomictest.eq


interface Insect {
  fun walk() = "$name: walk"
  fun fly() = "$name: fly"
}


class HouseFly: Insect


class Flea: Insect {
  override fun fly =
    throw Exception("Flea cannot fly")
  fun crawl() = "Flea: crawl"
}


fun Insect.basic() =
  walk() + " " +
  if (this is Flea)
    crawl()
  else
    fly()


interface SwimmingInsect: Insect {
  fun swim() = "$name: swim"
}


interface WaterWalker: Insect {
  fun walkWater() =
    "$name: walk on water"
}


class WaterBeetle: SwimmingINsect
class WaterStrider: WaterWalker
class WhirligigBeetle: SwimmingInsect, WaterWalker


fun Insect.water() =
  when(this) {
    is SwimmingInsect -> swim()
    is WaterWalker -> walkWater()
    else -> "$name drown"
  }


fun main() {
  val insects = listOf(
    HouseFly(), Flea(), WaterStrider(),
    WaterBeetle(), WhirligigBeetle()
  )
  insects.map { it.basic() } eq
    "[HouseFly: walk HouseFly: fly, " +
    "Flea: walk Flea: crawl, " +
    "WaterStrider: walk WaterStrider: fly, " +
    "WaterBeetle: walk WaterBeetle: fly, " +
    "WhirligigBeetle: walk WhirligigBeetle: fly" +
    "]"
  insects.map { it.water() } eq
    "[HouseFly: drown, Flea: drown, " +
    "WaterStrider: walk on water, " +
    "WaterBeetle: swim, " +
    "WhirligigBettle: Swim" +
    "]"
}

Name is defined as an extension property on any:

package typechecking
val Any.name
  get() = this::class.simpleName

Shape example:

package typechecking
import atomictest.eq


interface Shape {
  fun draw(): String
}


class Circle: Shape {
  override fun draw() = "Circle: Draw"
}


class Square: Shape {
  override fun draw() = "Square: Draw"
  fun rotate() = "Square: Rotate"
}


fun turn(s: Shape) =
  when(s) {
    is Square -> s.rotate()
    else -> ""
  }
  
fun main() {
  val shapes = listOf(Circle(), Square())
  shapes.map { it.draw } eq
    "[Circle: Draw, Square: Draw]"
  shapes.map { turn(it) } eq
    "[, Square: Rotate]"
}

The above code works but you have to keep adding when conditions if you add more types.

Type-check coding means testing for every type in your system

Example type checking in an auxillary function:

package typechecking
import atomictest.eq


interface BeverageContainer {
  fun open(): String
  fun pour(): String
}


class Can: BeverageContainer {
  override fun open() = "Pop Top"
  override fun pour() = "Can: Pour"
}


open class Bottle: BeverageContainer {
  override fun open() = "Remove Cap"
  override fun pour() = "Bottle: Pour"
}


class GlassBottle: Bottle()
class PlasticBottle: Bottle()


fun BeverageContainer.recycle() =
  when(this) {
    is Can -> "Recycle Can"
    is GlassBottle -> "Recycle Glass"
    else -> "Landfill"
  }
  
fun main() {
  val refrigerator = listOf(
    Can(), GlassBottle(), PlasticBottle()
  )
  refrigerator.map { it.open() } eq
    "[Pop TOp, Remove Cap, Remove Cap]"
  refrigerator.map { it.recycle() } eq
  "[Recycle Can, Recycle Glass, Landfill]"
}

Slightly cleaner more straightforward code in an extension, but the problem remains with the when statement needing adjustments if more classes are added.

We can improve the situation using a sealed class:

package typechecking3
import atomictest.eq
import typechecking.name


sealed class Shape {
  fun draw() = "$name: Draw"
}


class Circle: Shape()


class Square: Shape() {
  fun rotate() = "Square: Rotate"
}


class Triangle: Shape() {
  fun rotate = "Triangle: Rotate"
}


fun turn(s: Shape) =
  when(s) {
    is Circle -> ""
    is Square -> s.rotate()
    is Triangle -> s.rotate()
  }
  
fun main() {
  val shapes = listOf(Circle(), Square())
  shapes.map { it.draw() } eq
    "[Circle: Draw, Square: Draw]"
  shapes.map { turn(it) } eq
    "[, Square: Rotat
    e]"
}

Now if we add a new Shape compiler will tell us to add a new tpe check path to turn due to the sealed class Shape.

Trying to apply the above approach to the BeverageContainer example:

package typechecking3
import atomictest.eq


sealed class BeverageContainer {
  abstract fun open(): String
  abstract fun pour(): String
}


sealed class Can: BeverageContainer() {
  override fun open() = "Pop Top"
  override fun pour() = "Can: Pour"
}


class SteelCan = Can()
class AluminumCan: Can()


sealed class Bottle: BeverageContainer() {
  override fun open() = "Remove Top"
  override fun pour() = "Bottle: Pour"
}


class GlassBottle: Bottle()
sealed class PlasticBottle: Bottle)
class PETBottle: PlasticBottle()
class HOPEBottle: PlasticBottle()


fun BeverageContainer.recycle() =
  when(this) {
    is Can -> "Recycle Can"
    is Bottle -> "Recycle Bottle"
  }
  
fun BeverageContainer.recycle2() =
  when(this) {
    is Can -> when(this) {
      is SteelCan -> "Recycle Steel"
      is AluminumCan -> "Recycle Aluminum"
    }
    is Bottle -> when(this) {
      is GlassBottle -> "Recycle Glass"
      is PlasticBottle -> when(this) {
        is PETBottle -> "Recycle PET"
        is HOPEBottle -> "Recycle HOPE"
      }
    }
  }


fun main() {
  val refrigerator = listOf(
    SteelCan(), AluminumCan(), GlassBottle(),
    PETBottle(), HOPEBottle()
  )
  refrigerator.map { it.open() } eq
    "[Pop Top, Pop Top, Remove Cap, " +
    "Remove Cap, Remove Cap]"
  refrigerator.map { it.recycle() } eq
    "[Recycle Can, Recycle Can, " +
    "Recycle Bottle, Recycle Bottle, " +
    "Recycle Bottle]"
  refrigerator.map { it.recycle2() } eq
    "[Recycle Steel, Recycle Aluminum, " +
    "Recycle Glass, " +
    "Recycle PET, Recycle HOPE]"
}

In the above example:

Another example bringing recycle into BeverageContainer which is again now an interface:

package typechecking3
import atomictest.eq
import typechecking.name


interface BeverageContainer {
  fun open(): String
  fun pour() = "$name: Pour"
  fun recycle(): String
}


abstract class Can: BeverageContainer {
  override fun open() = "Pop Top"
}


class SteelCan: Can() {
  override fun recycle() = "Recycle Steel"
}


class AluminumCan: Can() {
  override fun recycle() = "Recycle Aluminum"
}


abstract class Bottle: BeverageContainer {
  override fun open() = "Remove Cap"
}


class GlassBottle: Bottle() {
  override fun recycle() = "Recycle Glass"
}


abstract class PlasticBottle: Bottle()


class PETBottle: PlasticBottle() {
  override fun recycle() = "Recycle PET"
}


class HOPEBottle: PlasticBottle() {
  override fun recycle() = "Recycle HOPE"
}


fun main() {
  val refrigerator = listOf(
    SteelCan(), AluminumCan(),
    GlassBottle(),
    PETBottle(), HOPEBottle()
  )
  refrigerator.map { it.open() } eq
    "[Pop Top, Pop Top, Remove Cap, " +
    "Remove Cap, Remove Cap]"
  refrigerator.map { it.recycle() } eq
    "[Recycle Steel, Recycle Aluminum, " +
    "Recycle Glass, " +
    "Recycle PET, Recycle HOPE]"
}

In the above example:

Nested Classes

A nested class is a class within the namespace of the outer cllass

The outer class “owns"the nested class.

Example of Plane being nested within Airport

package nestedclasses
import atomictest.eq
import nestedclasses.Airport.Plane


class Airport(private val code: String) {
  open class Plane {
    // Can access private properties
    fun contact(airport: Airport) =
      "Contacting ${airport.code}"
  }
  private class PrivatePlane: Plane()
  fun privatePlane(): Plane = PrivatePlane()
}


fun main() {
  val denver = Airport("DEN")
  var plane = Plane()
  plane.contact(denver) eq "Contacting DEN"
  //Can't do this
  //val privatePlane = Airport.PrivatePlane()
  val frankfurt = Airport("FRA")
  plane = frankfurt.privatePlane()
  //Can't do this
  //val p = plane as PrivatePlane
  plane.contact(frankfurt) eq "Contacting FRA"
}

The nested element Plane can access the private property code

Creating a Plane object does not require an Airport object, but if you create it outside the Airport class body, but you need to qualify the constructor call. By importing nestedclasses.Airport.Plane we avoid this qualification.

A nested class can be private like PrivatePlane

If you define and return a PrivatePlane from a member function as seen in privatePlane() you must upcast to a public type (assuming it extends a public type) and cannot be downcast to a private type.

Another example where clean() goes through a List of parts and calls clean() for each one producing a kind of recursion:

package nestedclasses
import atomictest.*


abstract class Cleanable(val id: String) {
  open val parts: List<Cleanable> = listOf()
  fun clean(): String {
    val text = "$id clean"
    if (parts.isEmpty()) return text
    return "${parts.joinToString(" ", "(", ")",
      transform = Cleanable::clean)} $text \n"
  }
}


class House: Cleanable("House") {
  override val parts = listOf(
    Bedroom("Master Bedroom"),
    Bedroom("Guest Bedroom")
  )
  class Bedroom(id: String): Cleanable(id) {
    override val parts =
      listOf(Closet(), Bathroom())
    class Closet: Cleanable("Closet") {
      override val parts =
        listOf(Shelf(), Shelf())
      class Shelf: Cleanable("Shelf")
    }
    class Bathroom: Cleanable("Bathroom") {
      override val parts =
        listOf(Toilet(), Sink())
      class Toilet: Cleanable("Toilet")
      class Sink: Cleanable("Sink")
    }
  }
}


fun main() {
  House().clean eq """
    (((Shelf clean Shelf clean) Closet clean
      (Toilet cclean Sink clean) Bathroom clean
    ) Master Bedroom clean
      ((Shelf clean Shelf clean) Closet Clean
      (Toilet clean Sink clean) Bathroom clean
    ) Guest Bedroom clean
    ) House clean
  """
}

Classes that are nested inside functions are called local classes:

package nestedclasses


fun localClasses() {
  open class Amphibian
  class Frog: Amphibian()
  val amphibian: Amphibian = Frog()
}

Amphibian could have been an interface, but local interfaces are not allowed.

To return a local class you must upcast them to a class defined outside the function:

package nestedclasses


interface Amphibian


fun createAmphibian(): Amphibian {
  class Frog: Amphibian
  return Frog()
}


fun main() {
  val amphibian = createAmphibian()
  // amphibian as Frog [1]
}

[1] is not allowed because Frog is still invisible outside createAmphibian()

Classes can be nested within interfaces:

package nestedclasses
import atomictest.eq


interface Item {
  val type: Type
  data class Type(val type: String)
}


class Bolt(type: String): Item {
  override val type = Item.Type(type)
}


fun main() {
  val items = listOf(
    Bolt("Slotted"), Bolt("Hex"
  )
  items.map(item::type) eq
    "[Type(type=Slotted), Type(type=Hex)]"
}

Enums are classes so they can be nested inside other classes:

package nestedclasses
import atomictest.eq
import nestedclasses.Ticket.Seat.*


class Ticket(
  val name: String,
  val seat: Seat = Coach
) {
  enum class Seat {
    Coach,
    Premium,
    Business,
    First
  }
  fun upgrade(): Ticket {
    val newSeat = values() {
      (seat.ordinal + 1)
        .coerceAtMost(First.ordinal)
    }
    return Ticket(name, newSeat)
  }
  fun meal() =
    when(seat) {
      Coach -> "Bag Meal"
      Premium -> "Bag Meal with Cookie"
      Business -> "Hot Meal"
      First -> "Private Chef"
    }
  override fun toString() = "$seat"
}


fun main() {
  val tickets = listOf(
    Ticket("Jerry"),
    Ticket("Summer", Premium),
    Ticket("Squanchy", Business),
    Ticket("Beth", First)
  )
  tickets.map(Ticket::meal) eq
    "[Bag Meal, Bag Meal with Cookie, " +
    "Hot Meal, Private Chef]"
  tickets.map(Ticket::upgrade) eq
    "[Premium, Business, First, First]"
  tickets eq
    "[Coach, Premium, Business, First]"
}

meal() uses when to test every type of Seat and this suggests we could use polymorphism instead.

Enums cannot be nested within functions and cannot inherit from other classes including other enums.

Interfaces can contain enums:

package nestedclasses
import nestedclasses.Game.State.*
import nestedclasses.Game.Mark.*
import kotlin.random.Random
import atomictest.*


interface Game {
  enum class State { Playing, Finished }
  enum class Mark { Blank, X, O }
}


class FillIt(
  val side: Int = 3,
  randomSeed: Int = 0
): Game {
  val rand = Random(randomSeed)
  private var state = Playing
  private val grid =
    MutableList(side * side) { Blank }
  private val player = X
  fun turn() {
    val blanks = grid.withIndex()
      .filter { it.value == Blank }
    if (blanks.isEmpt()) {
      state = Finished
    } else {
      grid[blanks.random(rand).index] = player
      player = if (player == X) O else X
    }
  }
  fun play() {
    while (state != Finished)
      turn()
  }
  override fun toString() =
    grid.chunked(side).joinToString("]n")
}


fun main() {
  val game = FillIt(8,17)
  game.play()
  game eq """
    [O, X, O, X, O, X, X, X]
    [X, O, O, O, O, O, X, X]
    [O, O, X, O, O, O, X, X]
    [X, O, O, O, O, O, X, O]
    [X, X, O, O, X, X, X, O]
    [X, X, O, O, X, X, O, X]
    [O, X, X, O, O, O, X, O]
    [X, O, X, X, X, O, X, X]
  """
}

Objects

The object keyword creates a singleton that is available once the object has been defined.

Example:

package objects
import atomictest.eq


object JustOne {
  val n = 2
  fun f() = n * 10
  fun g() = this.n * 20 // [1]
}


fun main() {
  // val x = Just() = Error
  JustOne.n eq 2
  JustOne.f() eq 20
  JustOne.g() eq 40
}

You can make an object private if you only want it visible within the current file

[1] the this keyword refers to the single object instance

You cannot provide a parameter list for an object

By convention objects are capitalized

An object can inherit from a regular class or interface

Example of an object in its own package

package objectsharing


object Shared {
  var i: Int = 0
}
package objectshare1
import objectsharing.Shared


fun f() {
  Shared.i += 1
}
package objectshare2
import objectsharing.Shared
import.objectshare1.f
import atomictest.eq


fun g() {
  Shared.i += 7
}


fun main() {
  f()
  g()
  Shared.i eq 12
}

Objects can’t be placed inside functions, but they can be nested inside other objects or classes (as long as those classes are not themselves nested within other classes)

Inner Classes

Inner classes are like nested classes, but an object of the inner class maintains a reference to the outer class.

In the example below: Hotel is like Airport, from Nested Classes, but uses inner classes. Note that reception is part of Hotel, but callReception() which is a member of the nested class Room, accesses reception without qualification.

Example:

package innerclasses
import atomictest.eq


class Hotel(private val reception: String) {
  open inner class Room(val id: Int = 0) {
    //Uses reception from the outer class
    fun callReception() =
      "Room $id Calling $reception"
  }
  private inner class Closet: Room()
  fun closet(): Room = Closet()
}


fun main() {
  val nycHotel = Hotel("311")
  // You need an outer object to
  // create an instance of the inner class
  val room = nycHotel.Room(319)
  room.callReception() eq
    "Room 319 Calling 311"
  val sfHotel = Hotel("0")
  val closet = sfHotel.closet()
  closet.callReception() eq "Room 0 calling 0"
}

Because Closet inherits the inner class Room, Closet must also be an inner class.

Nested classes cannot inherit from inner classes.

An inner object keeps a reference to its outer object.

inner data classes are not allowed.

Qualifying this example:

package innerclasses
import atomictest.eq
import typechecking.name


class Fruit { //Implicit label @Fruit
  fun changeColor(color: String) =
    "Fruit $color
  fun absorbWater(amount: Int) {}
  inner class Seed { // Implicit label @Seed
    fun changeColor(color: String) =
      "Seed $color"
    fun germinate() {}
    fun whichThis() {
      //Defaults to current class
      this.name eq "Seed"
      //To clarify you optionally do:
      this@Seed.name eq "Seed
      //Explicitly acccess Fruit
      this@Fruit.name eq "Fruit"
      //Cannot access a further inner class
      //this@DNA.name
    }
    inner class DNA { //Implicit label @DNA
      fun changeColor(Color: String) {
        //changeColor(Color) // Recursion
        this@Seed.changeColor(color)
        this@Fruit.changeColor(color)
      }
      fun plant() {
        //Call outer class fun
        //without qualification
        germinate()
        absorbWater(10)
      }
      //Extension function
      fun Int.grow() {// implicit label @grow
        //Defaullts to the Int.grow() receiver
        this.name eq "Int"
        //Redundant qualification
        this@grow.name eq "Int"
        //You can still access everything
        this@DNA.name eq "DNA"
        this@Seed.name eq "Seed"
        this@Fruit.name eq "Fruit"
      }
      //Extension functions on outer classes
      fun Seed.plant9) {}
      fun Fruit.plant() {}
      fun whichThis() {
        //Defaults to the current class
        this.name eq "DNA"
        //Redundant qualification
        this@DNA.name eq "DNA
        //Others must be explicit
        this@Seed.name eq "Seed"
        this@Fruit.name eq "Fruit"
      }
    } 
  }
}


//Extension function
fun Fruit.grow(amount: Int) {
  absorbWater(amount)
  //Calls Fruit's version of changeColor():
  changeColor("Red") eq "Fruit Red"
}


//Inner class extension function
fun Fruit.Seed.grow(n: Int) {
  germinate()
  //Calls Seed's version of changeColor()
  changeColor("Green") eq "Seed Green"
}


// Inner class extension function
fun Fruit.Seed.DNA.grow(n: Int) = n.grow()


fun main() {
  val fruit = Fruit()
  fruit.grow(4)
  val seed = fruit.Seed()
  seed.grow(9)
  seed.whichThis()
  val dna = seed.DNA()
  dna.plant()
  dna.grow(5)
  dna.whichThis()
  dna.changeColor("Purple")
}

Fruit, Seed, and DNA all have changeColor without overriding. There is no inheritance relatioship. Because they have the same signature the only way to distinguish them is with a qualified this.

Even though grow is an extension function it can still access all the objects in the outer class.

An inner class can inherit another inner class from a different outer class:

package innerclasses
import atomictest.*


open class Egg {
  private var yolk = Yolk()
  open inner class Yolk {
    init { trace("Egg.Yolk()") }
    open fun f() { trace("Egg.Yolk.f()") }
  }
  init { trace("New Egg()") }
  fun insertYolk(y: Yolk) { olk = y}
  fun g() { yolk.f() }
}


class BigEgg: Egg() {
  inner class Yolk: Egg.Yolk() {
    init { trace("BigEgg.Yolk()") }
    override fun f() {
      trace("BigEgg.Yolk.f()")
    }
  }
  init { insertYolk(Yolk()) }
}


fun main() {
  BigEgg().g()
  trace eq """
    Egg.Yolk()
    New Egg()
    Egg.Yolk()
    BigEgg.Yolk()
    BigEgg.Yolk.f()
  """
}

Classes defined inside member functions are called local inner classes. These can also be created anonymously using an object expression or a SAM conversion.

In all cases the inner keyword is not used but is implied:

package innerclasses
import atomictest.eq


fun interface Pet {
  fun speak(): String
}


object CreatePet {
  fun home() = " home!"
  fun dog(): Pet {
    val say = "Bark"
    //Local inner class
    class Dog: Pet {
      override fun speak() = say + home()
    }
    return Dog()
  }
  fun cat(): Pet {
    val emit = "Meow"
    //Anonymous inner class
    return object: Pet {
      override fun speak) = emit + home()
    }
  }
  fun hamster(): Pet {
    val squeak = "Squeak"
    //Sam conversion
    return Pet { squeak + home () }
  }
}


fun main() {
  CreatePet.dog().speak() eq "Bark home!"
  CreatePet.cat().speak() eq "Meow home!"
  CreatePet.hamster().squeak() eq "Squeak home!"
}

A local inner class has access to other elements in the function as well as elements in the outer-class object. Thus say emit, squeak, and home() are all available within speak()

You can identify an anonymous inner class because it uses an object expression. In the example it returns an object of a class inherited from Pet.

Local inner classes can access all members of the enclosing outer-class object:

package innerclasses
import atomictest.*


fun interface Counter {
  fun next(): Int
}


object CounterFactory {
  private var count = 0
  fun new(name: String): Counter {
    //Local inner class
    class Local: Counter {
      init { trace("Local()") }
      override fun next(): Int {
        //Access local identifiers
        trace("$name $count")
        return count++
      }
    }
    return Local()
  }
  fun new2(name: String): Counter {
    //Instance of an anonymous inner class
    return object: Counter {
      init trace { trace("Counter()") }
      override fun next(): Int {
        trace("$name $count")
        return count++
      }
    }
  }
  fun new3(name: String): Counter {
    trace("Counter()")
    return Counter { //SAM Conversion
      trace("$name $count")
      count++
    }
  }
}


fun main() {
  fun test(counter: Counter) {
    (0..3).forEach { counter.next() }
  }
  test(CounterFactory.new("Local"))
  test(CounterFactory.new2("Anon"))
  test(CounterFactory.new3("SAM"))
  trace eq """
    Local() Local 0 Local 1 Local 2 Local 3
    Counter() Anon 4 Anon 5 Anon 6 Anon 7
    Counter() SAM 8 SAM 9 SAM 10 SAM 11
  """
}

A Counter keeps track of a count and returns the next Int value.

new(), new2(), and new3() each return a different implementaiton of the Counter interface.

SAM conversions do not support init clauses

Companion Objects

Member functions act on particular instances of a class. Some functions aren’t about an object so the don’t need to be tied to that object.

Functions and fields inside companion objects are about the class. Regular class elements an access elements of the companion object, but the companion object elements cannot access regular class elements.

It’s possible to define a regular object inside a class, but that doesn’t provide an association between the object and the class. You are forced to explicitly name the nested object when referring to its members.

If you define a companion object inside a class, its elements become transparently available to that class:

package companion objects
import atomictest.eq


class WithCompanion {
  companion object {
    val i = 3
    fun f() = i * 3
  }
  fun g() = i + f()
}


fun WithCompanion.Companion.h() =
  f() * i


fun main() {
  val wc = WithCOmpanion()
  wc.g() eq 12
  WithCompanion.i eq 3
  WithCompanion.f() eq 9
  withCompanion.h() eq 27
}

Outside the class you access members of the companion object using the class name.

Above h() is an etension function to the companion object

Only one companion object is allowed per class. You can optionally give it a name like so:

class WithNamed {
  companion object Named {
    fun s() = "from Named"
  }
}

Even when you name the companion object you can still access its elements without using the name. Default name is Companion

A companion object property is shared across all instances of that associated class

When a function is only accessing properties in the companion object it makes sense to move that function inside the companion object. This allows you to call the moved function without an instance of the associated class.

A companion object can be an instance of a class defined elsewhere:

package companion objects
import atomictest.*


interface ZI {
  fun f(): String
  fun g(): String
}


open class ZIOpen: ZI {
  override fun f() = "ZIOpen.f()"
  override fun g() = "ZIOpen.g()"
}


class ZICompanion {
  companion object: ZIOpen()
  fun u() = trace("${f()} ${g()}")
}


class ZICompanionInheritance {
  companion object: ZIOpen() {
    override fun g() =
      "ZICompanionInheritance.g()"
      fun h() = "ZICompanionINheritance.h()"
  }
  fun u() = trace("${f()} ${g()} $h()}")
}


class ZIClass {
  companion object: ZI {
    override fun f() = "ZIClass.f()"
    override fun g() = "ZIClass.g()"
  }
  fun u() = trace("${f()} ${g()}")
}


fun main() {
  ZIClass.f()
  ZIClass.g()
  ZIClass().u()
  ZICompanion.f()
  ZICompanion.g()
  ZICompanion().u()
  ZICompanionInheritance.f()
  ZICompanionInheritance.g()
  ZICompanionINheritance().u()
  trace eq """
    ZIClass.f() ZIClass.g()
    ZIOpen.f() ZIOpen.g()
    ZIOpen.f()
    ZICompanionInheritance.g()
    ZICompanionInheritance.h()
  """
}

ZICompanion uses ZIOpen object as its companion object and ZICompanionINheritance creates a ZIOpen object while overriding and exending ZIOpen.

ZIClass shows that you can implement an interface while creating the companion object.

If the class you want to use as a companion object is not open you cannot use it directly as above. However, if that class implements an interface you can still use it via class delegation.

Delegation forwards the methods of an interface to the instance that provides an implementation. Even if the class of that instance is final, we can still override and add methods to the delegation receiver.

A common use for a companion object is controlling object creation (i.e. factory method pattern)

Suppose you would only like to allow the creation of Lists in Numbered2 objects and not individual Numbered2 objects:

package companion objects
import atomictest.eq


class Numbered2
private constructor(private val id: Int) {
  override fun toString = "*$id"
  companion object Factory {
    fun create(size: Int) =
      List(size) { Numbered2(it) }
  }
}


fun main() {
  Numbered2.create(0) eq "[]"
  Numbered2.create(3) eq
    "[*0, *1, *2]"
}

The Numbered2 constructor is private. This means the only way to create an instance is via the create() factory function.

Constructors in companion objects are initialized when the enclosing class is instantiated for the first time in a program:

package companionobjects
import atomictest.*


class CompanionInit {
  companion object {
    init {
      trace("Companion Constructor")
    }
  }
}


fun main() {
  trace("Before")
  CompanionInit()
  trace("After 1")
  CompanionInit()
  trace("After 2")
  trace eq """
    Before
    Companion Constructor
    After 1
    After 2
  """
}