0% found this document useful (0 votes)
6 views5 pages

Advancedkotlin 8

The document provides an overview of some advanced aspects of the Kotlin programming language, including interfaces, object expressions, generics, and companion objects. It discusses how interfaces can define abstract functions and properties, and how object expressions can be used to implement interfaces without defining a new class. Examples are provided to illustrate various concepts.

Uploaded by

Axe
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
6 views5 pages

Advancedkotlin 8

The document provides an overview of some advanced aspects of the Kotlin programming language, including interfaces, object expressions, generics, and companion objects. It discusses how interfaces can define abstract functions and properties, and how object expressions can be used to implement interfaces without defining a new class. Examples are provided to illustrate various concepts.

Uploaded by

Axe
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 5

Introduction

▶ We will comment on some aspects of Kotlin that facilitate


programming

VJ1229, Mobile Device Applications ▶ We will see:


• Interfaces

Advanced Aspects of Kotlin


• Some uses of the object keyword
• Some aspects of generics
• Scope functions
• The builder pattern
©2024, Juan Miguel Vilar Torres

1/35 2/35

Interfaces Example
We want to manage the NPCs for a game:

data class Point(val x: Int, val y: Int)

interface NPC {
▶ Declare abstract functions and functions implementations var location: Point
fun move()
▶ Can have abstract properties ➩ Cannot store state fun draw()
}

▶ If I is an interface, the variable class ManageNPCs {


val NPCs = ArrayList<NPC>()
var v: I
accepts objects of any class that implements the interface I fun addNPC(npc: NPC) = NPCs.add(npc)

fun updateScreen() {
for (npc in NPCs) {
npc.move()
npc.draw()
}
}
}
3/35 4/35

Example (2) Some Uses


Now we can declare our NPCs like this:

class Vampire(val name: String,


override var location: Point) : NPC { ▶ Group classes with similar characteristics
override fun move() {
// .. ▶ Allow the use of Collections with elements of different types
}
▶ Isolate one class from the implementation of other classes
override fun draw() {
// .. ▶ Ease testing
}
}

// ..
val manager = ManageNPCs()
manager.addNPC(Vampire("Dracula", Point(0, 10)))

5/35 6/35

Object Expressions An Example

interface StringProcessor {
fun transform(s: String): String
 }
 of an interface
▶ Used when you need an object

of a slightly different class fun main () {
val duplicator = object : StringProcessor {
and you don’t want to write a new class
override fun transform(s: String): String {
▶ They are written as the object keyword followed by a colon, return s + s
the interface or parent class, and the body }
}

val doubleGreeting = duplicator.transform("Hello")


}

7/35 8/35
Object for Singletons Example
object Identifiers {
private var counter = 0

fun getNumber() = counter++


▶ Used when there must be only one instance of a class
fun getId() = "id${getNumber()}"
▶ Eg: a single object to access a database, to access the
}
internet, etc
fun main () {
▶ Implemented with the object keyword and a name
val n = Identifiers.getNumber()
val m = Identifiers.getNumber()
▶ Don’t abuse it ➩ it is similar to a global variable
println("Two numbers: $n, $m") // 0, 1

val st1 = Identifiers.getId()


val st2 = Identifiers.getId()
println("Two strings: $st1, $st2") // id2, id3
}
9/35 10/35

Companion Objects Example

class Vampire(val name: String,


▶ A companion object is an object that can be associated to a override var location: Point) : NPC {
class // ..

▶ It allows the definition of attributes accessed through the class companion object {
name, no need to use an instance const val INITIAL_LIFE = 10
▶ Typical use: defining constants, factory methods, etc const val FIGHT_STRENGTH = 20
}
}

11/35 12/35

Generics Example

▶ Allow the parameterization of a class, function or interface


interface Stack<T> {
with a type val length : Int
▶ The type parameter is marked using <T> (where T is the name val isEmpty : Boolean
of the parameter) fun push(item : T)
fun pop(): T
▶ Very useful for collections }

13/35 14/35

Covariance and Contravariance Covariance and


Contravariance (2)
▶ It is possible to specify whether objects of the type variable
are only produced (covariant class) or consumed ▶ The use of covariance and contravariance annotations allow
(contravariant class) type safe assignments

▶ If T is only produced, it is marked as “out T”: ▶ If we have a class C with a type parameter out T:
interface Producer <out T> { • If S is a subtype of B then a value of type C<S> can be
fun produce(): T assigned to a variable of type C<B>
} val b: B = s
val cb : C<B> = cs
▶ If T is only consumed, it is marked as “in T”:
interface Consumer <in T> { ▶ If we have a class C with a type parameter in T:
operator fun consume(value: T): Int • If S is a subtype of B then a value of type C<B> can be
} assigned to a variable of type C<S>
val b: B = s
val cs : C<S> = cb
15/35 16/35
Example Example (2)
open class Animal
class Cat: Animal()
class Dog: Animal() interface Veterinary<in T> {
fun cure(patient: T)
interface PetShop<out T> { }
fun sell(money: Int): T
} fun testVeterinary(catVet: Veterinary<Cat>,
genericVet: Veterinary<Animal>) {
fun testPetShop(catPetShop: PetShop<Cat>, val dVet: Veterinary<Dog> = catVet // Error
genericPetshop: PetShop<Animal>) { val cVet: Veterinary<Cat> = genericVet // OK
val dPetShop: PetShop<Dog> = catPetShop // Error val gVet: Veterinary<Animal> = catVet // Error
val cPetShop: PetShop<Cat> = genericPetshop // Error }
val gPetShop: PetShop<Animal> = catPetShop // OK
}

17/35 18/35

Generic Functions Scope Functions

▶ They are used to apply some functions to an object

▶ Generic functions are marked with <T> before the class name: ▶ E.g. in the class ManageNPCs we could have written
updateScreen like this:
fun <T> mutableListOf(vararg elements: T): MutableList<T>

▶ When used, the type parameter can be omitted if it is inferred fun updateScreen() { fun updateScreen() {
for (npc in NPCs) { for (npc in NPCs)
from the context:
npc.move() with (npc) {
val list = mutableListOf(1, 2, 3) npc.draw() move()
} draw()
} }
}

19/35 20/35

Scope Functions (2) apply

▶ Applies some functions to an object and returns it


▶ Two axis of classification:
▶ This
• The object is accessed as this or it
return Vampire("Dracula", Point(2, 3)).apply {
• The return is the object or the value of the lambda
move()
▶ Available functions: draw()
}
Returns
Access ▶ is equivalent to
Object Lambda
val vampire = Vampire("Dracula", Point(2, 3))
this apply run, with vampire.move()
it also let vampire.draw()
return vampire

21/35 22/35

run with

▶ Similar to apply but returns the result of the lambda ▶ Similar to run but the object is received as a parameter

▶ This ▶ This
val currentPosition = vampire.run { val currentPosition = with (vampire) {
move() move()
draw() draw()
position position
} }

▶ is equivalent to ▶ is equivalent to
vampire.move() vampire.move()
vampire.draw() vampire.draw()
val currentPosition = vampire.position val currentPosition = vampire.position

23/35 24/35
also let
▶ Similar to apply but the object is accessed through it
▶ Similar to also but the result is the value of the lambda
▶ This
▶ This
return Vampire("Dracula", Point(2,3)).also {
it.move() val currentPosition = vampire.let {
it.draw() it.move()
println("The name of the vampire is ${it.name}") println("The name of the vampire is ${it.name}")
} it.position
}
▶ is equivalent to
▶ is equivalent to
val vampire = Vampire("Dracula", Point(2,3))
vampire.move() vampire.move()
vampire.draw() println("The name of the vampire is ${vampire.name}")
println("The name of the vampire is ${vampire.name}") val currentPosition = vampire.position
return vampire

25/35 26/35

The Builder Pattern The Screen Class

▶ Suppose we have a class for storing the information about a


▶ Used to build complex objects
screen in the game:
▶ Offers functions to add components or change options as class Screen (val dayLight: Double,
needed val lightSources: List<LightSource>,
▶ It usually uses fluid interfaces: val NPCs: List<NPC>,
val buildings: List<Building>
• The functions return the same object ) {
• It can be used as a (limited) DSL // ...
}
▶ It usually has a function that returns the object that has been
built ▶ Creating and filling the parameters of the constructor will be
cumbersome

27/35 28/35

The Builder Class The Builder Class (2)

And we add a function for each of the elements that can be added:
We create a class to incrementally build all the parameters:
fun setDayLight(dayLight: Double): ScreenBuilder {
class ScreenBuilder { this.dayLight = dayLight
private var dayLight: Double = 0.0 return this
private var lightSources = ArrayList<LightSource>() }
private var NPCs = ArrayList<NPC>()
Or, using apply:
private var buildings = ArrayList<Building>()
fun setDayLight(dayLight: Double) =
apply { this.dayLight = dayLight }

29/35 30/35

The Builder Class (3) The Builder Class (4)

The rest of elements that can be added:

fun addLightSource(lightSource: LightSource) = And a final function to actually build the screen
apply { lightSources.add(lightSource) }
fun createScreen() = Screen(dayLight,
fun addNPC(npc: NPC) = lightSources,
apply { NPCs.add(npc)} NPCs,
buildings)
fun addBuilding(building: Building) =
apply { buildings.add(building) }

31/35 32/35
Example of usage Example of usage (2)
Since each function returned the builder, they can be concatenated
like this:
Using scope functions, we can even shorten this code:
fun mainScreen(): Screen {
val builder = ScreenBuilder() fun mainScreen(): Screen = with(ScreenBuilder()) {
setDayLight(12.0)
builder.setDayLight(12.0) addLightSource(LightSource(Point(10, 20), 2.4))
.addLightSource(LightSource(Point(10, 20), 2.4)) addBuilding(Building("Saloon"))
.addBuilding(Building("Saloon")) addBuilding(Building("Drugstore"))
.addBuilding(Building("Drugstore")) addNPC(Vampire("Dracula", Point(100, 100)))
.addNPC(Vampire("Dracula", Point(100, 100))) createScreen()
}
return builder.createScreen()
}

33/35 34/35

Further Information

▶ In the Kotlin book

▶ Chapter “Abstract Classes and Interfaces”

▶ Chapter “The object Keyword”

▶ Chapter “Covariance in Generics”

▶ Chapter “Contravariance in Generics”

▶ Chapter “Scope Functions”

35/35

You might also like