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
}

 

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)
    }
}

 

 

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
}