We saw how anonymous classes could encapsulate reusable logic and allow us to build, for example, a general purpose counting method. But their syntax still left a lot to be desired! In this lesson we’ll improve on that by introducing lambda expressions. Awesome! Let’s start.
As a reminder, this is an advanced topic. We’re introducing it because you will see lambda expressions in real Java code, particularly when working with user interfaces.
But let’s warm up with a classic practice problem on software testing! This is similar to the problem you’ll need to solve for this lesson’s homework.
Create a method named test
accepting a single parameter: an instance of ArraySum
.
Each ArraySum
provides a method sum
that accepts an Array<Int>
and returns the sum of the values as an Int
,
or 0 if the array is null
.
However, some ArraySum
implementations are broken!
Your job is to identify all of them correctly.
To do this you should use assert
to test various inputs.
Here's an example:
Your function does not need to return a value. Instead, if the code is correct no assertion should fail, and if it is incorrect one should.
As you design test inputs, here are two conflicting objectives to keep in mind:
null
,
off-by-one errors, not handling special cases properly, etc.Good luck and have fun!
So far we’ve introduced Kotlin as an object-oriented programming language. However, Kotlin also supports other programming styles. One powerful and interesting style of programming is known as functional programming:
In computer science, functional programming is a programming paradigm where programs are constructed by applying and composing functions.
Let’s examine the Wikipedia definition together and contrast it with Java’s object-oriented style:
One characteristic of true functional programming languages is that functions (or methods) are first-class citizens. They can be stored in variables and passed to and returned from other functions, just like any other kind of data.
Unlike Java, Kotlin does support first-class functions. However, for this lesson we’ll focus on functional programming approaches that interoperate with Java. Specifically, declaring and implementing so-called functional interfaces. We may return to functional programming in Kotlin in future lesson.
Let’s look at our first lambda expression in Kotlin.
We accomplish this by combining two things we already know—interfaces and anonymous classes—with some new Kotlin syntax. Let’s see how, step by step.
But first, let’s state our goal.
Our first ingredient is called a functional interface.
A functional interface is any old interface, but with one restriction: it can only provide one method.
We’ll see why in a minute.
In Kotlin we also mark it with the fun
keyword.
Other than that, there are no restrictions on what a functional interface looks like. Here’s one:
Here’s another:
Next, we need a way to create something that implements a functional interface on the fly. But wait—we already know how to do that! It’s called an anonymous object:
We are so close now.
Imagine that we want to save into a variable a method that increments an Int
by one.
Here’s what it looks like given what we already know.
First we need our functional interface, and then an anonymous class to implement it correctly:
But we can do better! Let’s see how:
Declare a function named adder
.
adder
takes a single Int
parameter and returns a method that implements the Modify functional interface:
The returned "function" should implement modify
so that it adds the value passed to adder
.
So, for example:
The correct solution to this problem is a single line lambda expression!
To finish up, let’s return to our example from last time that used anonymous classes to count arrays in different ways. We’ll reimplement it using lambdas and show how much cleaner and more direct this syntax is.
Create a method named test
that accepts a single parameter: an instance of ArrayMax
.
Each ArrayMax
provides a method max
that accepts an IntArray
and returns the maximum of the values as an Int
.
If the array is null
or empty max
should throw an IllegalArgumentException
.
However, some ArrayMax
implementations are broken!
Your job is to identify all of them correctly.
To do this you should use assert
to test various inputs.
(Do not throw
other kinds of exceptions on failure, or use require
or check
.)
Here's an example:
Your function does not need to return a value. Instead, if the code is correct no assertion should fail, and if it is incorrect one should.
As you design test inputs, here are two conflicting objectives to keep in mind:
null
, off-by-one errors, not handling special cases properly, etc.You'll also need to think about how to use try-catch
to handle places where the code should throw an
exception, how to ensure that it does, and how to make sure it throws the right type.
Good luck and have fun!
Need more practice? Head over to the practice page.