Object Oriented Design and Development

Topic 6: Further Kotlin language features

This week we will look at some further Kotlin language features, in particular lambda functions and interfaces.

More on Functions in Kotlin

In Kotlin, functions can be stored in variables and passed as arguments to other functions. In this respect, Kotlin is similar to JavaScript. This property of functions makes them first class (see the Kotlin documentation).

Anonymous Functions

Passing functions as arguments to other functions

Lambda functions

Making the lambda more concise - the implicit "it"

Real-world use of lambdas

Question

Mappings: performing the same operation on all members of a list

Another common use of lambdas is to perform a mapping. A mapping transforms each member of an input list by a specified function, and returns a new list containing the transformed data. This example will convert each string in the input list to lower case and return a new list containing the lower case values:

fun main(args: Array<String>) {
    val peopleList = listOf("Mark Cranshaw", "Rob Cooper", "Al Monger", "Mark Udall", "Margaret Jones")
    val lowerCaseList = peopleList.map { person -> person.lowercase() }
    println(lowerCaseList)    
}
Note how we use the map() function to transform each member of peopleList by a specified lambda. The lambda here will take each member of the input list in turn (person) and return that member converted to lower case (i.e. person.lowercase()). So, the list returned from map, i.e. lowerCaseList, will contain the person names converted to lower case.

Other collection functions which use lambdas

Other collection functions - example

Interfaces

The Problem With Inheritance and the Need for Interfaces

Imagine we have a situation where a class could potentially inherit from more than one parent class. For example we could have a Bird could inherit from both Animal and FlyingThing. It could inherit the following methods and attributes from Animal:

... and the following from FlyingThing: How do we handle this?

problem with inheritance

Multiple Inheritance?

Introduction to Interfaces

Interfaces are used to define common behaviour across classes which might be from different inheritance hierarchies. For example, birds might be animals and planes might be vehicles, but they share the common behaviour of flying.

Generally you should follow the principle: if something "fundamentally is" something else, use inheritance. If it just shares common behaviour, use an interface. So for example a Bird is fundamentally an animal, so it makes sense to inherit Bird from Animal. But a Bird isn't fundamentally a "FlyingThing", it just has the behaviour of flying. So it makes sense to make FlyingThing an interface.

Interface Example

interface FlyingThing { 
    fun fly(): String 
}

How do we implement an interface

Interfaces with polymorphism

Real-world use of interfaces

The above was just a trivial example showing the concept of interfaces. However, how are they used in the real world? The advantage of using interfaces is that you can specify a list of methods which must always be present, without revealing what actual class will be used. As long as the class you use implements the interface and the list of methods stated in the interface, any class can be used where a method parameter is of an interface type. Why is this potentially more useful than inheriting from an abstract superclass which specifies a list of abstract methods?

It's probably best to illustrate real-world interface usage with an actual example. A common use of interfaces is to implement user event handling in a GUI application. Events occur when the user interacts with the UI, for example by clicking a button. We respond to events with event handlers - functions and methods which run when the event occurs. Typical event-handling code might look something like the example below (this is not the real code you would use in Kotlin GUI programming, it's just an example to illustrate the point). Here we are adding an event handler object (clickHandler) to a button:

button.addClickHandler(clickHandler)
The addClickHandler() method of the Button class might have the signature below, i.e. it takes one parameter of type ClickHandler:
fun addClickHandler(clickHandler: ClickHandler)
Here, ClickHandler could be an interface which might specify one method, onClick(), for example:
interface ClickHandler {
    fun onClick()
}

The advantage of this design over making ClickHandler an abstract superclass is that any class can implement the ClickHandler interface and provide an onClick() method to handle the user clicking on the button - including classes which already inherit from something else. If we created a ClickHandler class instead, the onClick() method would have to be placed inside a class inheriting from ClickHandler which makes the code less flexible as some of our classes might already be subclasses of another superclass. By defining an interface instead, it means that any object we like can act as the ClickHandler, provided it implements the ClickHandler interface and has an onClick() method.

This also allows us to make changes to our implementation more easily. If we want to change the class which handles our click events, we can easily do it by making the new class implement the interface and adding an onClick() method to it.

Later on in the module we will see how interfaces are often used in design patterns.

Single Abstract Method (SAM) conversions

As a language which aims to provide conciseness in code where possible, Kotlin allows you to use an alternative syntax when providing an object implementing an interface as an argument. Imagine we have code like this (again, not code that would actually work, but merely an example to illustrate the concept):

class MyGuiWindow : Window() {

    fun setupGui() {
        val b = Button("Click Me!")
        val eventHandler = object: ClickHandler {
            override fun onClick() {
                displayAlert("Button Clicked!")
            }
        }
        b.addClickHandler(eventHandler)
        b.addTo(this) // "this" means the current object, i.e. the window
    }
}
This example also shows another new concept, the anonymous class. An anonymous class is an unnamed, single-instance class which typically inherits from an abstract class or (as here) implements an interface, and provides implementations of the required methods on-the-fly:
val eventHandler = object: ClickHandler { ... }
This means that the variable eventHandler is an object which implements ClickHandler without creating a new class. Note how it includes an implementation of onClick().

This works, but you could argue that creating the anonymous class with an implementation of onClick() is quite wordy and long-winded.

What Kotlin allows us to do instead is to add a lambda as the event handler making use of SAM Conversions. SAM stands for Single Abstract Method and describes an interface with only one method defined - as ClickHandler does here. The idea of SAM is that method calls which expect an interface with only one method defined can be written instead as a lambda, to make it more concise. We could rewrite our setupGui() as follows:
fun setupGui() {
    val b = Button("Click Me!")
    b.addClickHandler(ClickHandler {
        displayAlert("Button Clicked!")
    })
    b.addTo(this)
}
Note how the argument to addClickHandler() is now a ClickHandler object with a lambda as an argument. This lambda is the Single Abstract Method implementation, i.e. the implementation for the onClick() for our button.

For this to work you need to change your interface to be a functional interface. To do this, you just precede the keyword interface with the keyword fun:

fun interface ClickHandler {
    fun onClick()
}

Questions

  1. Write a simple program, with just a main(), which reads in 5 strings from the keyboard into a mutable list. Using a filter with a lambda, create another list which contains only those strings which are not blank (""). Display the filtered list.
  2. Write a method in your University class which finds all students who passed (mark at least 40). It should do this by applying a filter to the student list, returning only those students with a mark of at least 40.
  3. Write a method in University called getStudentNames(). This should return a list of names of all students at the university (i.e. it should be a list of strings). Use a mapping to do this.
  4. At:
    https://github.com/nwcourses/com534-topic6-starter
    is another version of the University project. You are going to simulate event handling by using an event handler interface called MenuHandler which handles the "event" of the user selecting a menu option.
    • Note how the TuiApplication class (which represents the application as a whole, and is created from main) is implementing the MenuHandler interface, so it can act as the handler for each menu item.
    • Secondly write, inside TuiApplication, the onMenuItemSelected() method to handle the user selecting a particular menu option. This receives a parameter of the number of the menu item being selected, so you should use a when statement to test the number and run the appropriate code. The three items of functionality (add a student, search by ID, search by name) are provided in the code already, but you need to link them to the onMenuItemSelected().
    • Using anonymous classes, change your code to write three separate handler objects for each menu item in separate anonymous class objects, as shown in the notes. Each handler object should, inside its onMenuItemSelected(), include the appropriate functionality (add a student, search by ID, search by name). It can now ignore the choice parameter (why?)
    • Finally change your code again to use a lambda, with SAM conversions, for each event handler.