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.
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()
}
- introduced in Kotlin 1.4
- official docs
- purpose is to achieve first-class functions
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
}
- introduced in Kotlin 1.1
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.
- introduced in Kotlin 1.5
- previously called “inline class”
@JvmInline
is temporary and will be removed with Project Valhalla (seriously)
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
- /u/Boza_s6 pointed out an error in my implication that
String
would be allocated in the stack. Strings are not primitives so I switched the example toInt
for accuracy. - /u/smieszne pointed out that my original example using ImprovedBruceLee as the data object would not output “BruceLee” but rather “ImprovedBruceLee”. As clever as my original example was 🙄, I resorted to just using the class name BruceLee for clarity.
-
without having to type the full package name over and over ↩︎
-
To learn more about stack vs heap memory allocation and why stack allocation is less expensive read https://www.baeldung.com/java-stack-heap ↩︎