Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlin Language

Kotlin Language

Introduction to Kotlin Language.

Jussi Pohjolainen

December 16, 2020
Tweet

More Decks by Jussi Pohjolainen

Other Decks in Technology

Transcript

  1. Why Kotlin? • It's "Java" without the "boilerplate" • Compatible

    with JVM • Android – development • 2017: first-class support for Kotlin in Android • 2019, preferred language for Android App Development • In Java, JDK 7 is fully supported, some JDK 8 features also available • Spring – development • Also possible to to use Kotlin -> JavaScript transpilers
  2. Why Kotlin? • Less code • Safe (null pointer exceptions)

    • Interoperable (JVM Support) • Tool friendly, choose any Java IDE or command line
  3. JVM • Java bytecode is the instruction set of the

    Java virtual machine • When compiling .java -> .class you will end up with bytecode • javac App.java • Bytecode is run using JVM • java App • Java is the most common language for JVM • But there are others • Scala, Groovy, Clojure, Kotlin
  4. Working with the Command Line Compiler • Download latest release

    • https://github.com/JetBrains/kotlin/releases/latest • Download only the compiler • Put the compiler to path • More information: • https://kotlinlang.org/docs/tutorials/command-line.html
  5. $ cat Hello.kt fun main() { println("Hello World!") } $

    kotlinc Hello.kt $ java HelloKt Hello World!
  6. $ cat Hello.kt fun main(args: Array<String>) { println("Hello World!") }

    $ kotlinc Hello.kt $ java HelloKt Exception in thread "main" java.lang.NoClassDefFoundError: kotlin/jvm/internal/Intrinsics at HelloKt.main(Hello.kt) Caused by: java.lang.ClassNotFoundException: kotlin.jvm.internal.Intrinsics at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:606) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:168) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522) ... 1 more
  7. Default imports import java.lang.*; import kotlin.* import kotlin.annotation.* import kotlin.collections.*

    import kotlin.comparisons.* import kotlin.io.* import kotlin.ranges.* import kotlin.sequences.* import kotlin.text.*
  8. $ cat Hello.kt fun main() { println("Hello World!") } $

    kotlinc Hello.kt $ java -cp .:/path/to/kotlinc/lib/kotlin-runner.jar HelloKt Hello World! $ kotlinc Hello.kt -include-runtime -d app.jar $ java -jar app.jar Hello World! Kotlin classes It will dynamically add main class attribute to the class that holds main function
  9. Output contents of class file > javap -c HelloKt.class Compiled

    from "Hello.kt" public final class HelloKt { public static final void main(java.lang.String[]); Code: 0: aload_0 1: ldc #9 // String args 3: invokestatic #15 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V 6: ldc #17 // String Hello World! 8: astore_1 9: iconst_0 10: istore_2 11: getstatic #23 // Field java/lang/System.out:Ljava/io/PrintStream; 14: aload_1 15: invokevirtual #29 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 18: return }
  10. Easier running $ cat Hello.kt fun main(args: Array<String>) { println("Hello

    World!") } $ kotlinc Hello.kt $ kotlin HelloKt Hello World! Kotlin generates HelloKt named class
  11. Working with Packages $ cat Main.kt package com.organisation.main import fi.util.*;

    fun main() { println(sum(5,5)) } $ cat Util.kt package fi.util fun sum(a: Int, b: Int) : Int { return a + b } MacBook-Pro-7:test pohjus$ Declare package Importing package
  12. Compiling $ kotlinc *.kt $ tree . ├── META-INF │

    └── main.kotlin_module ├── Main.kt ├── Util.kt ├── com │ └── organisation │ └── main │ └── MainKt.class └── fi └── util └── UtilKt.class It creates dir structure Every top level function will be a class in the end. Rapid discover of top-level members with this meta data file
  13. Basic Types • Everything seems to be an object, even

    basic types! • Some of the types can have special internal representation, can be presented as primitive value • When using Int it is compiled to Integer or int to JVM • Use val for constant, var for variable.
  14. // Int val one = 1 // Declaring a type

    val another : Int = 1 // Long val threeBillion = 3000000000 // Long val oneLong = 1L // Byte val oneByte: Byte = 1 // Float val oneFloat = 1.0f // Double val oneDouble = 1.0
  15. Type Conversion fun main() { // Float val oneFloat =

    1.0f // Double val oneDouble : Double = oneFloat } Code.kt:6:30: error: type mismatch: inferred type is Float but Double was expected val oneDouble : Double = oneFloat
  16. Type Conversion fun main() { // Float val oneFloat =

    1.0f // Double val oneDouble : Double = oneFloat.toDouble() } Use methods to make the cast
  17. Type Conversion between classes open class Person { fun drink()

    { println("Drink Water"); } } class Programmer : Person() { fun codeApps() { println("Code Apps") } } fun main() { val tina : Person = Programmer() tina.drink() val temp : Programmer = tina as Programmer temp.codeApps() } To type cast between objects, use as or as?
  18. Other • Characters • Char • Cannot be treated directely

    as numbers, use toInt() • Booleans • Boolean • Arrays • Array • Has [] operator overloading, size and other useful methods • You can create it using arrayOf() function • Also primitive type arrays available: IntArray, ByteArray ...
  19. Simple Example fun main() { // character val x :

    Char = 'a' // a print(x) // 97 print(x.toInt()) val value : Boolean = 1 < 2 // true print(value) // false print(value.not()) }
  20. String • Strings are immutable • You can access characters

    using string[i] syntax • Can be iterated using for • Can concatenat using + • You can use ', " or """ • You can embed variables: "i = $i"
  21. fun main() { val value : Double = Math.random() val

    embed = "random = $value" val list = """ <ul> <li>Hello</li> </ul> """ println(list) println(embed) println(embed[0]) }
  22. fun main() { val value = "hello world" for(i in

    0..value.length - 1) { println(value[i]) } for(i in 0 until value.length) { println(value[i]) } } Using ranges
  23. Nullable fun main() { val oneInt : Int = 1

    oneInt = null } Internally oneInt is a primitive type... If it is a primitive type can you do this?
  24. Problem 1: val vs var fun main() { val oneInt

    : Int = 1 oneInt = null } val is constant
  25. Problem 2: Int vs Int? fun main() { var oneInt

    : Int? = 1 oneInt = null } Now internal type is Integer (object)
  26. Problem? fun generate(): String? { val random = (Math.random() *

    2).toInt() if(random == 0) { return "Hello World"; } else { return null; } } fun main() { val value : String? = generate() println(value.length) } What is the problem here?
  27. Common pitfall: Null Pointer Exceptions • In Kotlin, the type

    system distinguishes between references that can hold null and those that can not • val a : String? = "Hello World" • a = null • To check null conditions (does variable contain null) • if • Safe call • Elvis Operator • !! Operator • Also casting available (Safe Casts)
  28. Checking for null conditions:if fun generate(): String? { val random

    = (Math.random() * 2).toInt() if(random == 0) { return "Hello World"; } else { return null; } } fun main() { val value : String? = generate() val length = if(value != null) value.length else -1 println(length) } null checking! Generate 0 or 1 (kotlin has a nicer way of doing this ..)
  29. Checking for null conditions: if fun generate(): String? { val

    random = (Math.random() * 2).toInt() if(random == 0) { return "Hello World"; } else { return null; } } fun main() { val value : String? = generate() if(value != null) println(value.length) } Works also!
  30. Checking for null conditions: if fun generate(): String? { val

    random = (Math.random() * 2).toInt() if(random == 0) { return "Hello World"; } else { return null; } } fun main() { val value : String? = generate() if(value != null && value.length > 0) println("It is not empty string") } Works also!
  31. Checking for null conditions: Save Call fun generate(): String? {

    val random = (Math.random() * 2).toInt() if(random == 0) { return "Hello World"; } else { return null; } } fun main() { val value : String? = generate() println(value?.length) } Returns either 11 or null
  32. Checking for null conditions: Save Call fun myrandom() : Boolean

    { return (0..1).random() == 0 } class Person { var pet : Pet? = if(myrandom()) Pet() else null } class Pet { var favoriteToy: Toy? = if(myrandom()) Toy() else null } class Toy { var name: String? = if(myrandom()) "ball" else null } fun main() { val person : Person? = if(myrandom()) Person() else null println(person?.pet?.favoriteToy?.name) } If some of these are null, output null Kotlin way of creating random value No need for ternary operator, if is enough
  33. Checking for null conditions: Elvis operator ?: fun main() {

    val hello : String? = if((0..1).random() == 0) "hello world" else null val length1 = if(hello != null) hello.length else -1 println(length1) val length2 = hello?.length ?: -1 println(length2) } Maybe "hello world" or null Outputs 11 or -1 Does the same but uses elvis operator!
  34. Checking for null operators: !! fun main() { val hello

    : String? = if((0..1).random() == 0) "hello world" else null val length = hello!!.length println(length) } Crashes the app if hello is null
  35. Safe Cast: as? • You can use save cast between

    objects • val circle : Circle? = object as? Circle • Now the circle here may contain null or the object
  36. Control Flow: If • No ternary operator! Just use if:

    • val max = if (a > b) a else b • Or: val max = if (a > b) { print("Choose a") a } else { print("Choose b") b }
  37. Control Flow: switch case, when when (x) { 1 ->

    print("x == 1") 2 -> print("x == 2") else -> { print("x is neither 1 nor 2") } }
  38. Control Flow: when when (x) { 0, 1 -> print("x

    == 0 or x == 1") else -> print("otherwise") }
  39. Control Flow: when val grade = 8 val output =

    when(grade) { 1,2,3,4 -> "Fail" 5,6,7,8 -> "Ok" 9,10 -> "Great" else -> "Unknown" } println(output)
  40. Control Flow: For - loop val myarray : Array<Int> =

    arrayOf(1,2,3) for(item in myarray) { println(item) } Must provide iterator
  41. Ranges • Create ranges of values using rangeTo() function •

    Range defines interval with two endpoint values • Ranges are defined for comparable types • Has order • IntRange, LongRange and CharRange can be also iterated
  42. Custom Range class Person(var weight: Int) : Comparable<Person> { override

    fun compareTo(other: Person): Int { return this.weight - other.weight } } fun main() { val personRange = Person(50).rangeTo(Person(100)) print(Person(60) in personRange) } true You cannot iterate this, that must be implemented with custom iterator()
  43. Example of rangeTo (and ..) function val rangeInt1 : IntRange

    = 1.rangeTo(10) val rangeChar1 : CharRange = 'a'.rangeTo('z') // The same! val rangeInt2 : IntRange = 1..10 val rangeChar2 : CharRange = 'a'..'z' Operator form of range
  44. Combine range with in function fun main() { val rangeInt

    : IntRange = 1..10 // False println(0 in rangeInt) // True println(1 in rangeInt) // False println(12 in rangeInt) }
  45. Progression • Integral type ranges (Int, Long, Char) has extra

    feature: they are also progressions • Progressions can be iterated over • Progressions have three essential properties • first • last • step
  46. For – loop to Kotlin loop for (int i =

    first; i <= last; i += step) { // ... } for (i in first..last step step) print(i) Traditional loop (not kotlin) Kotlin approach
  47. for + in + range val rangeInt : IntRange =

    1..10 for(i in rangeInt) { println(i) }
  48. Control Flow: For – loop using range expression for (i

    in 6 downTo 0 step 2) { println(i) }
  49. Comparing Strings fun main() { val secret = "ken sent

    me" println("Give password:") val password : String? = readLine() if(password != null) { println(password == secret) println(password === secret) } } translates to equals translates to ==
  50. String templates fun main() { val myarray = arrayOf("Hello", "World")

    val simpleStr = "again" val str : String = """ <ul> <li>${myarray.get(0)}</li> <li>${myarray.get(1)}</li> <li>$simpleStr</li> </ul> """ println(str) } Can contain expression or a simple value
  51. Trim margins fun main() { val myarray = arrayOf("Hello", "World")

    val simpleStr = "again" val str : String = """ |<ul> | <li>${myarray.get(0)}</li> | <li>${myarray.get(1)}</li> | <li>$simpleStr</li> |</ul> """.trimMargin("|") println(str) } Clears extra whitespaces. "|" is on by default, you can omit that.
  52. Arrays • Arrays are presented by Array – class •

    Presents array in JVM • Can be created • constructor, arrayOf, arrayOfNulls, EmptyArray • Lot of different methods available • Notice that Kotlin provides also it's own collection classes
  53. Creating arrays and collection classes val a : Array<Int> =

    arrayOf(1, 2, 3) val l : List<Int> = listOf(1, 2, 3) val m : MutableList<Int> = mutableListOf(1, 2, 3)
  54. Array<Int> fun main() { var t1 : Array<Int> = arrayOf(1,

    2, 3) println(t1.size) println(t1.get(0)) t1.set(0, 12) t1[0] = 12 println(t1.get(0)) println(t1[0]) }
  55. Different methods available fun main() { var t1 : Array<Int>

    = arrayOf(1, 2, 3) println(t1.average()) }
  56. Functions • Kotlin functions are first-class • passing functions as

    arguments to other functions • returning functions from frunctions • assigning functions into variables
  57. Basic Function fun sum(a : Int, b: Int) : Int

    { return a + b } fun main() { println(sum(5, 5)) }
  58. Basic Function fun sum(a : Int, b: Int) : Int

    { return a + b } fun main() { println(sum(a = 5, b = 5)) }
  59. Any, Unit • Java has Object (root class) and void

    to represent lack of type • Problem with primitive types, boxing • In Kotlin: Any is supertype for all non-nullable types • Also primitive types are any • In Kotlin: Unit is the same than void • Unit is a type, and can be used as type argument • It is returned implicitly, no need of a return statement • Unit is needed in generics
  60. Unit usage fun doIt1() : Unit { println("Hello world") }

    fun doIt2() { println("Hello world") } fun main() { val a = doIt1() println(a) // kotlin.Unit val b = doIt2() println(b) // kotlin.Unit }
  61. Basic function with default arguments fun repeat(text: String, amount: Int

    = 1) : String { var newString = "" for(i in 1..amount) { newString += text } return newString } fun main() { println(repeat("hello", 10)) } Return type Default value
  62. Named arguments fun repeat(text: String, wordSeparator : String = "_",

    amount: Int = 1) : String { var newString = "" for(i in 1..amount - 1) { newString += text + wordSeparator } newString += text return newString } fun main() { println(repeat("hello", amount = 10)) } Using named argument we change the last argument of the function
  63. Named arguments fun repeat(text: String, wordSeparator : String = "_",

    amount: Int = 1) : String { var newString = "" for(i in 1..amount - 1) { newString += text + wordSeparator } newString += text return newString } fun main() { println(repeat("hello", 10)) } Three arguments "error: the integer literal does not conform to the expected type String"
  64. Single-expressions fun sum(a : Int, b: Int) : Int =

    a + b fun main() { println(sum(5,5)) } You can omit { and } and even return if only one line
  65. Varargs fun sum(vararg numbers : Int) : Int { var

    sum = 0 for(item in numbers) { sum += item } return sum } fun main() { println(sum(5,5,5)) val a = intArrayOf(1, 2, 3) println(sum(*a)) } We can send list of numbers Notice that if having array, use a spread (*) operator
  66. infix notation class Person { infix fun drinkBeer(beer: String) {

    println("drinking beer " + beer) } } fun main() { val jaska = Person() jaska drinkBeer "Lapin kulta" jaska.drinkBeer("lapin kulta") } infix functions must be 1) member functions or extension function 2) must have single parameter 3) must not use varargs You can now call the method if different syntax
  67. Prebuild infix notation fun main() { val progression1 : IntProgression

    = 6.downTo(0) val progression2 : IntProgression = 6 downTo 0 for(i in progression1) { println(i) } for(i in progression2) { println(i) } for(i in 6 downTo 0) { println(i) } }
  68. Local Functions fun main() { fun outer(a: Int) { fun

    inner(b: Int) : Int { return a + b } println(inner(5)) } outer(5) } local function can access outer functions variables (closure)
  69. Function Type and Reference fun sum(a : Int, b :

    Int) : Int = a + b fun main() { var myfun: (Int, Int) -> Int myfun = ::sum println(myfun(4,4)) } Type here is a function Function reference
  70. Lambda example fun main() { var myfun: (Int, Int) ->

    Int myfun = { a, b -> a + b } println(myfun(4,4)) } local function can access outer functions variables (closure)
  71. Lambda Example fun readFile(path: String, callback: (data: String, err: String)

    -> Unit) { callback("some data", "") } fun main() { readFile("file.txt", { data: String, err: String -> if(err.length > 0) { throw Exception("failed") } println(data) }) } Passing lambda
  72. Lambda fun readFile(path: String, callback: (data: String) -> Unit) {

    callback("some data") } fun main() { readFile("file.txt", { data: String -> println(data) }) readFile("file.txt", { data -> println(data) }) readFile("file.txt") { data -> println(data) } readFile("file.txt") { println(it) } } Different syntax, does the same Second argument is a function Trailing lambda if last argument is function! When using only one argument you can use it!
  73. Lambda with Unit return type fun map(myarray: Array<Int>, callback: (item:

    Int) -> Unit) { for(i in myarray.indices) { callback(myarray[i]) } } fun main() { val myarray = arrayOf(1,2,3) map( myarray, { item -> println(item) } ) } Returns Unit, is a "void" function
  74. Lambda with Unit return type fun map(myarray: Array<Int>, callback: (item:

    Int) -> Unit) { for(i in myarray.indices) { callback(myarray[i]) } } fun main() { val myarray = arrayOf(1,2,3) map( myarray, { item -> item + 1 } ) } Returns Unit, is a "void" function Does not return anything
  75. Lambda with Unit return type fun map(myarray: Array<Int>, callback: (item:

    Int) -> Int) { for(i in myarray.indices) { myarray[i] = callback(myarray[i]) } } fun main() { val myarray = arrayOf(1,2,3) map( myarray, { item -> item + 1 } ) } Returns Int Lambda returns the last line!
  76. Lambda's last line is return fun main() { val myarray

    = arrayOf(-1, -2, 3, 4, 5) map( myarray, { item -> if(item > 0) { return item + 1 } else { return item -1 } } ) myarray.forEach { println(it) } } Hello.kt:11:13: error: 'return' is not allowed here return item + 1
  77. Lambda's last line is return map( myarray, { item ->

    var returnValue = item - 1 if(item > 0) { returnValue = item + 1 } returnValue } )
  78. Example of map - function val list = arrayOf(1, 2)

    val another1 = list.map( { value : Int -> value + 1} ) val another2 = list.map( { value -> value + 1} ) val another3 = list.map() { value -> value + 1 } val another4 = list.map() { it -> it + 1 } val another5 = list.map() { it + 1 } println(another1) println(another2) println(another3) println(another4) println(another5)
  79. Simple classes class Dog {} class Cat fun main() {

    val dog = Dog() val cat = Cat() } If empty body you can omit curly braces
  80. Constructors • Class in Kotlin can have • Primary constructor

    • Secondary constructors • Primary constructor is part of the class header
  81. Primary Constructor class Dog constructor(name: String) {} class Cat(name: String)

    { } fun main() { val dog = Dog("spot") val cat = Dog("onni") } If no annotations you can omit constructor keyword constructor does not contain any code, this is done in init block!
  82. init block class Cat(name: String) { var name : String

    init { println("initializer here!") this.name = name } } fun main() { val cat = Cat("onni") println(cat.name) } init block is executed
  83. init block omit class Cat(name: String) { var name :

    String = name } fun main() { val cat = Cat("onni") println(cat.name) } If only one line, we can do this directly without the need for a block
  84. Declaring properties from primary constructor class Cat(var name: String) {

    } fun main() { val cat = Cat("onni") println(cat.name) } By adding var or val here it will initialize a property!
  85. Visibility class Cat private constructor(var name: String) { } fun

    main() { val cat = Cat("onni") println(cat.name) } Does NOT work because this is private Notice that if using visibility modifiers (or annotations) then constructor keyword is required
  86. Secondary Constructor class Cat (var name: String) { var age:

    Int = 0 constructor(name: String, age: Int) : this(name) { this.age = age } } fun main() { val cat = Cat("onni", 3) println(cat.name) println(cat.age) } Secondary constructor must call primary constructor
  87. Inheritance • All classes inherit superclass Any • class Dog

    // inherites Any • Any has three methods • equals(), hashCode() and toString() • By default all classes are final, they cannot be inherited! • To make class open for inherit: • open class Mammal
  88. Example open class Person(var name: String) class Programmer(name : String,

    var salary : Int) : Person(name) fun main() { val tina = Programmer("tina", 4000) println(tina.name) println(tina.salary) } You must initialize the base class here
  89. override open class Shape(var x : Int, var y :

    Int) { open fun draw() { println("rect") } } class Circle(x : Int, y : Int, var radius: Int) : Shape(x, y) { override fun draw() { println("circle") } } fun main() { val c = Circle(5,5,5) c.draw() } overriding can be done only if function is declared as open!
  90. Abstract class abstract class Shape(var x : Int, var y

    : Int) { abstract fun draw() } class Circle(x : Int, y : Int, var radius: Int) : Shape(x, y) { override fun draw() { println("circle") } } fun main() { val c = Circle(5,5,5) c.draw() } Declaring class and method as abstract
  91. Example of GUI import javax.swing.*; import java.awt.event.*; class MyWindow(title :

    String) : JFrame(title), ActionListener { private var button : JButton init { this.button = JButton("Click") add(this.button) } } fun main() { val mywindow = MyWindow("hello world") mywindow.setSize(500,500) mywindow.setVisible(true) }
  92. "Traditional approach": backing property class Person { private var _age

    : Int = 0 var age: Int get() { return this._age } set(value) { this._age = value } } fun main() { var p = Person() p.age = 80 println(p.age) }
  93. Using keyword field class Person { var age: Int =

    12 get() { return field } } fun main() { var p = Person() println(p.age) } Special keyword field refers to the age here
  94. Declaring Getters and Setters class Person { var age: Int

    get() = field [<setter>] } Shorter syntax for returning
  95. Declaring Getters and Setters class Person { var age: Int

    get() = field set(value) { field = value } } Special keyword field refers to the age here
  96. Properties must be initialized class Car { var motor: Motor

    fun start() { this.motor = Motor() this.motor.start() } } fun main() { val c = Car() c.start() } error: property must be initialized or be abstract var motor: Motor
  97. lateinit class Car { lateinit var motor: Motor fun start()

    { this.motor = Motor() this.motor.start() } } fun main() { val c = Car() c.start() } It works now lateinit works only on non-primitive types
  98. Data class data class User(var name: String, var age: Int)

    fun main() { val a = User("jack", 40) val b = User("jack", 40) println(a.equals(b)) // true println(a.hashCode()) println(a.toString()) // "User(name=jack, age=40)" val c = b.copy() println(c) // User(name=jack, age=40) } equals / hashcode toString copy()
  99. Example interface Movable { fun move() } class Airplane :

    Movable { override fun move() { println("Airplane flies") } } class Bird : Movable { override fun move() { println("Bird flies") } } fun main() { var x : Movable = Bird() x.move() }
  100. Object Expression with Functional Interfaces interface Movable { fun move()

    } fun main() { move(object : Movable { override fun move() { println("anonymouse moves") } }) } fun move(movable : Movable) { movable.move() }
  101. Swing Example import javax.swing.*; import java.awt.event.*; class MyWindow(title: String) :

    JFrame(title) { val button = JButton("click") init { button.addActionListener(object: ActionListener { override fun actionPerformed(e : ActionEvent) { println("hello") } }) button.addActionListener() { println("world!") } add(button) } } fun main() { val window = MyWindow("hello") window.pack() window.setVisible(true) } If using Java's functional interfaces, you can use lambda syntax here
  102. mypackage2/Util.kt package mypackage2 // Visible inside of mypackage2/Util.kt private val

    PI = 3.14 // Visible everywhere public fun abs(value : Int) = if(value < 0) value * - 1 else value // Visible in the same module internal fun calculate(radius : Double) = PI * radius * radius // protected not available for top-level declarations
  103. protected open class Person(name : String) { protected var name

    : String init { this.name = name } } class Programmer(name : String) : Person(name) { fun hello() { println("$name hello") } }
  104. internal and module • IntelliJ IDEA module • Maven project

    • Gradle source set • Set of files compiled with one invocation <kotlinc> with Ant
  105. Extensions • Ability to extend a class with new functionality

    without inheritance • You can write new functionality to third – party libraries (that would not be modifiable) • You can have • extension functions • extension properties
  106. Example fun String.isPalindrome() : Boolean = this.reversed() == this fun

    main() { println( if (readLine()!!.isPalindrome()) "It was palindrome" else "It was not palindrome") } Extension to String class
  107. Extending properties val <T> List<T>.lastIndex: Int get() = size -

    1 fun main() { var list : List<Int> = listOf(1,2,3,4) print(list.lastIndex) }