Extension functions
With the help of extension functions classes can be “extended”, even if they are actually final
- like String
, for example.
Extension functions allow you to declare functions outside of a class, and yet their call looks like that of a method of that given class.
Example
Suppose that in a project it is often necessary to calculate the square of an integer.
A suitable function for this is a quick write:
fun squared(i: Int) : Int {
return i * i
}
or, thanks to Kotlin’s expression bodies, even shorter:
fun squared(i: int) = i * i
Since Kotlin allows functions on package level, you could easily use this function now - without any utility class.
But with Kotlin we can do even better!
Using a corresponding extension function, we can “teach” the Int
class itself to compute and return the square of an integer.
This means that calling the squared
method looks a lot more “object oriented”, and the function “moves even closer to the subject”:
val i = 42.squared()
With Java that would not be possible like that - and looks accordingly unfamiliar.
Let’s take a look at the squared
extension function that makes this possible:
fun Int.squared() = this * this // extension function
val i = 42.squared() // call the Extension Function
check(i == 42 * 42) // proof that everything works correctly
The declaration of the squared
function actually looks quite usual - if it were not for the Int.
in its name.
Also, it suddenly does not take an integer for an argument anymore…?
Let’s start with the unfamiliar prefix.
This is called the receiver type and specifies the type we want to extend - Int
in our case - followed by a .
.
The function itself now lacks the argument i
, so we can no longer calculate i * i
.
Instead, we use this
.
But how is this possible given the function is not even noted within a class’s body?
Well, that’s exactly what we have previously specified the receiver type (Int
) for.
It is the representative of a class that would otherwise surround the function.
So if we call the function - val i = 42.squared()
- the instance to which we call it (42
) becomes this
, which is referenced within the function.
In fact, an extension function behaves very similar to one that is directly noted in a class itself - and looks almost exactly the same. There is one very important difference though:
Within an extension function you can not access private
or protected
properties of a class!
Thus, the essential difference between inheritance and extension remains: while inheritance is (hopefully) intended by the developer of the parent class, but at least considered, with extension functions you can safely extend any class - meaningfully limited to no wreak havoc.
Extension properties
Besides extension functions there also are extension properties.
So we can improve our example a little by declaring the following extension property instead of the extension function:
val Int.squared get() = this * this
Again, the declaration of the property differs from its “normal” counterpart only by the prefix of the receiver type.
Thus, the call is still a bit nicer, since the empty parentheses are omitted:
val s = 42.squared
As with common properties and functions, it is largely a matter of taste (=style guide), whether one uses a function or property.
In that case, I would personally prefer the property - simply because I would consider the square of a number quite one of its properties, its calculation is not really expensive and - last but not least - the call seems more elegant without parentheses.
Summary
- Extension functions allow functions to be declared and called as if they were an integral part of the receiver type.
- To declare an extension function, precede its name with the name of the type you want to extend (“receiver type”), separated by a
.
character. - To reference the actual instance of the receiver type,
this
is used. - Extension functions are common functions in every other aspect - particularly they have no access to
protected
andprivate
properties of the receiver type.