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
}

 

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 +
                '}';
    }
}

 

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

 

Lambdas

Lambdas in Java.

// Easier way to work with interfaces that have just one method that has to be implemented (functional interfaces). Also true if more than one method but other(s) have default implementation
// Often used in place of anonymous classes
public class Lambdas {

    public static void main(String[] args) {
        new Thread(new CodeToRun()).start(); // One method

        new Thread(new Runnable() { // Another method - anonymous class. Still a lot of code to run one line of output
            @Override
            public void run() {
                System.out.println("Also printing from the Runnable");
            }
        }).start();

        // Can be replaced with a lambda
        new Thread(() -> System.out.println("Still printing from the Runnable")).start(); // Passes the println() directly to the Thread constructor
        // 3 parts to a lambda - 1. argument list (empty in this case) 2. arrow token 3. body (put code to be executed here) - use {} for multiple lines (remember ; when multiple lines):

        new Thread(() -> {
            System.out.println("Line one");
            System.out.println("Line two");
        }).start();

        Employee john = new Employee("John Doe", 30);
        Employee tim = new Employee("Tim Buchalka", 21);
        Employee jack = new Employee("Jack Hill", 40);
        Employee snow = new Employee("Snow White", 22);

        List<Employee> employees = new ArrayList<>();
        employees.add(john);
        employees.add(tim);
        employees.add(jack);
        employees.add(snow);

//        Collections.sort(employees, new Comparator<Employee>() {
//            @Override
//            public int compare(Employee employee1, Employee employee2) {
//                return employee1.getName().compareTo(employee2.getName());
//            }
//        });

        Collections.sort(employees, (employee1, employee2) -> employee1.getName().compareTo(employee2.getName()));

        for (Employee employee : employees) {
            System.out.println(employee.getName());
        }

//        String sillyString = doStringStuff(new UpperConcat() {
//            @Override
//            public String upperAndConcat(String s1, String s2) {
//                return s1.toUpperCase() + s2.toUpperCase();
//            }
//        }, employees.get(0).getName(), employees.get(1).getName());
//        System.out.println(sillyString);

        String sillyString = doStringStuff((s1, s2) -> s1.toUpperCase() + s2.toUpperCase(), employees.get(0).getName(), employees.get(1).getName()); // No return keyword required
        System.out.println(sillyString);

    }
    //What if the interface returns a value?
    public final static String doStringStuff(UpperConcat uc, String s1, String s2) {
        return uc.upperAndConcat(s1, s2);
    }
}

class CodeToRun implements Runnable {
    @Override
    public void run() {
        System.out.println("Printing from the Runnable");
    }
}

class Employee {
    private String name;
    private int age;

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

interface UpperConcat {
    public String upperAndConcat(String s1, String s2);
}