A few Kotlin constructs have been introduced into the language over time. I wrote this post as a personal/public service advisory to remind us of their significance.

Kotlin header image
Would love to credit img owner

1. fun interface (SAM)

Many languages (like Java) did not initially treat functions as first-class citizens. They would emulate this functionality by using an interface with exactly one abstract method (a.k.a Single Abstract Method or SAM) interfaces.

fun interface OnClickListener {
    // single abstract function
    fun onClick(view: View)
}

fun Button.setOnClickListenerXXX(listener: OnClickListener) {
    // listener then invoked at some point
    listener.onClick(this)
}

When fun interface is used like the above, we typically must pass an instance of an object that implements this SAM interface.

val clickListener = object: OnClick {
    override fun onClick(view: Button) {
        Toast.makeText(context, "Button 1 Clicked", Toast.LENGTH_SHORT).show()
    }
}

// You can now "pass this function" around
myButton.setOnClickListenerXXX(clickListener)

In practice, modern JVMs allow a far less verbose version of the above via lambdas:

myButton.setOnClickListenerXXX { view ->
    Toast.makeText(view.context, "testing 1", Toast.LENGTH_SHORT).show()
}

1.1. (vs) function types

Kotlin treats functions as first-class citizens and bakes the concept of “function types” right into the language.

Function types and interfaces are not the same thing but serve a similar purpose (treat functions as first-class citizens). The book Effective Kotlin does good justice in pointing out the nuance between these two concepts.

At a time when lambdas were not properly supported with most modern JVMs, function types were the elegant alternative. This makes it confusing today (the existence of two similar concepts) because they can appear very similar in usage. Let’s compare the two:

// -------------------------
// definition via fun interface
// -------------------------

fun interface OnClickListener {
    fun onClick(view: View)
}
fun Button.setOnClickListenerXXX(listener: OnClickListener) {
    listener.onClick(this)
}

// -------------------------
// definition via fun type
// -------------------------

fun Button.setOnClickListenerYYY(listener: (View) -> Unit) {
    listener.invoke(this)
}

The snippet (View): Unit is Kotlin allowing you to natively define a function as a type (function type? 🤯).

In practice, you’ll find no difference in how the above two snippets are used

myButton.setOnClickListenerXXX { view ->
    // do something with the view...
    Toast.makeText(view.context, "testing 1", Toast.LENGTH_SHORT).show()
}

myButton.setOnClickListenerYYY { view ->
    // do something with the view...
    Toast.makeText(view.context, "testing 2", Toast.LENGTH_SHORT).show()
}

My 2 cents: just use function types when possible.

2. type alias

Type aliases provide alternative names for existing types. It’s conceptually straightforward and particularly useful when used with longer function type definitions.

typealias MyHandler = (Int, String) -> Unit

// now just use MyHandler as your type definition
//  instead of tediously typing (Int, String) -> Unit every time

val myHandler: MyHandler = { intValue, stringValue ->
    // do something
}

3. import alias

The interesting thing with type alias is that you aren’t limited to using it just for function types. You can use it to “redefine” any type (or even shorten awkward long class names):

typealias DropdownView = Spinner        // 🙄
typealias AVD = AnimatedVectorDrawable

But this is not the way 🙅. Kotlin has a nicer way to do this called “import alias”:

import android.widget.Spinner as DropdownView
import android.graphics.drawable.AnimatedVectorDrawable as AVD

private lateinit var myDropDown: DropdownView
private lateinit var myAVD: AVD

Another important use of import alias is when you’re trying to disambiguate between multiple types with the same name. Say you have a type defined with a generic name but you also have a protobuf library autogenerating a different class with the exact same name. It’s possible to use both types within the same class by disambiguating them using import alias 1:

import java.sql.Date as SqlDate
import java.util.Date as JavaDate

val date1: SqlDate = SqlDate(0)
val date2: JavaDate = JavaDate()

4. value class

This article explains why we need value classes (and why we should stop using type alias or data class in these situations).

This is the gist:

fun auth1(userId: Int, pin: Int): Boolean

val u: Int = 909
val p: Int = 1234

auth1(u, p)
auth1(p, u) // ⚠️ no compilation error

See the problem here? Both userid and pin are of type Int but serve different intents. Wouldn’t it be nice if the compiler told us that we were passing the wrong fields? You can solve this with some good engineering and data class:

data class UserId(val field: Int)
data class Pincode(val field: Int)

fun auth2(userId: UserId, pin: Pincode): Boolean

val u = UserId(909)
val p = Pincode(1234)

auth2(u, p)
auth2(p, u) // 🛑 error (✅)

This “works” but has one big problem: data class allocations are expensive. With the Int primitive usage we’re dealing with the stack whereas with data class objects we enter heap territory2.

We could use type alias and avoid the allocation cost. But we run into the original issue viz. no compilation error:

typealias UserId = Int
typealias Pincode = Int

fun auth3(userId: Int, pin: Int): Boolean

val u = UserId(909)
val p = Pincode(1234)

auth3(u3, p3)
auth3(p3, u3)  // ⚠️ no compilation error

To solve this issue, Kotlin introduced the concept of “value classes” in version 1.5:

@JvmInline
value class UserId(val field: Int)
@JvmInline
value class Pincode(val field: Int)

fun auth4(userId: UserId, pin: Pincode): Boolean

val u = UserId(909)
val p = Pincode(1234)

auth4(u, p)
auth4(p, u) // 🛑 error (✅)

This is similar to data class but the difference is that underneath the hood, there’s no allocation costs 💪. So why not just use value class always instead of data class? Not so fast Scott, value class can only take in a single primitive field so there’s very legitimate reasons when you should be using a data class instead.

5. data object

Have you ever tried to print the singleton object during debugging?

object BruceLee
println("Object Bruce Lee is ${BruceLee}")
// Object Bruce Lee is BruceLee@37f8bb67

yuck!

Experimental since 1.7.20 and stable since 1.8.0 Kotlin introduces the concept of “data object”. Think of it as tacking on a nicer .toString() to your Singleton.

data object BruceLee
println("Object Bruce Lee is ${BruceLee}")
// Object Bruce Lee is BruceLee

For the gory details, you can read the KEEP or proposal discussion.

My thanks to Donn for reviewing this post.

Revisions


  1. without having to type the full package name over and over ↩︎

  2. To learn more about stack vs heap memory allocation and why stack allocation is less expensive read https://www.baeldung.com/java-stack-heap ↩︎