Many languages do not have the concept of a function type. Instead, they use interfaces with a single method. Such interfaces are known as SAMs (Single-Abstract Method). Here is an example SAM that is used to pass information about what should happen when a view is clicked:
interface OnClick {
fun onClick(view: View)
}
When a function expects a SAM, we must pass an instance of an object that implements this interface[^38_1].
fun setOnClickListener(listener: OnClick) {
//...
}
setOnClickListener(object : OnClick {
override fun onClick(view: View) {
// ...
}
})
However, Kotlin supports two mechanisms that give us more freedom:
the function type,
the functional interface.
// Function type usage
fun setOnClickListener(listener: (View) -> Unit) {
//...
}
// Functional interface declaration
fun interface OnClick {
fun onClick(view: View)
}
// Functional interface usage
fun setOnClickListener(listener: OnClick) {
//...
}
When we use either of these two, the argument can be defined as:
Objects that implement the declared function type or a functional interface.
class ClickListener : (View) -> Unit {
override fun invoke(view: View) {
// ...
}
}
setOnClickListener(ClickListener())
Both function types and functional interfaces allow the above usages, but in general we consider function types as the standard way to represent operations and actions as objects.
Using function types with type aliases
As we said already, function types are the standard way to represent operations and actions as objects. If you want to name a concrete function type, you can use a type alias.
typealias OnClick = (View) -> Unit
A type alias provides another name for a type that is like a nickname. No matter whether you use someone's full name or their nickname, you still mean the same person. It’s the same with type aliases: during compilation they are replaced with the type they represent.
Type aliases can also be generic:
typealias OnClick<T> = (T) -> Unit
Function type parameters can be named. The advantage of naming them is that these names can then be suggested by default by the IDE. However, when we start naming parameters, the types tend to get longer. This is why we often use this feature together with type aliases.
typealias OnClick = (view: View) -> Unit
fun setOnClickListener(listener: OnClick) { /*...*/ }
Reasons to use functional interfaces
A functional interface is a heavier solution. Such interfaces need to be defined, but in return:
they define a new named type,
handler functions can be named differently (in a function type, handler name is always invoke),
interoperability with other languages is better.
interface SwipeListener {
fun onSwipe()
}
fun interface FlingListener {
fun onFling()
}
fun setOnClickListener(listener: SwipeListener) {
// when swipe happens
listener.onSwipe()
}
fun main() {
val onSwipe = SwipeListener { println("Swiped") }
setOnClickListener(onSwipe) // Swiped
val onFling = FlingListener { println("Touched") }
setOnClickListener(onFling) // Error: Type mismatch
}
Functional interfaces also allow non-abstract functions to be added and other interfaces to be implemented.
interface ElementListener<T> {
fun invoke(element: T)
}
fun interface OnClick : ElementListener<View> {
fun onClick(view: View)
override fun invoke(element: View) {
onClick(element)
}
}
When we design a class to be used from a language other than Kotlin, interfaces are cleaner and better supported. These other languages cannot see type aliases or have name suggestions. What is more, Kotlin function types need to return something, at least Unit, and returning Unit must be explicit in Java:
// Kotlin
class CalendarView() {
var onDateClicked: ((date: Date) -> Unit)? = null
var onPageChanged: OnDateClicked? = null
}
fun interface OnDateClicked {
fun onClick(date: Date)
}
Another advantage of functional interfaces is that they are not wrapping types. Since a function type is a generic type under the hood, primitives cannot be used. This means that a parameter of type Int in Java will be interpreted as Integer instead of int. This can sometimes make a difference, as explained in Item 47: Avoid unnecessary object creation. Functional interfaces do not have this problem.
Overall, the main reasons to prefer functional interfaces are:
Java interoperability,
optimization for primitive types,
to help us represent not merely a function but an interface with a contract.
When you don't need any of these, use function types instead of functional interfaces.
Avoid expressing actions using interfaces with multiple abstract methods
Another practice that can be observed among developers who switched to Kotlin from Java is expressing actions using interfaces with multiple abstract methods:
class CalendarView {
var listener: Listener? = null
interface Listener {
fun onDateClicked(date: Date)
fun onPageChanged(date: Date)
}
}
This pattern was popular in Java when functional interfaces weren't supported. I believe this is largely a result of laziness. From an API consumer’s point of view, it is better to define them as separate properties containing either function types or functional interfaces:
// Using functional interfaces
class CalendarView {
var onDateClicked: OnDateClicked? = null
var onPageChanged: OnPageClicked? = null
}
// Using function types
class CalendarView {
var onDateClicked: ((date: Date) -> Unit)? = null
var onPageChanged: ((date: Date) -> Unit)? = null
}
In this way, the implementations of onDateClicked and onPageChanged do not need to be tied together in an interface. Now, these functions may be changed independently, and we can use function literals (e.g., lambda expressions) to set them.
Summary
To express behavior, prefer function types or functional interfaces instead of standard interfaces or abstract classes.
Function types are used more often. When they are used multiple times or are getting too long, we hide them behind type aliases.
Functional interfaces are preferred primarily for Java (or other languages) interoperability and for more complicated cases when what we want to express is more than just an arbitrary function.
[^38_1]: Unless it is a Java SAM: in such cases, there is special support, and we can pass a function type instead.
Marcin Moskala is a highly experienced developer and Kotlin instructor as the founder of Kt. Academy, an official JetBrains partner specializing in Kotlin training, Google Developers Expert, known for his significant contributions to the Kotlin community. Moskala is the author of several widely recognized books, including "Effective Kotlin," "Kotlin Coroutines," "Functional Kotlin," "Advanced Kotlin," "Kotlin Essentials," and "Android Development with Kotlin."
Beyond his literary achievements, Moskala is the author of the largest Medium publication dedicated to Kotlin. As a respected speaker, he has been invited to share his insights at numerous programming conferences, including events such as Droidcon and the prestigious Kotlin Conf, the premier conference dedicated to the Kotlin programming language.
A Kotlin enthusiast since the 1.0 of the language. Android Developer Advocate at Stream. Android/Kotlin tech editor at RayWenderlich.com. Instructor at BME-VIK, teaching Kotlin and Android. Creator of RainbowCake, Krate, and MaterialDrawerKt. Ranked right around the very top of the Kotlin tag on StackOverflow.
After working as a Java engineer for 8 years in various French IT companies, I moved to mobile application development on iOS and Android in 2012. In 2015, I decided to focus on Android and joined i-BP, an IT department of the French banking group BPCE, as Android expert. I am now passionate about Android, clean code, and, of course, Kotlin programming.
I have been developing on and off since I was 10. I started developing full time since I graduated from the University of Utah. I started evaluating Kotlin as a viable language since version 0.6 and have been using it as my primary language since version 0.8. I was part of an influential team that brought Kotlin to my entire organization. I love playing table top games with my family.