Taking lambda as an argument in a function

fun main() {
    val game = Game()

    var gameIsNotEnded = true

    while (gameIsNotEnded) {
        print("Enter a direction: N/S/E/W:")
        gameIsNotEnded = game.makeMove(readLine())
    }
}

enum class Directions {
    NORTH, SOUTH, EAST, WEST, START, END
}

class Game {
    var path = mutableListOf(Directions.START)
    val north = {path.add(Directions.NORTH)}
    val south = {path.add(Directions.SOUTH)}
    val east = {path.add(Directions.EAST)}
    val west = {path.add(Directions.WEST)}
    val end = {path.add(Directions.END); println("Game Over");false}

    fun makeMove(moveDir: String?): Boolean {
        return when (moveDir) {
            "N" -> move(north) // This passes the 'north' lambda into the move function
            "S" -> move(south)
            "E" -> move(east)
            "W" -> move(west)
            else -> move(end)
        }
    }
}

fun move(where: () -> Boolean): Boolean { // Here we take a lambda as an argument
    return where.invoke() // The lambda will be invoked when called
}

 

Lateinit

Normally, properties declared as having a non-null type must be initialised in the constructor. However, fairly often this is not convenient. For example, properties can be initialised through dependency injection, or in the setup method of a unit test. In this case, you cannot supply a non-null initialiser in the constructor, but you still want to avoid null checks when referencing the property inside the body of a class.

To handle this case, you can mark the property with the lateinit modifier.

The modifier can be used on var properties declared inside the body of a class (not in the primary constructor, and only when the property does not have a custom getter or setter) and, since Kotlin 1.2, for top-level properties and local variables. The type of the property or variable must be non-null, and it must not be a primitive type.

Accessing a lateinit property before it has been initialised throws a special exception that clearly identifies the property being accessed and the fact that it hasn’t been initialised.

class MainActivity : AppCompatActivity() {

    private lateinit var tvDescription: TextView // We don't want to set the value yet but we'd also prefer to not set to nullable. Use lateinit to let compiler know you will initialise it later
    // N.B. If you don't initialise it the IDE will not warn you and you'll get a kotlin.UninitializedPropertyAccessException at runtime

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        tvDescription = findViewById(R.id.tv_description)
        tvDescription.text = "Refreshing glass of water gets you hydrated"


    }
}

 

Calling Kotlin from Java – Interoperability

Things to consider when calling Kotlin from Java.

import academy.learnprogramming.kotlincode.*;

import static academy.learnprogramming.kotlincode.CarKt.topLevel;
import static academy.learnprogramming.kotlincode.StaticCar.anotherTopLevel;

public class Main {

    public static void main(String[] args) {
  // So how do we call the top-level function from the Car.kt file?
        topLevel(); // If we type the function name the IDE suggests an import (see above). Compiler will quietly generate static class for all top-level items and will give the class the same name as the Kotlin file name.
        // So if we want to refer to a class we use the file name:
        CarKt.topLevel();
        //To demonstrate how we can deal with naming conflicts:
//        anotherTopLevel(); // Without using JvmName to rename file IDE cannot locate this function
        anotherTopLevel(); //Once JvmName has been implemented the IDE will suggest an import under the 'StaticCar' file name
        CarKt.print("Print this Java string"); // We cannot invoke extension function like we would in Kotlin. Need to use standard call
        Car car = new Car("blue", "BMW", 2011);
        System.out.println(car.getModel()); // We call getters and setters from Java as standard as compiler has produced these for Kotlin class properties by default (val only generates getters). Wouldn't work if properties private
        Car2 car2 = new Car2("red", "Ferrari", 2017, false);
        car2.setColour("purple"); // If custom sets and gets are created Java will access those
//        car2.setModel("A3"); // But not if setter is private
//        car2.setYear(1999); // Doesn't exist as setters only generated for var not val
        System.out.println(car2.getColour());
        System.out.println(car2.isAutomatic()); // When dealing with Booleans generates getter name beginning with 'is'
        // If we want Java to access the property directly without going through getter or setter, we can add an @JvmField annotation to the property:
        int year = car2.year; // Can't use @JvmField with private properties or one that overrides another property or can be overridden, or one with const keyword
        // How do we access functions within companion objects? Same for non-anonymous object instances (instances we create using the object keyword)?
        // Must use @JvmStatic annotation. Results in two version being created. One in instance as normal, but also one as a Java static function.
        Car2.Companion.carComp(); // Without @JvmStatic we're accessing the instance method generated by the compiler, not the static version. So we can't type Car2.carComp()
        Car2.carComp2(); // With the @JvmStatic annotation. Means we're accessing the static version of the method.
        // This could be necessary if carComp() was accessing static properties
        // For named object instances i.e. Singletons:
        SingletonObj.INSTANCE.doSomething(); // We can go through the INSTANCE object to access the functions within. Compiler creates an instance and assigns INSTANCE to it
        SingletonObj2.doSomething2(); // If we annotate the function with @JvmStatic we can access it directly
        // Fields, if they're not private:
        System.out.println("is 4WD = " + Car2.Companion.is4WD()); // Without @JvmField
        System.out.println("is pimped out = " + Car2.isPimpedOut); // With @JvmField
        // If the field is a const you don't need to annotate it - automatically converted to static field:
        System.out.println("a constant = " + Car2.constant);
        // Also true for late initialised properties (whatever they are)

        // Null safety
        // Nothing stopping Java passing a null object to a nonNullable function parameter - Kotlin will generate an exception:
//        car2.printMe(null);
        // Kotlin doesn't require functions to declare what exceptions they can throw, whereas Java does.  What happens when we call an exception-able Kotlin function from Java?
//        CarKt.doIO(); // Throws IOException. Can we catch it?
//        try {
//            CarKt.doIO();
//        } catch (IOException e) { // Error - 'IOException is never thrown' - Java can't see the potential exception
//
//        }
        try {
            CarKt.doIO2();
        } catch (IOException e) { // @Throws annotation solves the problem
            System.out.println("IOException!");
        }
        // What happens when we've defined default parameters in a Kotlin function?
        CarKt.defaultArgs("The number is: ", 40);
//        CarKt.defaultArgs("The number is: "); // Error even though second argument should be optional
        // When a Kotlin function with default values is called, only one version is generated for Java (requires all parameters). Annotate with @JvmOverloads:
        CarKt.defaultArgs2("The number is: "); // All better
    }
}

 

fun topLevel() = println("I'm in the car file")

fun main() {
    "Print this".print() // Calling the extension function from within the Kotlin class, as standard
}

object SingletonObj {
    fun doSomething() = println("I'm doing something in the Singleton object")
}

object SingletonObj2 {
    @JvmStatic fun doSomething2() = println("I'm doing something in the second Singleton object")
}

fun doIO() {
    throw IOException()
}

@Throws(IOException::class)
fun doIO2() {
    throw IOException()
}

fun defaultArgs(str: String, num: Int = 25) {}

@JvmOverloads fun defaultArgs2(str: String, num: Int = 25) {}

class Car(val colour: String, var model: String, val year: Int) {

}

class Car2(colour: String, model: String, @JvmField val year: Int, val isAutomatic: Boolean) { // year property has been designated for direct access through Java

    companion object {
        const val constant = 25
        val is4WD = false
        @JvmField val isPimpedOut = true
        fun carComp() = println("I'm in car's companion object")
        @JvmStatic fun carComp2() = println("I'm also in car's companion object")
    }

    fun printMe(text: String) {
        println("I don't expect a null value: $text")
    }

    var colour: String = colour
    set(value) { // example custom setter which always produces same output
        field = "always green"
    }
    var model: String = model
    private set(value) {
        field = "Focus"
    }
}

fun String.print() { // Extension function
    println(this)
}

 

Calling Java from Kotlin – Interoperability

Things to consider when calling Java from Kotlin.

fun main() {
    val car = Car("silver", "Audi", "A3", "3.2 V6", "Leather", 2004)
    car.colour = "Akoya silver" // Can only refer to this like a property if getter and setters are set in Java. Will work without setter if property is public, but not usually desirable
    // Wildcard assignments in Java are converted to use-site variance in Kotlin. ? extends someClass -> out someClass, ? super someClass -> in someClass
    // If you call a Java method that throws an exception and you don't handle the exception in the code, you don't have to declare that the Kotlin function throws the exception - Kotlin doesn't care
    // varargs: If you want to call a Java method that wants multiple arguments:
    car.variableMethod(5, "hello", "goodbye") // This works
    val strings = arrayOf("hello", "goodbye")
//    car.variableMethod(10, strings) // But it doesn't allow us to pass in an array
    car.variableMethod(10, *strings) // So we need to use the spread operator to unpack the array
    // Kotlin doesn't have void, so it will return Unit
    // As previously discussed, when Java method expects primitive types we cannot use arrayOf. We need to use e.g. intArrayOf or call .toIntArray on the array:
//    car.wantsIntArray(arrayOf(1, 2, 3)) // Nope
    car.wantsIntArray(intArrayOf(1, 2, 3)) // Fine
    car.wantsIntArray(arrayOf(1, 2, 3).toIntArray()) // Fine
    // Any? is top of Kotlin hierarchy, Object is top of Java. What happens when Java calls Object explicitly? Will be treated like Any in Kotlin.
    // However, does not have methods such as wait(), notify(), clone() or finalize(). If wanting to call these cast the variable to java.lang.Object in your Kotlin code:
//    car.anObject.equals("bh") // Has equals() method and some others but missing the methods listed above
//    (car.anObject as java.lang.Object).notify() // All methods now available on casting
    // Static methods and fields are translated to companion objects. You can't pass the companion object around as a value but you can use its members
    println("x = ${Car.x}") // Accessing static variable
    println(Car.xString()) // The same as calling a companion object
    // Single Abstract Method (SAM) interface as of Java 8, can use lambda. E.g. Runnable interface is a SAM (see Car.java demoMethod and demoMethod2)
    // If a Java method wants an object that implements a SAM interface e.g. Runnable then you can pass it a Kotlin function:
    car.demoMethod3 ({ println("I'm in a Thread") }) // Function signature has to match parameter, in this case Runnable
}
package academy.learnprogamming.javainteroperability;


import org.jetbrains.annotations.NotNull; // N.B. Had to prompt for download from Maven
import org.jetbrains.annotations.Nullable;

public class Car {

    public static int x = 5;

    private String colour;
    private String manufacturer;
    private String model;
    private String variant;
    private String interior;
    private int year;
    private Object anObject;

    public Car(String colour, String manufacturer, String model, String variant, String interior, int year) {
        this.colour = colour;
        this.manufacturer = manufacturer;
        this.model = model;
        this.variant = variant;
        this.interior = interior;
        this.year = year;
    }

    public void demoMethod() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("I'm in a Thread");
            }
        }).start();
    }

    public void demoMethod2() {
        new Thread(() -> System.out.println("I'm in a Thread")).start();
    }

    public void demoMethod3(Runnable r) {
        new Thread(r).start();
    }

    public static String xString() {
        return "This is x: " + x++;
    }

    public String getColour() {
        return colour;
    }

    public void setColour(@NotNull String colour) { // @NotNull comes from JetBrains, not Java. Not enforced by Java.
        this.colour = colour;
    }

    public String getManufacturer() {
        return manufacturer;
    }

    public void setManufacturer(@Nullable String manufacturer) {
        this.manufacturer = manufacturer;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public String getVariant() {
        return variant;
    }

    public void setVariant(String variant) {
        this.variant = variant;
    }

    public @Nullable String getInterior() {
        return interior;
    }

    public void setInterior(String interior) {
        this.interior = interior;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public Object getAnObject() {
        return anObject;
    }

    public void setAnObject(Object anObject) {
        this.anObject = anObject;
    }

    public void variableMethod(int num, String... strings) {
        for (String string: strings) {
            System.out.println(string);
        }
    }

    public void wantsIntArray(int[] theArray) {
        for (int i: theArray) {
            System.out.println(i);
        }
    }

    @Override
    public String toString() {
        return "Car{" +
                "colour='" + colour + '\'' +
                ", manufacturer='" + manufacturer + '\'' +
                ", model='" + model + '\'' +
                ", variant='" + variant + '\'' +
                ", interior='" + interior + '\'' +
                ", year=" + year +
                '}';
    }
}

 

Java-Kotlin Interoperability – Nullability

How to deal with differences in nullability between Java and Kotlin.

fun main() {
    val car = Car("blue", "Ford", "Focus", "SE", "fabric", 2015) // Using Java class, no difference
    // Remember, there are some differences: Java class may require primitive type, so may need to cater for this; also differences in access modifiers
    // Nullability: Kotlin protects against nullability, Java does not. IntellliJ has @NotNull and @Nullable annotations. For documentation only, Java does not enforce, but at least IDE should push for compliance.
    // If Kotlin sees these annotations it will map @NotNull to non-nullable and @Nullable to nullable
    car.colour = "green"
    println(car)
//    car.colour = null // Will throw IllegalArgumentException at compile-time due to @NotNull annotation in Java class
    var manufacturer = car.manufacturer
    println(manufacturer.javaClass) // So is this String variable nullable?
    manufacturer = null // This would be fine with either no annotation or with @Nullable in Java class
    println(manufacturer) // But...
    var model: String = car.model // Non-nullable Kotlin variable declared. We can use this to protect against null values coming in from Java code
//    model = null // Not allowed!
    var variant: String? = car.variant // If we make this variable nullable
    println(variant?.javaClass) // String? is not a subtype of Any as there is no equivalent in Java, so need to add ? to variant
//    var interior: String = car.interior // Not allowed since return type of getInterior is nullable, but Kotlin variable is non-nullable
    // In Java, if a variable is not defined as either Nullable or notNull it is called a platform type. These can't be created in Kotlin.
    // If there is a platform type Kotlin compiler appends the ! symbol to the end of the type, so look out for these in error messages:
    val colour: Int = car.colour // If you hover over car.colour you will see the Found: String! error message - platform type
    // If Java values are not annotated you should perform the usual null checks in Kotlin code - compiler won't do that for you
}
package academy.learnprogamming.javainteroperability;


import org.jetbrains.annotations.NotNull; // N.B. Had to prompt for download from Maven
import org.jetbrains.annotations.Nullable;

public class Car {

    private String colour;
    private String manufacturer;
    private String model;
    private String variant;
    private String interior;
    private int year;

    public Car(String colour, String manufacturer, String model, String variant, String interior, int year) {
        this.colour = colour;
        this.manufacturer = manufacturer;
        this.model = model;
        this.variant = variant;
        this.interior = interior;
        this.year = year;
    }

    public String getColour() {
        return colour;
    }

    public void setColour(@NotNull String colour) { // @NotNull comes from JetBrains, not Java. Not enforced by Java.
        this.colour = colour;
    }

    public String getManufacturer() {
        return manufacturer;
    }

    public void setManufacturer(@Nullable String manufacturer) {
        this.manufacturer = manufacturer;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public String getVariant() {
        return variant;
    }

    public void setVariant(String variant) {
        this.variant = variant;
    }

    public @Nullable String getInterior() {
        return interior;
    }

    public void setInterior(String interior) {
        this.interior = interior;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    @Override
    public String toString() {
        return "Car{" +
                "colour='" + colour + '\'' +
                ", manufacturer='" + manufacturer + '\'' +
                ", model='" + model + '\'' +
                ", variant='" + variant + '\'' +
                ", interior='" + interior + '\'' +
                ", year=" + year +
                '}';
    }
}

 

Walking the file tree in Kotlin

fun main() {
    File(".").walkTopDown().forEach { println(it) } // Walk functions all return a sequence of files. This starts at the root directory and navigates to each branch, listing as it goes (directories before files).
    // Also walkBottomUp()
    println("======")
    // We can treat the output like a sequence and filter out the results we want:
    File(".").walkTopDown()
        .filter { it.name.endsWith(".kt") }
        .forEach { println(it) }
    // If you want to do something before or after entering the directory you can use the onEnter and onLeave
    // onEnter takes a lambda that returns a Boolean - if false, is skipped
}

 

Kotlin IO

File IO in Kotlin.

// Kotlin simply supplements the existing Java IO libraries with kotlin.io package
// Note text file in base directory of project <-
fun main() {
    File("testfile.txt").reader()
    // Abbreviates the Java declaration: new InputStreamReader(new FileInputStreamReader(new File("testfile.txt")), UTF-8")

    val lines = File("testfile.txt").reader().readLines() // DO NOT USE THIS FUNCTION FOR HUGE FILES. Reads entire file into memory.
    // Closes the Stream for us. Returns listOf<String>
    lines.forEach { println(it)}

    val reader2 = File("testfile.txt").reader()
    val lines2 = reader2.readText() // Returns one String which we would have to parse and close the Stream ourselves
    println("Lines2: $lines2") // Prints fine as text contains newlines
    reader2.close()
    println("======")

    val lines3 = File("testfile.txt").reader().use { it.readText() } // Closes the Reader itself - .use functions close the resource
    println("Lines3: $lines3")
    println("======")

    val lines4 = File("testfile.txt").bufferedReader().use { it.readText() } // BufferedReader
    println("Lines4: $lines4")
    println("======")

    val lines5 = File("testfile.txt").readText() // Version of readText which runs from the File class - limit of 2GB
    println("Lines5: $lines5")
    println("======")

    // If we don't want to read the whole file at once:
    File("testfile.txt").reader().forEachLine { println(it) } // Closes the resource for us
    println("======")

    // If we don't want to read the whole file at once:
    File("testfile.txt").reader().useLines { it.forEach { println(it) } } // useLines returns a reference so we need forEach. More verbose
    println("======")

    // Binary files - best done by using Java methods. No great enhancements in Kotlin.
    val di = DataInputStream(FileInputStream("testfile.bin"))
    var si: String

    try {
        while (true) {
            si = di.readUTF()
            println("Binary file: $si")
        }
    } catch (e: EOFException) {

    }

    // Writing files is no different to Java

    // Try with resources. To make sure a resource is released after using it use try...finally block, or better: try with resources. Kotlin method is to use one of the .use functions (see above)

}

 

Use-site variance

Use of covariance and contravariance within functions in Kotlin.

fun main() {

    val cars1 = mutableListOf(KotlinCar(), KotlinCar())
    val cars2: MutableList<KotlinCar> = mutableListOf()
    copyCars(cars1, cars2)

    val fords1 = mutableListOf(Ford(), Ford())
    val fords2: MutableList<Ford> = mutableListOf()
//    copyCars(fords1, fords2) // Wants a MutableList<KotlinCar>, as consistent with covariance/invariance. Once again we don't want to have to write a copyCars() function for every type of car.
    copyCars2(fords1, fords2) // Works because copyCars2 is covariant
//    copyCars2(fords1, cars2) // Doesn't initially work because in each case <T> is different (Ford and KotlinCar)
    val cars3: MutableList<KotlinCar> = mutableListOf(Ford(), Ford()) // Yet we should be able to as we can do this <-
    // If we look at our copyCars function we can see that we only read from 'source' and only write to 'destination', so we can use co- and contravariance
    copyCars3(fords1, cars1) // It works!
}

fun copyCars(source: MutableList<KotlinCar>, destination: MutableList<KotlinCar>) {
    for (car in source) {
        destination.add(car)
    }
}

fun <T> copyCars2(source: MutableList<T>, destination: MutableList<T>) {
    for (car in source) {
        destination.add(car)
    }
}

fun <T> copyCars3(source: MutableList<out T>, destination: MutableList<T>) { // Co- or contravariance declared in function - use-site variance, also known as type projection. Can also be used with function return types
// fun <T> copyCars3(source: MutableList<T>, destination: MutableList<in T>) { // This would also work
    for (car in source) {
        destination.add(car)
    }
}

open class KotlinCar {

}

class Toyota: KotlinCar() {

}

class Ford: KotlinCar() {

}

 

Contravariance

Understanding how contravariance affects the ability to use Generics in Kotlin.

fun main() {
    val roseTender = object: FlowerCare2<Rose2> {
        override fun prune(flower: Rose2) {
            println("I'm pruning a rose!")
        }
    }

    val daffodilTender = object: FlowerCare2<Daffodil2> {
        override fun prune(flower: Daffodil2) {
            println("I'm pruning a daffodil!")
        }
    }

    val roseGarden = Garden2(listOf(Rose2("Rosemary"), Rose2("Rosie")), roseTender)
    println(roseGarden.javaClass)

    val daffodilGarden = Garden2(listOf(Daffodil2(), Daffodil2(), Daffodil2()), daffodilTender)
    println(roseGarden.javaClass)

    roseGarden.tendFlower(0)

    daffodilGarden.tendFlower(2) // This is rather repetitive. We have to create Gardens and call tendFlower, but roseTender and daffodilTender are doing the same thing

    val flowerTender = object : FlowerCare2<Flower2> { // Create a generic flowerTender that can handle any type of Flower2
        override fun prune(flower: Flower2) {
            println("I'm tending a specifically-named ${flower.name}!")
        }
    }

    val roseGarden2 = Garden2(listOf(Rose2("Rosemary"), Rose2("Rosie")), flowerTender) // Won't work without adding 'in' to FlowerCare2 class, as expects FlowerCare2<Rose2>. We need <T> matching to be more relaxed.
    // Covariance preserves the subtyping. Contravariance is the opposite - starting with subclass and wanting to accept superclasses.
    // Comes at a price. We can only write them and not read from them. Cannot use them as return types from functions.

    roseGarden2.tendFlower(1)

}

class Garden2<T: Flower2>(val flowers: List<T>, val flowerCare: FlowerCare2<T>) {
    fun pickFlower(i: Int) = flowers[i]
    fun tendFlower(i: Int) {
        flowerCare.prune(flowers[i])
    }
}

open class Flower2(val name: String) {

}

class Rose2(val roseName: String): Flower2(roseName) {

}

class Daffodil2: Flower2("Daffodil") {

}

interface FlowerCare2<in T> { // Made contravariant by 'in' keyword. Will accept T or any superclass of.
  // As it is done in the declaration is known as 'declaration-site variance'. Java only has 'use-site variance' (see post of same name).
    fun prune(flower: T)
//    fun pick(): T // Cannot return this Generic as it is now contravariant
}

 

Covariance in Kotlin

Understanding how covariance affects the ability to use Generics in Kotlin.

fun main() {
    val shortList: List<Short> = listOf(1, 2, 3, 4, 5)
    convertToInt5(shortList) // All fine here

    val shortList2: MutableList<Short> = mutableListOf(1, 2, 3, 4, 5)
//    convertToInt6(shortList2) // Not allowed - type mismatch. But why? Surely Short is a sub-type (N.B. NOT a sub-class) of Number? And why did it work with an immutable List but not a mutable one?
    // List is a class, but List<String> is a type. Although we rationally would assume that it should accept a List<Short> if it accepts a List<Number>, it is not the same as a subclass.
    // So we want List<Short> to be a subtype of List<Number>. This is where the covariant keyword comes in - it preserves subtyping when working with Generics.
    // When looking at the Collections interface we can see that immutable Collections are covariant (<out E>) whereas mutable Collections are not - hence why the immutable List worked.

}

fun convertToInt5(collection: List<Number>) {
    for (num in collection) {
        println("${num.toInt()}")
    }
}

fun convertToInt6(collection: MutableList<Number>) {
    for (num in collection) {
        println("${num.toInt()}")
    }
}

fun tendGarden(roseGarden: Garden<Rose>) {
    waterGarden(roseGarden) // By default wants a garden<Flower> and we are passing it a Garden<Rose>, even though Rose is a subclass of Flower
    // This is because the Garden class is invariant, so will only accept Garden<Flower>. When we add 'out' to the Garden class this is resolved, but at a price:
    // We're now restricted and can only use the covariant class in the 'out' position, like an immutable Collection (can read but not add)
    // Function parameters are considered in the 'in' position (invariant) and the function return type is in the 'out' position (covariant)
    // Constructor parameters don't have 'in' or 'out' positions so you can always pass in a covariant class. No danger in this situation.
    // But if you pass in a var of type <T> to a constructor, it can't be covariant because setter needs to be generated. It would need to be invariant or declared as a val.
    // If you have a private function or property you don't need to worry about 'in' and 'out' - they're safe inside the class.
}

fun waterGarden(garden: Garden<Flower>) {

}

open class Flower {

}

class Rose: Flower() {

}

class Garden<out T: Flower> { // We can have a garden of daisies, daffodils etc. 'Out' keyword added to make it covariant.
  // As is done within the declaration is known as 'declaration-site variance'. Java only has 'use-site variance' (see post of same name).

    val flowers: List<T> = listOf()

    fun pickFlower(i: Int): T = flowers[i] // Pick the ith flower. Being overly verbose for readability
//    fun plantFlower(flower: T) {} // Not allowed as T is in 'out' position, not suitable as a parameter. This is to stop e.g. a daisy being planted in a rose garden.
    // This applies to member functions of the covariant class only.
}

// If you are ABSOLUTELY sure you will not pass an invalid type to your invariant class you can suppress the compiler warning by using the <@UnsafeVariance T> annotation.

 

Reified Parameters

Reify parameters to allow us to use Generics at runtime.

// Since Generics are erased at runtime then any Generic parameters that are passed to functions cannot determine which type it was invoked with
// Reification is ONLY useful when wanting to check the data type in a function
// Can't mark classes, properties or non-inline functions as reified
fun main() {

    val mixedList: List<Any> = listOf("string", 1, BigDecimal(22.5), "fall", BigDecimal(-349654.345))
    val bigDecimalsOnly = getElementsOfType2<BigDecimal>(mixedList) // Type in <> can be changed to whatever desired
    println(bigDecimalsOnly)
}

//fun <T> getElementsOfType(list: List<Any>): List<T> {
//    var newList: MutableList<T> = mutableListOf()
//    for (element in list) {
//        if (element is T) { // Will not work as T is erased at compile-time
//            newList.add(element)
//        }
//    }
//    return newList
//}

// This situation can benefit from inline functions as the parameters can be REIFIED - prevents the type from being erased at runtime:
inline fun <reified T> getElementsOfType2(list: List<Any>): List<T> { // Require both inline and reified declarations. By inlining the function the compiler can determine what type the parameter is and substitute it in at compile-time.
    var newList: MutableList<T> = mutableListOf()
    for (element in list) {
        if (element is T) { // Now works
            newList.add(element)
        }
    }
    return newList
}

 

Generics Functions in Kotlin

Using functions with Generics in Kotlin.

fun main() {

    val ints = listOf(1, 2, 3, 4, 5)
    val shorts: List<Short> = listOf(10, 20, 30, 40, 50)
    val floats: List<Float> = listOf(100.3f, -458.43f)
    val strings = listOf("1", "2", "3")

    val nullableShorts: List<Short?> = listOf(10, 20, 30, 40, 50)

    convertToInt2(ints)
    println("======")
    convertToInt2(shorts)
    println("======")
    convertToInt2(floats)
    println("======")
//    convertToInt2(strings) // Nope

    append(StringBuilder("String 1"), StringBuilder("String 2")) // StringBuilder conforms to both CharSequence and Appendable
    println("======")

    convertToInt(nullableShorts) // Will accept because the Generic T has no bounds and is therefore nullable
    println("======")
    convertToInt3(nullableShorts) // Works as convertToInt3() accepts nullable Numbers

}

fun <T> convertToInt(collection: Collection<T>) {
     for (num in collection) {
//         println("${num.toInt()}") // Will not accept this as there is no guarantee that what is being passed in is a Number
     }
}

// So we restrict the Generic type to a Number class to limit type options:
fun <T: Number> convertToInt2(collection: Collection<T>) {
    for (num in collection) {
         println("${num.toInt()}") // Works as the compiler knows it is getting a Number
    }
}

fun <T> append(item1: T, item2: T)
where T: CharSequence, T: Appendable { // Restricts to both CharSequence and Appendable. Can have multiple interfaces but only one class as an object cannot be e.g. an Int and Short at the same time
    println("Append result is ${item1.append(item2)}")
}

// What about nullability? Generic defaults to Any? type so is nullable. But if we specify a bound we need to state nullable if necessary

// So we restrict the Generic type to a Number class to limit type options:
fun <T: Number?> convertToInt3(collection: Collection<T>) { // Use ? to allow null
    for (num in collection) {
        println("${num?.toInt()}") // Remember to add nullable here too
    }
}

// If you want to limit the Collection to only non-nullable types:
fun <T: Any> convertToInt4(collection: Collection<T>) { // Use Any without the ? to specify a non-nullable of any type
    for (num in collection) {
        println(num)
    }
}

 

Generics

Using generics.

How we would use data type specifications in Java and their benefits:

public class JavaGenerics {
    public static void main(String[] args) {

        List<String> list = new ArrayList<>(); // By giving an indication of data type we have improved error-checking, need less casting and code is clearer

        list.add("Hello");
//        list.add(new BigDecimal(10.5)) // Error-checking picks up on the incompatibility
        list.get(0).toUpperCase(); // IDE suggests functions related to data type

        List list1 = new ArrayList<>(); // However, we can still create a Collection without specifying type

    }
}

The same in Kotlin, introducing Generics:

// Use generics to give the compiler a hint as to what type of objects you are dealing with, often related to Collections
// Allows compiler to do some error-checking at compile time
fun main() {

    val list: MutableList<String> // Kotlin insists on knowing the data type
    list = mutableListOf("Hello")
    list.add("another string")
    printCollection(list)
    list[0].toUpperCase() // We can use the relevant functions

    println("======")

    val bdList = mutableListOf(BigDecimal(-33.45), BigDecimal(3503.99), BigDecimal(0.329))
//    printCollection(bdList) // Won't allow printCollection because it expects String
    printGenericCollection(bdList) // Works because Generics are used

    println("======")

    bdList.printCollectionExtension() // Same result using extension function - IDE suggests

// At runtime the application has none of the Generic information - type erasure. It is simply a syntactical feature e.g. it cannot run 'instanceOf' on a Generic since that information does not exist at runtime.
// Also true of Kotlin since it uses the JVM. How do we use the 'is' operator (equivalent of instanceOf)?
    if (list is List<String>){} // This is allowed! How come, since Generics are lost at runtime? Suspect the function is handled at compile-time by Kotlin
    val listAny: Any = listOf("str1", "str2") // To check this theory...
//    if (listAny is List<String>) {} // This is invalid as the statement cannot be evaluated at compile-time
//    if (listAny is List) // We can't even do this since Kotlin expects a data type
    if (listAny is List<*>) {// So we do this instead - star projection syntax, sort of like wildcard
        println("So let's assume this List contains strings") // If it isn't a List of non-Strings this
        val strList = listAny as List<String> // Casting. This is how we use 'as' in conjunction with this. If it isn't a List of Strings it will throw a ClassCastException. Compiler will warn
        println(strList[1].replace("str", "string"))
    }
}


fun printCollection(collection: List<String>) {
    for (item in collection) {
        println(item)
    }
}

fun <T> printGenericCollection(collection: List<T>) { // The <T> after fun is the type parameter declaration and is necessary
    for (item in collection) {
        println(item)
    }
}

// Extension function example:
fun <T> List<T>.printCollectionExtension() {
    for (item in this) {
        println(item)
    }
}

 

 

Kotlin Collections – Sequences

Using Sequences – the equivalent to Java Streams.

// When chaining a e.g. filter and map together there will be intermediate Collections formed in the background. What if there are a large number of chained actions or the Collection is very large (or unknown size)?
// To avoid the creation of intermediate Collections we can use Sequences. Equivalent to Streams in Java, and aren't available on all platforms.
// Each element is evaluated and passed to next step in chain (if appropriate). No need for intermediates
// Only one function - iterator. Can convert a Collection to a Sequence but ONLY DO THIS FOR LARGE COLLECTIONS as Kotlin Collections are very efficient.
 fun main() {
    val immutableMap = mapOf(1 to Car3("green", "Toyota", 2015),
        2 to Car3("red", "Ford", 2016),
        3 to Car3("silver", "Honda", 2013),
        17 to Car3("red", "BMW", 2015),
        8 to Car3("green", "Ford", 2010)
    )

    // Two types of Sequence - intermediate (returns a Sequence to pass onto next operation) and terminal (terminates the chain)
    println(immutableMap.asSequence().filter { it.value.model == "Ford" }
        .map {it.value.colour}) // Intermediate - lazy i.e. not evaluated immediately. If you never reach terminal operation would be wasteful to evaluate intermediates

    listOf("Joe", "Mary", "Jane").asSequence()
        .filter { println("Filtering $it"); it[0] == 'J' } // Intermediate operation
        .map { println("Mapping $it"); it.toUpperCase() } // Intermediate operation. No return - need terminal operation

    val name = listOf("Joe", "Mary", "Jane").asSequence()
        .filter { println("Filtering $it"); it[0] == 'J' }
        .map { println("Mapping $it"); it.toUpperCase() }
        .toList() // Terminal operation - can now see results of operations
    println(name)

    // In some cases the desired result will be returned long before iterating the entire dataset, so many intermediate Collections could be avoided by using Sequences:
    println("======")
    val name1 = listOf("Joe", "Mary", "Jane").asSequence()
        .filter { println("Filtering $it"); it[0] == 'J' }
        .map { println("Mapping $it"); it.toUpperCase() }
        .find { it.endsWith('E') } // Operation completes without ever having to evaluate Mary and Jane
    println(name1)
    println("======")

    // With Collections, the order in which you call functions makes a difference. If we reverse the filter and map functions:
    val name2 = listOf("Joe", "Mary", "Jane")
        .map { println("Mapping $it"); it.toUpperCase() }
        .filter { println("Filtering $it"); it[0] == 'J' }
        .find { it.endsWith('E') } // All 3 names are mapped and then filtered - less efficient
    println(name2)
    println("======")

    // But with Sequences, the order in which you call functions may or may not make a difference. If we reverse the filter and map functions:
    val name3 = listOf("Joe", "Mary", "Jane").asSequence()
        .map { println("Mapping $it"); it.toUpperCase() }
        .filter { println("Filtering $it"); it[0] == 'J' }
        .find { it.endsWith('E') } // No difference
    println(name3)
    println("======")

    // But if we put Joe last:
    val name4 = listOf("Mary", "Jane", "Joe").asSequence()
        .map { println("Mapping $it"); it.toUpperCase() }
        .filter { println("Filtering $it"); it[0] == 'J' }
        .find { it.endsWith('E') } // Much less efficient
    println(name4)

    // Moral of the story: think out the order in which you run operations
}

 

Kotlin Collections – Useful Functions

Useful functions for Kotlin Collections.

fun main() {
    val setInts = setOf(10, 15, 19, 5, 3, -22)

    println(setInts.filter { it % 2 != 0 }) // Odd numbers only

    val immutableMap = mapOf(1 to Car3("green", "Toyota", 2015),
        2 to Car3("red", "Ford", 2016),
        3 to Car3("silver", "Honda", 2013),
        17 to Car3("red", "BMW", 2015),
        8 to Car3("green", "Ford", 2010)
    )

    println("2016 cars only: ${immutableMap.filter { it.value.year == 2016 }}") // 2016 cars only

    val mutableMap = mutableMapOf(1 to Car3("green", "Toyota", 2015), 2 to Car3("red", "Ford", 2016), 3 to Car3("silver", "Honda", 2013))

    mutableMap.filter { it.value.colour == "silver" }

    println(mutableMap) // Map hasn't changed by being filtered. Filter creates new instance of the map.

    // Add 10 to every element and create new
    val ints = arrayOf(1, 2, 3, 4, 5)
    val add10List: MutableList<Int> = mutableListOf()
    for (i in ints) {
        add10List.add(i + 10)
    }
    println("Add10List: ${add10List}")
    // The Kotlin way - using the map FUNCTION (not Collection)
    val add10List2 = ints.map { it + 10 }
    println("Add10List2: ${add10List2}")

    // Create a List that contains all the years of the Cars in our Map
    val carYearsList = immutableMap.map { it.value.year }
    println("Car years list: ${carYearsList}")

    // You can chain filter and map functions together:

    println("Ford cars only: ${immutableMap.filter { it.value.model == "Ford" }}") // Ford cars only

    println("Ford car colours: ${immutableMap.filter { it.value.model == "Ford" }.map { it.value.colour}}") // Ford car colours

    // If all the elements in a Collection match a condition:
    println(immutableMap.all { it.value.year > 2014 }) // Are all cars modern (post-2015)?
    println(immutableMap.any { it.value.year > 2014 }) // Are any cars modern (post-2015)?
    println(immutableMap.count { it.value.year > 2014 }) // How many cars are modern (post-2015)?
    // To find value that matches we can move them into a List and use the find function
    val cars = immutableMap.values
    println("First modern car: ${cars.find { it.year > 2014}}") // Finds first item in List that satisfies condition

    println("Cars grouped by colour: ${cars.groupBy { it.colour }}") // Group cars by colour
    println("Cars sorted by year: ${cars.sortedBy { it.year }}") // Sorted by year

    println("Sorted by key: ${immutableMap.toSortedMap()}") // Sorted by key

}

data class Car3(val colour: String, val model: String, val year: Int)

 

Kotlin Collections – Sets

Using Sets in Kotlin.

// Similar to Lists except no duplicate items
// setOf and immutableSetOf functions
fun main() {
    val setInts = setOf(10, 15, 19, 5, 3, -22)
    println(setInts.plus(20)) // Prints out set incl 20 but does not change Set as is immutable
    println(setInts.plus(10)) // Can't contain duplicates, so original Set printed
    println(setInts.minus(19))
    println(setInts.minus(100)) // Nothing happens as 100 not a value
    println(setInts.average()) // Result is Double
    println(setInts.drop(3)) // Drops the 1st 3 elements of the Set
    println("======")

    val mutableInts = mutableSetOf(1, 2, 3, 4, 5)
    mutableInts.plus(10) // Does not add an element, just returns a result, like minus function
    println(mutableInts)

}

 

Kotlin Collections – Maps

Using Maps in Kotlin.

fun main() {
    // Use 'to' to map the key to the value. Don't really need to state Type as is inferred by Kotlin
    val immutableMap = mapOf(1 to Car("green", "Toyota", 2015), 2 to Car("red", "Ford", 2016), 3 to Car("silver", "Honda", 2013))

    println(immutableMap.javaClass) // Returns java.util.LinkedHashMap
    println(immutableMap) // Returns keys and instance references as we haven't overwritten toString() or made a data class

    val mutableMap = mutableMapOf<String, Car>("John's car" to Car("red", "Range Rover", 2010), "Jane's car" to Car("blue", "Hyundai", 2012))
    println(mutableMap.javaClass) // Also returns java.util.LinkedHashMap. Kotlin prefers this as it has a predictable iteration order - allows easy conversion from List to Set to Map
    println(mutableMap)
    mutableMap.put("Mary's car", Car("red", "Corvette", 1965)) // Don't use the 'to' keyword when putting


    val mutableMap2 = hashMapOf<String, Car>("John's car" to Car("red", "Range Rover", 2010), "Jane's car" to Car("blue", "Hyundai", 2012)) // If you want a hashMap instead of a LinkedHashMap
    println(mutableMap2)
    mutableMap2.put("Mary's car", Car("red", "Corvette", 1965)) // Don't use the 'to' keyword when putting

    val pair = Pair(10, "ten") // Create a Pair
//    val firstValue = pair.first // Access 1st value
//    val secondValue = pair.second // Access 2nd value
    val (firstValue, secondValue) = pair // The Kotlin way - destructuring declaration. We've distributed the public values into different variables
    println(firstValue)
    println(secondValue)

    // If we want to iterate over the mutableMap:
    for ((k, v) in mutableMap) { // Destructuring declaration - only works in classes that have implemented component functions. To do this with custom class see next code block (Pair is a data class so has this included):
        println(k)
        println(v)
    }

    val car = Car("blue", "Corvette", 1959)
    val (colour, model, year) = car
    println("Colour: $colour Model: $model Year: $year")

    val car2 = Car2("blue", "Corvette", 1959)
    val (colour2, model2, year2) = car2
    println("Colour: $colour2 Model: $model2 Year: $year2")
}

class Car(val colour: String, val model: String, val year: Int) {

    operator fun component1() = colour // Implementing component functions. Only works if properties are public
    operator fun component2() = model
    operator fun component3() = year

}

data class Car2(val colour: String, val model: String, val year: Int) // No need to implement component functions as data classes already have them

 

Kotlin Collections – Lists

Using Lists in Kotlin.

// Kotlin often uses Java Collections, but even then you can do more with them
// Completely interoperable with Java
// You can operate on an immutable Collection (add, remove items), but the result will be a brand new instance
// All Collections take a generic type and all the read-only interfaces are covariant (e.g. you can assign a List of BigDecimal to a List of Any)
// If you look at the Collections declaration you will see it only has e.g. isEmpty() and contains(), and is covariant <out E>. MutableCollection adds add(), remove() etc and is not covariant <E>. Cannot assign mutable Collection of e.g. BigDecimal to Any.
// List and Set Collections. Array is considered a Collection but is in kotlin package, not kotlin/Collections. Doesn't implement any Collection interfaces.
fun main() {
    val strings = listOf("spring", "summer", "autumn", "winter") // This is wrong - produces List not ArrayList
    println(strings.javaClass) // returns java.utils.Arrays$ArrayList - can't add or remove anything but can change one of the elements (.set), therefore mutable.
    // Kotlin makes it immutable by not having any functions in the Kotlin List interface that can change the ArrayList
    // However, there are ways around this if the List is passed to Java code so need to be aware!

    val mutableSeasons = strings.toMutableList() // Creates a mutable List from an immmutable one
    mutableSeasons.add("Another season")
    println(mutableSeasons)

    val emptyList = emptyList<String>()
    println(emptyList.javaClass) // returns kotlin.collections.EmptyList. Not much you can do with this list so no need for Java class. Maybe you would return it where your function would usually return an immutable List.
//    println(emptyList[0]) // Still lets you call get() resulting in IndexOutOfBoundsException. Check if empty first if potentially receiving an empty List.

    val notnullList = listOfNotNull("hello", null, "goodbye") // You can use this technique to filter out any potential null results...
    println(notnullList) // ... returning a List of non-null values

    //We can get a standard Java ArrayList by specifically requesting it:
    val arrayList = arrayListOf(1, 2, 4)
    println(arrayList.javaClass) // Mutable List of class java.util.ArrayList

    val mutableList = mutableListOf<Int>(1, 2, 3)
    println(mutableList.javaClass) // Also mutable List of class java.util.ArrayList

    println(mutableList[2]) // We can use [] to get and set items
    mutableList[1] = 20
    println(mutableList)
    mutableList.set(0, 40) // Or we can use the getters and setters
    mutableList.get(2)

    val array = arrayOf("black", "white", "green")
    val colourlist = listOf(array) // Creates a List with one Array item
    println(colourlist) // We probably would have wanted an array of 3 elements, so:
    val actualColourList = listOf(*array)
    println(actualColourList)// or even easier...
    val actualColourList2 = array.toList()
    println(actualColourList2)

    val ints = intArrayOf(1, 2, 3)
    println(ints.toList()) // Converts a Kotlin Array to a primitive array for passing to Java

    // To manipulate Lists:
    println(strings.last()) // Get the last element
    println(strings.asReversed()) // Print them out backwards

    // The long way:
    if (strings.size > 5) {
        println(strings[5])
    }
    // The Kotlin way:
    println(strings.getOrNull(5)) // Will get element 5 or return null

    val ints1 = listOf(1, 2, 3, 4, 5)
    println(ints1.max()) // Gets largest value
    println(actualColourList.zip(strings)) // Only creates pairs for as long as pairs can be made

    val mergedLists = listOf(actualColourList, strings) // Creates a List of 2 Lists
    println(mergedLists)

    val combinedList = actualColourList + strings // Concatenates the 2 Lists
    println(combinedList)

    // To combine two lists and exclude duplicates:
    val strings2 = listOf("spring", "summer", "autumn", "summer", "winter")
    val colourList2 = listOf("black", "white", "red", "black", "red")

    val noDupsList = colourList2.union(strings2)
    println(noDupsList)

    // To remove duplicates without having to combine the List with anything else:
    val noDupColours = colourList2.distinct() // Returns a new list
    println(noDupColours)
}

 

Lambdas with Receivers

Lambdas with receivers in Kotlin.

fun main() {

    println(countTo100())
    println(countTo99().toString()) // Call toString() here, on the returned StringBuilder object

    val employee = KotlinEmployee2("Jim", "Johnson", 2011)
    val employees = listOf(KotlinEmployee2("John", "Smith", 2012), KotlinEmployee2("Jane", "Wilson", 2015), KotlinEmployee2("Mary", "Johnson", 2010), KotlinEmployee2("Mike", "Jones",2002))

    // Let's pretend we don't know about the find() function in Collections
    findByLastName(employees, "Wilson")
    findByLastName(employees, "Watson")
    findByFirstName(employees, "Jane")
    findByFirstName(employees, "Joan")

    with (employee.firstName) { // Regular usage of 'with' function
        println(capitalize())
    }

    myWith(employee.firstName) {// Demo: This is a high-order function. It passes a function into a function
        println(capitalize())
    // Every time this is called a lambda object is created. This could result in excessive memory consumption. To avoid this, make the myWith function inline

        println(employee.run { firstName }) // Another example of an extension lambda which works with all data types. Takes one lambda as argument and returns the result of executing the lambda. Sends in 'this' as an argument, but the 'this' can be excluded when calling e.g. 'name' instead of 'this.name'.

        DbConnection.getConnection().let { connection -> ..... } // Connection will no longer be available outside the block, limiting its scope.
// .let can also be used as an alternative to testing against null:
variable?.let { someFunction } // This block will be executed if 'variable' is not null. 'it' (variable) has now also been cast to not-null
// It is also particularly useful for chaining manipulations together:
        println((employee.let{it.firstName.capitalize()} // Capitalise first name
                         .let{it + " Snr"} // Concatenate some text
                         .let{it.length} // Get its length
                         .let{it + 31}) // And add 31. Returns 38

        println(employee.apply {}) // Another example of an extension lambda which works with all data types. Returns the object it's applied to rather than the result of the execution. Really useful for calling functions on a newly created object:
        val employee2 = KotlinEmployee2(name = "Jack").apply{ name = "James" } // Immediately call a function on a newly created object
        println(employee2.name)

    "Some String".apply somestring@{ // Add label so we can apply an operation to it
        "Another String".apply {
            println(toLowerCase()) // Operates on "Another String"
            // If we want to perform an operation on "Some String" use labels:
            println(this@somestring.toUpperCase())
        }
    }


}

// The long way
//fun countTo100(): String {
//    val numbers = StringBuilder()
//    for (i in 1..99) {
//        numbers.append(i)
//        numbers.append(",")
//    }
//    numbers.append(100)
//    return numbers.toString() // numbers variable is used a lot
//}

fun countTo100() = with(StringBuilder()) { // 'with' converts the instance you pass to it into a RECEIVER
        for (i in 1..99) {
            append(i) // We can exclude the instance reference
            append(",") // Could also type this.append but less concise
        }
        append(100)
        toString()
    }

Further explanation:

// EXTENSION LAMBDA - this is the equivalent of the built-in 'with' function:
fun myWith(name: String, block: String.() -> Unit) { // block (common convention name, can be anything) is the name of the function definition for the operation. We use this name in the body of the myWith function
    // String.() specifies the class we are extending (the Receiver object), so that we can use myWith on it. Unit is the return type of this particular function i.e. no return type
    // So block is now an extension function on a String object, and it can be applied to a String. We can now apply the passed-in function to the passed-in argument.
    name.block()
    // When we call the myWith function on fish.name (above), fish.name is the name argument and capitalize() is the block function
    // capitalize() returns a copy of the passed-in String, and does not change the original String. Wrapping it in a printLine shows us the result
}

// 'apply' does the same as 'with' but always returns the receiver object

fun countTo99() = StringBuilder().apply {
    for (i in 1..98) {
        append(i) // We can exclude the instance reference
        append(",") // Could also type this.append but less concise
    }
    append(99) // Don't call toString() here as the whole StringBuilder object is returned
} // You could add .toString() here as well

// The long way
fun findByLastName(employees: List<KotlinEmployee2>, lastName: String) {
    for (employee in employees) {
        if (employee.lastName == lastName) {
            println("We have an employee with last name $lastName")
            return
        }
    }
    println("Nobody here with the last name $lastName")
}

fun findByFirstName(employees: List<KotlinEmployee2>, firstName: String) {
    employees.forEach { // We can use a lambda as this is a functional interface
        if (it.firstName == firstName) {
            println("we have an employee with first name $firstName")
            return // Returns from both the lambda and the function i.e. a non-local return. This will only work when the function that is taking the lambda is inlined
            // If we want to make it a local return we can label the lambda: employees.forEach returnBlock@ {     followed by    return@returnBlock. In this case it would result in the "Nobody..." String being printed
            // This is useful when using nested 'with' or 'apply' statements (see "Some String" example in main function)
        }
    }
    println("Nobody here with first name $firstName")
}

data class KotlinEmployee2(val firstName: String, val lastName: String, val startYear: Int) { // Data class overrides our toString for a legible output

}

 

Lambdas in Kotlin

Using lambdas in Kotlin.

// Lambdas have to be inside curly braces and can be used as variables. Can be called directly through the 'run' library function

fun main() {
    run { println("I'm in a lambda") } // run function at work

    val employees = listOf(KotlinEmployee("John", "Smith", 2012), KotlinEmployee("Jane", "Wilson", 2015), KotlinEmployee("Mary", "Johnson", 2010), KotlinEmployee("Mike", "Jones",2002))
    println(employees.minBy { e -> e.startYear }) // minBy expects lambda. Each element is iterated through as e and the startYear compared. Returns employee with the earliest starting year
    println(employees.minBy { it.startYear }) // As there is only one argument we can use 'it'
    println(employees.minBy (KotlinEmployee::startYear)) // This is a member reference, and can be used instead of a lambda expression...
    // ...when only accessing a property or when it calls a top-level function that doesn't take any parameters (must be inside parentheses)
    run (::topLevel) // Calling top-level function member variable. No arguments allowed.
    //Lambdas can access local variables as long as they are declared before it. When you use a lambda within a function it can also access the function parameters:
    var num = 10 // Unlike Java (final local variables only) this can be accessed despite being mutable
    run {
        num += 15
        println(num)
    }

    useParameter(employees, 53) // Lambda accessing function parameter
}

data class KotlinEmployee(val firstName: String, val lastName: String, val startYear: Int) { // Data class overrides our toString for a legible output

}

fun topLevel() = println("I'm a function")

fun useParameter (employees: List<KotlinEmployee>, num: Int) {
    employees.forEach {
        println(it.firstName)
        println(num)
//        num++ // Cannot change function parameters as they are val. Can only access them
    }
}

 

Loops in Kotlin

Loops in Kotlin.

enum class Season {
    SPRING, SUMMER, AUTUMN, WINTER
}

fun main() {
    // for (int i = 0; i < 10; i++) does not exist in Kotlin. Use ranges instead:
    for (i in 1..20) {

    }

    val range = 1..5
    val charRange = 'a'..'z' // Valid as long as values are comparable
    val stringRange = "ABC".."XYZ" // Valid as long as values are comparable

    println(3 in range) // true
    println('q' in charRange) // true
    println("CCC" in stringRange) // true
    println("CCCCCC" in stringRange) // Still lower than XYZ
    println("ZZZZZZ" in stringRange)// false
    println(32 !in 1..10) // Not in the range
    println("=======")

    val backwardRange = 5.downTo(1)
    println(5 in backwardRange) // true

    val stepRange = 3..15 // Define initial range
    val step3 = stepRange.step(3) // Take the first and then every third value
    for (i in step3) println(i)
    println("=======")
    // Or
    for (i in 0..20 step 4) println(i)
    println("=======")

    val reverseStep = step3.reversed() // Only works for integers
    for (i in reverseStep) println(i)
    println("=======")
    // Or
    for (i in 20 downTo 15) println(i) // Decrement
    println("=======")
    for (i in 20 downTo 10 step 5) println(i) // Decrement in steps
    println("=======")
    for (i in 1 until 10) println(i) // Excludes last number in range
    println("=======")

    // To loop through a String range we need an iterator, which is included in the String class:
    val aString = "howdy"
    for (a in aString) println(a)
    println("=======")

    val seasons = arrayOf("Spring", "Summer", "Autumn", "Winter")
    for (season in seasons) println(season)
    println("=======")
    println("whatever" !in seasons)
    println("=======")
    val bString = "hello"
    println('e' in bString)
    println('e' !in bString)
    println("=======")

    // Long way
    for (index in seasons.indices) {
        println("${seasons[index]} is season number ${index + 1}")
    }
    println("=======")
    // Or
    seasons.forEach { println(it) } // <- Lambda expression
    seasons.forEachIndexed { index, value -> println("$value is season number $index")} // Equivalent to above expression
    println("=======")

    // We can name loops
    // Say we want to break out of both the j & k loops when k hits 7...
    for (i in 1..3) {
        println("i = $i")
        jloop@ for (j in 1..4) { // Label the loop
            println("j = $j")
            for (k in 5..10) {
                println("k = $k")
                if (k == 7) {
                    break@jloop // Call the @jloop. Also works with continue@jloop
                }
            }
        }
    }
    println("=======")
    // Can get out of hand easily so only use when necessary

    // if (expression) in Kotlin can evaluate to a value, unlike Java
    // Makes ternary operator ( String name = case.equals("uppercase") ? "JOHN" : "john"; ) obsolete
    val someCondition = 69 < 22
    var num: Int
    if (someCondition) { // No return
        num = 50
    } else {
        num = 592
    }
    // Or
    var num2 = if (someCondition) 50 else 592 // returns the int value. Must provide else if using an expression

    // When statement is enhanced switch statement
    val num3 = 200
    val y = 400
    when(num3) {
        100, 600 -> println("100") // If value is 100 or 600
        200 -> println("200")
        in 201..300 -> println("In range 201 to 300") // Ranges allowed
        y + 80 -> println("80 more than y") // Expressions allowed
        else -> "Didn't match anything"
    }

    val obj1: Any = "I'm a string"
    val obj2: Any = BigDecimal(25.2)
    val obj3: Any  = 45

    val something: Any = obj2
    if (something is String) println(something.toUpperCase()) // All of this
    else if (something is BigDecimal) println(something.remainder(BigDecimal(10.5))) // can be replaced
    else if (something is Int) println("${something - 22}") // by the following:

    when (something) {
        is String -> println(something.toUpperCase())
        is BigDecimal -> println(something.remainder(BigDecimal(10.5)))
        is Int -> println("${something - 22}")
    }
    // Can also evaluate to a value
    var z = when (something) {
        is String -> something.toUpperCase()
        is BigDecimal -> something.remainder(BigDecimal(10.5))
        is Int -> "${something - 22}"
        else -> "No idea what type it is"
    }
    // Use with enums
    val timeOfYear = Season.WINTER // See Season enum at top-level
    val str = when(timeOfYear) { // No else required when enum constrains the options
        Season.SPRING -> "Flowers are blooming"
        Season.SUMMER -> "Sun is shining"
        Season.AUTUMN -> "Leaves are falling"
        Season.WINTER -> "It's snowing"
    }
    println(str)
    // Can be used without a value - alternative to if..else:
    val num4 = -50
    val num5 = 25
    if (num4 < num5) {
        println("num4 is less than num5")
    } else if (num4 > num5) {
        println("num4 is greater than num5")
    } else {
        println("num4 = num5")
    }
    // Can be written as
    when {
        num4 < num5 -> println("num4 is less than num5")
        num4 > num5 -> println("num4 is greater than num5")
        else -> println("num4 = num5")
    }
}

 

Kotlin Try Catch

Using the Try/Catch statement in Kotlin.

fun main() {
    println(getNumber("22")) // returns 22
    println(getNumber("22.5")) // returns 0
    println(getNumber2("33") ?: "I can't print the result") // Use null return to determine output
    println(getNumber2("33.5") ?: IllegalArgumentException("Number isn't an Int")) // Use null return to throw a different Exception

    notImplementedYet("Yep")
}
// Don't have to declare the exceptions that Kotlin throws as it does not distinguish between checked and unchecked exceptions. Try/catch can be used as an expression.
fun getNumber(string: String): Int {
    return try {
        Integer.parseInt(string)
    }
    catch (e: NumberFormatException) {
        0
    }
    finally {
        println("I'm in the finally block") // Will print regardless of outcome. Prints before the result as it gets executed and then the function returns the result.
        1 // Does not feature as the finally block is not involved in the execution of the function
    }
}
//If you want to return null instead of throwing an exception:
fun getNumber2(string: String): Int? {
    return try {
        Integer.parseInt(string)
    }
    catch (e: NumberFormatException) {
        null
    }
    finally {
        println("I'm in the finally block")
    }
}
// A developer might implement a function that will always throw an Exception since it has not been completed yet - as a reminder to themselves and other developers that code is incomplete:
fun notImplementedYet (something: String): Nothing { // Makes it clear to developers that this code will never return a value
    throw IllegalArgumentException("Implement me!")
}

 

Imports in Kotlin

From within the same module:

package academy.learnprogramming.inheritance.inheritance // We can change this, but suggest sticking to directory structure

// Packages don't have to match the directory structure, but best to
// We import classes and interfaces as we would in Java,  but what about top-level functions?
fun topLevel(str: String) = println("Top-level function: $str") // This top-level function is called from a file in a different package. If auto-import is turned on it will detect this function and import for us
package academy.learnprogramming.inheritance.academy.learnprogramming.anotherpackage

import academy.learnprogramming.inheritance.inheritance.topLevel // Import statement produced by IDE

// enums can be imported as a class or individual enum:
import academy.learnprogramming.inheritance.inheritance.Department.* // class, or
import academy.learnprogramming.inheritance.inheritance.Department.IT // enum

// If importing from multiple sources and two have the same name you can import 'as':
import academy.learnprogramming.inheritance.inheritance.someImport as anotherImportName // Use this reference when calling. Does not work with the * wildcard

fun main() {
    topLevel("A string") // IDE suggests this function and imports it as auto-import is turned on
    println(IT.getDeptInfo())
}
// Object declarations can accessed similarly
// If trying to access class functions you will need to import the entire class
// typealiases and extension functions are imported the same way, by name

To add another module you must use the IDE. Right-click on the module and select Open Module Settings (F4). Make sure the correct module is selected and use the ‘+’ to add Module Dependency:

You can then use the Alt-Enter/Cmd-Enter prompt to auto-import the function you have typed in, or the IDE will suggest the function name if you start typing it.

Kotlin Enums

Enums in Kotlin.

package academy.learnprogramming.inheritance.inheritance

fun main() {
    println(Department.ACCOUNTING.getDeptInfo())
}

enum class Department (val fullName: String, val numEmployees: Int){ // In Kotlin we declare enums as a class
    HR("Human Resources", 5), IT("Information Technology", 10), ACCOUNTING("Accounting", 3), SALES("Sales", 20); // Need to close the list with ;

    fun getDeptInfo() = "The $fullName department has $numEmployees employees"
}

 

Kotlin object keyword – Singletons, companion objects and object expressions

Use of the object keyword in Kotlin.

fun main() {
    println(CompanyCommunications.getTagLine()) // Call the class name as there is only one instance
    println(CompanyCommunications.getCopyRightLine())

    SomeOtherClass().printPrivateVar() // Will allow us access to privateVar despite being private, thanks to Companion object
    SomeClass2.SomeCompanion.accessPrivateVar() // Using companion name, but again redundant

    val someClass = SomeClass4.justAssign("this is the string as is")
    val someClass2 = SomeClass4.upperOrLowerCase("this is the string as is", false)
    println(someClass.someString)
    println(someClass2.someString)
//    val someClass3 = SomeClass4("This will not work") // Constructor is private so cannot instantiate directly

    var thisIsMutable = 45

    wantsSomeInterface(object: SomeInterface { // Uses object to create an instance of the someInterface interface - NOT a Singleton. Is generated and then destroyed. Can use multiple interfaces, unlike Java
        override fun mustImplement(num: Int): String {
            return "This is from mustImplement: ${num * 100 + thisIsMutable}" // This expression can access variables from outside it (val or var, doesn't need to be final), unlike Java
        }
    })
}

// 'object' keyword's 3 uses: Singletons, companion objects, object expressions

// Singletons
// //In Java, you would create a private constructor that allows only one instance which other classes can access
// In Kotlin, there can only be one instance of an 'object' class, so we use that:
object CompanyCommunications { // No class keyword needed. Also no constructors as instance is created instantaneously
    val currentYear = Year.now().value
    fun getTagLine() = "Our company rocks!"
    fun getCopyRightLine() = "Copyright \u00A9 $currentYear Our Company. All rights reserved."
}

// Companion Objects
// No static keyword in Kotlin - instead we can use top-level functions and properties, and Object declarations (above)
// But what if you want an equivalent to Java static (see Java objects below), where you can access functions and properties without creating an instance of the class?
// We create companion objects inside the class which can be accessed without having to generate an instance of the class
class SomeClass {

    companion object {
        private var privateVar = 6 // Because is private no getter/setter generated

        fun accessPrivateVar() {
            println("I'm accessing privateVar: $privateVar")
        }
    }

}

class SomeClass2 {

    companion object SomeCompanion { // You can also name your companion object, but little use
        private var privateVar = 6

        fun accessPrivateVar() {
            println("I'm accessing privateVar: $privateVar")
        }
    }

}

class SomeOtherClass() {
    fun printPrivateVar () {
        println(SomeClass.accessPrivateVar()) // Could use SomeClass.Companion.accessPrivateVar() but redundant
    }
}

class SomeClass3 {

    val someString: String

    constructor(str: String) { // First secondary constructor
        someString = str
    }

    constructor(str: String, lowerCase: Boolean) { // Second secondary constructor
        if (lowerCase) {
            someString = str.toLowerCase()
        } else {
            someString = str.toUpperCase()
        }
    }
// But instead of doing it this way we can create a companion class that returns an instance of the class (see below)
}

class SomeClass4 private constructor(val someString: String){ // We want to implement the Factory pattern here. This class should generate instances of the class - nobody should be able to do it directly as in SomeClass3, so primary is made private

    companion object{
        private var privateVar = 6

        fun accessPrivateVar() {
            println("I'm accessing privateVar in SomeClass4: $privateVar")
        }

        fun justAssign(str: String) = SomeClass4(str) // Can only call the private constructor from inside class therefore controlling how class is instantiated...
        fun upperOrLowerCase(str: String, lowerCase: Boolean): SomeClass4 { // so to get an instance of this class you have to go through either the justAssign or upperOrLowerCase function
            if (lowerCase) {
                return SomeClass4(str.toLowerCase())
            } else {
                return SomeClass4(str.toUpperCase())
            }
        }

    }

}

// Object expressions - equivalent to an anonymous object in Java
interface SomeInterface {
    fun mustImplement(num: Int): String
}

fun wantsSomeInterface(si: SomeInterface) {
    println("Printing from wantsSomeInterface - ${si.mustImplement(22)}")
}

Companion object equivalent in Java

public class SomeClass {

    private static int privateVar = 6;

    public static void main(String[] args) {
        new SomeOtherClass().someOtherMethod();
    }

    public static void accessPrivateVar() {
        System.out.println("I'm accessing privateVar: " + privateVar);
    }
}
public class SomeOtherClass {

    public void someOtherMethod() {
        SomeClass.accessPrivateVar();
    }
}

 

Kotlin Interfaces

Interfaces in Kotlin.

fun main() {
    println(InheritingClass("A name").number2)
}

// Very similar in implementation to Java

interface MyInterface { // No primary constructor () as you never make an instance. Open not required, as extendable by default
    val number: Int // Can include properties in interfaces. This variable is currently abstract
//    val number2: Int = 50 // Initialising properties not allowed in interfaces. Instead:
    val number2: Int
        get() = 50 // Declare getter to return fixed value. However, no 'field' available here.
    fun myFunction(str: String): String
}

interface MySubInterface: MyInterface {
    fun mySubfunction(num: Int): String
}

class anotherSomething(override val number: Int) : MySubInterface { // Implement abstract property
    override fun myFunction(str: String): String {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    override fun mySubfunction(num: Int): String {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

}

// A class can extend from another class and implement and interface at the same time:
open abstract class AbstractClass(val name: String) {
    open fun printName() = println("The name is $name")
    abstract fun aNumber(): Double
}

class InheritingClass(name: String): AbstractClass(name), MyInterface { // both inherits and implements interface
    override val number: Int = 5 // Implement abstract property
    override fun myFunction(str: String): String {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    override fun aNumber(): Double {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }
}

 

Kotlin Inheritance

Inheritance in Kotlin.

fun main() {
    val laserPrinter = LaserPrinter("Brother 1234", 15)
    laserPrinter.printModel()
    SomethingElse("Hey")
}

open abstract class Printer(val modelName: String) { // Must be declared open as everything is final by default, therefore not extendable. Abstract in this case, which makes 'open' declaration redundant. () added to create primary constructor
    open fun printModel() = println("The model of this printer is $modelName") // Must be open to be overridden
    abstract fun bestSellingPrice(): Double // Does not need to be declared open as is abstract
}

open class LaserPrinter(modelName: String, ppm: Int): Printer(modelName) { // To extend a class in Kotlin use the : annotation. Requires Printer to be initialised as Printer has constructor by default, so () added after both LaserPrinter and Printer
    // We do not have to match constructor signatures (see ppm added in LaserPrinter not present in Printer)
//    constructor(): super() // How to initialise the Printer class if we don't want a primary constructor in Printer i.e. it doesn't have () in the first line
    final override fun printModel() = println("The model of this laser printer is $modelName") // Overrides the super implementation. Final has been added to prevent further overriding

    override fun bestSellingPrice(): Double = 129.99 // Pretend this is an actual function that does something. Still need to use override for abstract functions
    // When a function is overridden it is declared open, therefore can be overridden by subclasses. If not desirable,

}

class specialLaserPrinter(modelName: String, ppm: Int): LaserPrinter(modelName, ppm) { // LaserPrinter needs to be open to subclass
    override fun bestSellingPrice(): Double { // bestSellingPrice can be overridden
        return super.bestSellingPrice() + 5
    }
//    override fun printModel() // Won't work because printModel() is final in LaserPrinter
}


// Secondary constructors (which can take a function where the primary cannot) in inherited classes only make sense if no primary constructor is called, as so:
open class Something { // No primary constructor
    val someProperty: String

    constructor(someParameter: String) {
        someProperty = someParameter
        println("I'm in the parent's constructor")
    }
}

class SomethingElse: Something { // No primary constructor

    constructor(someOtherParameter: String): super(someOtherParameter) {
        println("I'm in the child's constructor")
    }
}

//open data class DataClass(val number: Int) {} // Data classes are closed-typed cannot extended, abstract or inner classes. Can inherit FROM other classes, but stops there.

 

Inline Functions

The inline function’s body is substituted for the function call on compilation. Often done when a function has a lambda expression to avoid the formation of a class and object in the background. – reduces overhead. Also beneficial when using Generics as it allows us to reify parameters. Not all functions are able to be inlined – depends on how function parameters are being used.

inline fun anyOldFunction() { // Add inline to the start of the declaration
}

 

Extension Functions

Add functions to existing classes – allows you to extend any class. Only means adding functions, not subclassing. Many Kotlin functions are extensions of the Java classes.

val A_CONSTANT = "I'm a constant"

fun main() {    
    println(Utils().upperFirstAndLast("this is all in lowercase")) // This is how you would typically provide a utility function in Java, in its own class (Utils.kt)
    val aString = "this is also in lowercase"
    println(aString.upperFirstAndLast()) // Uses extension function defined below in the file. Gives illusion of being part of the String class
}

fun String.upperFirstAndLast(): String { // Extension class, adjoins itself to the String class, does not require parameter as it will be called on a String
    val upperFirst = this.substring(0,1).toUpperCase() + this.substring(1) // This code upperCases the first and last characters. Note the use of 'this' to refer to the input String. Can be excluded in this case
    return upperFirst.substring(0, upperFirst.length - 1) +
            upperFirst.substring(upperFirst.length - 1, upperFirst.length).toUpperCase()
A_CONSTANT // Can also access any publicly available variables
}

 

Kotlin Functions

Using functions in Kotlin.

package academy.learnprogramming.datatypes

fun main() { // Default return type for a function is Unit, does not need to be specified. Equivalent to Void
    println(labelMultiply(3,4,"The result is:"))
    println(labelMultiply2(3,4,"The result is:"))
    println(labelMultiply3(3,4)) // Default label defined in function
    println(labelMultiply3(operand2 = 3, operand1 = 4)) // We can specify the parameters in any order if we label them

    val emp = testEmployee("Jane")
    println(emp.upperCaseFirstName()) // This is how we call functions from within a class

    // Can have functions that take an arbitrary number of arguments:
    val car1 = testCar("blue", "Toyota", 2015)
    val car2 = testCar("red", "Ford", 2016)
    val car3 = testCar("gray", "Ford", 2017)
    printColours("The colour is:", car1, car2, car3)
//    printColours2(car1, car2, car3, "The colour is:") // Won't work as varargs is not last in parameter list
    printColours2(car1, car2, car3, label = "THE COLOUR IS:") // Works

    var manyCars = arrayOf(car1, car2, car3)
//    printColours3(manyCars) // Won't work - expects testCar, gets Array<testCar>. Use spread operator instead:
    printColours3(*manyCars) // * is spread operator, means the function will accept many values:
    var manyCars2 = arrayOf(car2, car3)
    println("-----------")
    printColours3(*manyCars, *manyCars2, car2)


}

fun labelMultiply (operand1: Int, operand2: Int, label: String): String {
    return ("$label ${operand1 * operand2}")
}
// This class has a block body

// Because Kotlin this can be reduced to:
fun labelMultiply2 (operand1: Int, operand2: Int, label: String) = "$label ${operand1 * operand2}" // If single operation we can exclude 'return'. Compiler infers type
// This class has an expression body

fun labelMultiply3 (operand1: Int, operand2: Int, label: String = "This is the result:"): String { // We can use defaults in function parameters. However, cannot infer type so need :String
    return ("$label ${operand1 * operand2}")
}

fun printColours(label: String, vararg cars: testCar) { // Only one set of varargs allowed per function. Easiest if listed last
    for (car in cars) {
        println("$label ${car.colour}")
    }
}

fun printColours2(vararg cars: testCar, label: String) { // In this case the varargs aren't listed last so arguments must be labelled at instantiation (see above)
    for (car in cars) {
        println("$label ${car.colour}")
    }
}

fun printColours3(vararg cars: testCar) { // To accept array of testCars
    for (car in cars) {
        println(car.colour)
    }
}

data class testCar(val colour: String, val model: String, val year: Int)

class testEmployee (val firstName: String) {
    fun upperCaseFirstName() = firstName.toUpperCase()
}

 

Data class

Kotlin Data class.

fun main() {

    val emp = Employee("John", true) // Regular class instantiation
    println(emp)
    val car = Car("blue", "Toyota", 2015) // Data class instantiation
    println(car) // Improved toString(), shows all labels and values, instead of reference

    val emp2 = Employee("John", true)
    println(emp == emp2) // false as are referentially inequal

    val car2 = Car("blue", "Toyota", 2015)
    println(car == car2) // Compares structurally instead of referentially. Uses equals() function

    val car3 = car.copy() // Makes a structurally identical object
    println(car3)
    val car4 = car.copy(model = "Audi") // Copy with variations
    println(car4)
    val car5 = car.copy("Silver", "Audi", 2004) // Copy with variations. Can exclude identifier if changing all, but then why would you copy?
    println(car5)
}


// Main purpose of data class is to store state
// Data classes have improved toString() function, custom implementation of the equals() and hashcode() functions and have copy() function. All can be overridden
// Have all that is needed for a destructuring declaration
// Cannot be abstract, sealed or inner classes
data class Car(val colour: String, val model: String, val year: Int) { // Must have at least one parameter in constructor, all must have val or var i.e. must be declared in constructor
    val notMe: String = "nope" // will not benefit from improved functions as not declared in constructor
}

//Regular class for comparison
class Employee (val firstName: String, val fullTime: Boolean)

 

Kotlin Access Modifiers & Constructors

Kotlin Access Modifiers & Constructors – also see Java comparison of top-level modifiers

fun main() {
    // Public by default, also private, protected and internal
    // Can't use protected at the top level
    // Can have multiple (top-level) classes in one file, unlike Java. Hence you can have private classes which are only accessible from the same file
    val person  = Person()
    println(person)
    // Internal means accessible from within the same module. You can have more than one module in a project.

    // In Kotlin, classes can't see private members belonging to inner classes
    // When compiling, the JVM compiles private -> package-private, internal -> public, but Kotlin enforces its visibility rules so it won't be broken
    // However, when mixing Java and Kotlin may result in Java being able to access stuff in Kotlin it shouldn't e.g. internal should only access in same module, but ANY Java code can access it as is compiled to public. Look out for this - typically the name will be very long and ugly when trying to access from Java. Probably shouldn't be doing!

    // All classes are public and final by default. Have constructor created by default.

    val emp = Employee("John")
    println(emp.firstName)

    val emp1 = Employee1("Jack")
    println(emp1.firstName)

    val emp2 = Employee2("Jane", false)
    println("${emp2.firstName} ${emp2.fullTime}")
    val emp2x = Employee2("Jill") // Default fullTime applied
    println("${emp2x.firstName} ${emp2x.fullTime}")

    val emp2a = Employee2a("Jane", false)
    println("${emp2a.firstName} ${emp2a.fullTime}")
    val emp2ax = Employee2a("Jill") // Default fullTime applied
    println("${emp2ax.firstName} ${emp2ax.fullTime}")

    emp2ax.fullTime = false // Demonstrate setter
//    emp2x.firstName = "Joan" // Not allowed as firstName is a val

    println(Demo().dummy)

    val emp3 = Employee3("Jim", false)
//    println(emp3.firstName) // Invalid. firstName is private, so how do we get or set? In Java we could allocate getters and setters and would allow us access. In Kotlin, default getters and setters are blocked in private - can't be done.
    // Therefore no point in designating properties private if needing to be changed. Fine to have public properties - members outside will not be able to change them.

    val emp4 = Employee4("Jill") // Default fullTime applied
    emp4.fullTime // Calls custom getter
    emp4.fullTime = false // Calls custom setter


}

private class Person {

}

// This is equivalent to the Employee Java class in this module. **The long way**
class Employee constructor(firstName: String){ // Declare constructor on same line as class - this is the primary constructor.

    val firstName: String // Declaration

    init { // NOT a constructor. This block runs when the class is first instantiated. Can have multiple, run in order.
        this.firstName = firstName
    }

}

//The Kotlin way
class Employee1 (val firstName: String) // Declaration inline with constructor
// If you wanted to declare access modifier for constructor, you would need 'constructor' keyword:
// class Employee2 protected constructor(val firstName: String)

// This is equivalent to the Employee2 Java class in this module. **The long way**
class Employee2 (val firstName: String) {
    var fullTime: Boolean = true // Cannot be null. Only need this if using the variable outside the constructor body
    constructor(firstName: String, fullTime: Boolean): this(firstName){
        this.fullTime = fullTime
    } // Secondary constructor. Calls the primary and feeds in firstName. Cannot declare properties in secondary constructor
}

//The Kotlin way
class Employee2a (val firstName: String, var fullTime: Boolean = true) // No need for multiple constructors as default is declared

//Demo of class without primary constructor, but with secondary. Because you can run expression in secondaries but not primaries
class Demo {
    val dummy: String

    constructor() {
        dummy = "hello"
    }
}

// Up until now all properties have been (default) public. Here is an Employee class with private properties:
class Employee3 (private val firstName: String, private var fullTime: Boolean = true) // Cannot access e.g. emp.firstName. Reason it worked before is Kotlin creates default getters and setters (see equivalent Employee3 Java class). Likely no need to do this as all properties are public & final by default and have to go through the getters and setters to access them.

// To customise the getters/setters (accessors) you cannot declare the property within the primary constructor, but in the class:
class  Employee4 (val firstName: String, fullTime: Boolean = true) { // Not declaring the property, just using it
    var fullTime = fullTime // Must declare getter/setter (accessor) immediately after declaring the property
    get() {
        println("We are getting fullTime")
        return field // The only time we ever use the 'field' identifier - refers to a backing field in custom accessor
    }
    set(value) {
     println("Running the custom set")
        field = value
    }
}

 

 

 

public class JavaEmployee {

    public final String firstName;

    public JavaEmployee (String firstName) {
        this.firstName = firstName;
    }
}

 

public class JavaEmployee2 {

    public final String firstName;
    private final boolean fullTime;

    public JavaEmployee2(String firstName) { // Alternative constructor which applies default fullTime
        this.firstName = firstName;
        this.fullTime = true;
    }

    public JavaEmployee2(String firstName, boolean fullTime) {
        this.firstName = firstName;
        this.fullTime = fullTime;
    }
}

 

public class JavaEmployee3 {

    public String firstName;
    private boolean fullTime;

    public JavaEmployee3(String firstName) { // Alternative constructor which applies default fullTime
        this.firstName = firstName;
        this.fullTime = true;
    }

    public JavaEmployee3(String firstName, boolean fullTime) {
        this.firstName = firstName;
        this.fullTime = fullTime;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public boolean isFullTime() {
        return fullTime;
    }

    public void setFullTime(boolean fullTime) {
        this.fullTime = fullTime;
    }
}

 

Kotlin Arrays

Kotlin’s handling of arrays

fun main() {
    val names = arrayOf("John", "Jane", "Jill", "Joe") // Assumes Strings

    val longs1 = arrayOf(1L, 2L, 3L) // State Long by use of L
    val longs2 = arrayOf<Long>(1, 2, 3, 4) // Or by declaring datatype in arrayOf<>
    val longs3 = arrayOf(1, 2, 3, 4) // Otherwise will assume Ints

    println(longs2 is Array<Long>)
//    println(longs3 is Array<Long>) // Will fail because is Int
    println(longs3 is Array<Int>)

    println(longs1[2])

    val evenNumbers = Array(16) {i -> i*2} // Use lambda expression to get even numbers up to 30
    for (number in evenNumbers) {
        println(number)
    }

    val lotsOfNumbers = Array(100001) {i -> i} // Numbers from 0 to 100,000
    val allZeroes = Array(100) {0} // One hundred zeroes

    var someArray: Array<Int> // If you don't want to initialise it till later
    someArray = arrayOf(1, 2, 3, 4)
    for (number in someArray) {
        println(number)
    }

    someArray = Array(6) {i -> (i + 1) * 10}
    for (number in someArray) {
        println(number)
    }

    val mixedArray = arrayOf("hello", 22, BigDecimal(10.5), 'a') // Can have array of mixed datatypes i.e. Any
    for (element in mixedArray) {
        println(element)
    }

    val myIntArray = arrayOf(3, 99, 234, 54)
//    DummyClass().printNumbers(myIntArray) // Fails because it expects IntArray but found Array<Int>
    val myIntArray2 = intArrayOf(3, 99, 45, 756) // Use specific Array types
    DummyClass().printNumbers(myIntArray2) // Works!

//    var someOtherArray = Array<Int>(5) // Fails. By declaring the size (5) we are actually trying to instantiate the array, so we would need to add values
    var someOtherArray: Array<Int> // This is okay
    var anotherArray = IntArray(5) // However, primitive type arrays allow this as they're initialised to default, in this case all zeroes
    DummyClass().printNumbers(anotherArray)

    // So can you instantiate an array of fixed size? An array of nulls. Potentially risky as could lead to NullPointerExceptions, so you have to explicitly ask for them.
    // Not a problem with primitive arrays as they are initialised with defaults e.g. zeroes for IntArray
    // However, array of Objects is potentially null, so we use ArrayOfNulls to warn compiler
    val nullableInts = arrayOfNulls<Int>(5)
    for (i in nullableInts) {
        println(i)
    }
    // Try nullableInts[0]. and see how few options are available
    nullableInts[3] = 5
    val intValue = nullableInts[3]?.rem(2) // Now options are available provided you use ?


    // If you want pass an Array<Int> to Java you can convert it:
    DummyClass().printNumbers(evenNumbers.toIntArray()) // evenNumbers was an Array<Int>
    // And vice versa
    val convertedIntArray = myIntArray2.toTypedArray() // Converts a primitive IntArray to Array<Int>
}
public class DummyClass {

    public String isVacationTime (boolean onVacation) {
        return onVacation ? "I'm on vacation" : "I'm working";
    }

    public void printNumbers (int[] numbers) {
        for (int number: numbers) {
            System.out.println(number);
        }
    }
}

 

Datatypes

Datatype rules for Kotlin.

fun main() {
    val myInt = 10
    println("default datatype is ${myInt is Int}")
    var myLong = 22L
    // Every datatype has .toXxxx() functions to convert to another type. In Kotlin, you cannot declare myLong = myInt and expect it to expand the data type.
    myLong = myInt.toLong()

    val myByte: Byte = 111
    var myShort: Short
    myShort = myByte.toShort()

    var myDouble = 65.984 // Defaults to a Double
    println(myDouble is Double) // As proof

    val myFloat = 838.9823f // f declares it a Float
    println(myFloat is Float) // As proof

//    myDouble = myFloat // Will fail - no expansion
    myDouble = myFloat.toDouble()

    val char = 'b' // Defaults to Char
//    char myChar = 65 // In Java would call the char of value 65 = 'A'
    val myChar = 65 // In Kotlin, obviously produces a very different result!
//    val myChar2: Char = 65 // Fails, does not conform to data type
    val myCharInt = 65
    println(myCharInt.toChar()) // => A

    // When calling Java from Kotlin, how do we provide primitive types since they don't exist in Kotlin?
    val vacationTime = false
    val onVacation = DummyClass().isVacationTime(vacationTime)
    println(onVacation) // Works because the Kotlin datatype classes compile down to primitives. Also works if Java is expecting Boolean due to Java boxing.

    // Any, Unit, Nothing classes
    // Any is Kotlin root class
    val anything: Any
    // Unit class is equivalent to void. A Singleton instance of Unit is returned instead of nothing
    // Nothing class is subclass of Any class. Could use when e.g. you know something will never return
}

 

Kotlin declarations

Declaration rules for Kotlin.

typealias EmployeeSet = Set<Employee> // Declare at top of class

val MY_CONSTANT = 100 // Constants can be declared at the top-level in Kotlin

fun main() {
    val number: Short = 25 // Force a Float as the default would be Int
//    number = "heya" // Cannot change variable type

    // USE VAL UNLESS YOU NEED VARIABLE TO BE REASSIGNED

    val number2: Int // val can be declared without be initialised
    number2 = 25 // so now it's happy
//    number2 = 22 // val cannot be reassigned once initialised

    var number3: Int // var can be declared without be initialised
    number3 = 25
    number3 = 22 // var can be reassigned once initialised

    val employee1 = Employee("Lynn Jones", 500) // employee1 is fixed, cannot be reassigned
    employee1.name = "Lynn Smith" // However, var properties within a val can be reassigned

    val employee2: Employee
    val number4 = 100

    if (number < number4) {
        employee2 = Employee("Jane Smith", 400)
    } else {
        employee2 = Employee("Mike Watson", 150)
    } // Might look iffy but is still acceptable because employee2 will only be assigned once

    val sb: StringBuilder // <- Check declaration to see that StringBuilder is a Type Alias: @SinceKotlin("1.1") public actual typealias StringBuilder = java.lang.StringBuilder See top of class
    val employees: EmployeeSet // Example of type alias declared at start of class

    val names = listOf("John", "Jane", "Mary")
    println(names[1]) // Use square brackets to access Collection members

    for (i in 1..5) {
        println(i)
    }

    println(employee1)

    val change = 4.22
    println()
    println("$$change")
    println("To prevent the variable being printed out escape using \$change")

    val numerator = 10.99
    val denominator = 20.00
    println("The value of $numerator divided by $denominator is ${numerator/denominator}")

    println("Employee ID is ${employee1.id}") // References to other class properties are considered expressions so put them in brackets

    val filePath = "c:\\dir\\somedir" // Regular strings can sometimes require a lot of escapes
    val filePath2 = """c:\dir\somedir""" // No need for escapes with triple quoted strings!

    println()
    val nurseryRhyme = """Humpty Dumpty sat on a wall
Humpty Dumpty had a great fall
All the king's horses and all the king's men
Couldn't put Humpty together again"""
    println(nurseryRhyme)
    println()

    val nurseryRhyme2 = """Humpty Dumpty sat on a wall
        *Humpty Dumpty had a great fall
        *All the king's horses and all the king's men
        *Couldn't put Humpty together again""".trimMargin("*") // Trims everything before the quoted character. Means we can use margins in code. Default character is |
    println(nurseryRhyme2)
    println()

    val eggName = "Humpty"

    val nurseryRhyme3 = """$eggName Dumpty sat on a wall
        |$eggName Dumpty had a great fall
        |All the king's horses and all the king's men
        |Couldn't put $eggName together again""".trimMargin() // Trims everything before the quoted character. Means we can use margins in code. Default character is |
    println(nurseryRhyme3)
    println()

    println(MY_CONSTANT) // Printing top-level constant from within class
}

class Employee(var name: String, val id: Int) {
    override fun toString(): String {
        return "Employee(name='$name', id=$id)"
    }
}