Quirky Kotlin Extensions

Henna Singh
4 min readJan 11, 2023

I am starting to get back to Kotlin and started studying random topics. I have been putting this topic off for a long time now , so decided to pick it up and write down my learnings. I will have this topic in mind for some while now ;)

Extension Functions

The extension function extends the class. It is defined outside of the class but can be called as a regular member of the class.

fun String.lastChar() = this.get(this.length - 1) 

Here, the extension function is defined to a string and can be used as a member function. The type that a function extends is called a receiver; here the String is the receiver of the lastChar function. In the body of the function, the receiver is accessed by this reference.

The members of the receiver can be called without explicit mention of this. An important thing to note is, that you can’t define an extension function and used it everywhere. This has to be explicitly imported.

fun String.lastChar() = get(length -1)

import com.example.util.lastChar
val c: Char = "abc".lastChar()

The private members of a class cannot be referenced inside extension functions. Kotlin extension functions are regular static functions defined in a separate auxiliary class.

Extensions play an important role in the language.

Examples from Standard Library

val set = hashSetOf(1, 7, 53)
val list = arrayListOf(1, 7, 53)
val map = hashMapOf( 1 to "one", 7 to "seven", 53 to "fifty-three")

The classes of the collections can be checked by calling :

println(set.javaClass)
println(list.javaClass)
println(map.javaClass)

As you noticed, Java Standard collection classes from java.util.package are used under the hood. When collections are created using functions from the Kotlin standard library, instances of Java classes are created.

The Kotlin library provides extensions for regular Java collections.

Extension function: joinToString

println(listOf('a', 'b', 'c').joinToString(separator = "", prefix = "(", 
postfix = ")" )) // (abc)

//regualr extension function from Kotlin Library

fun <T> Iterable<T>.joinToString(
separator: CharSequence = ", ",
prefix: CharSequence = "",
postfix: CharSequence = ""
): String

Extension function: getOrNull()

fun main(args: Array<String>) {
println("Hello, ${args.getOrNull(0)}!")

//defined as

fun <T> Array<T>.getOrNull(index: Int) =
if(index in 0 until size) this[index] else null

//similar function for list

val list = listOf("abc")
println(list.getOrNull(0)) //abc
println(list.getOrNull(1)) //null

fun <T> List<T>.getOrNull(index: Int) =
if (index in 0 until size) this[index] else null

Extension function: withIndex()

used for iterating over collections.

val list = listOf("a", "b", "C")
for ((index, element) in list.withIndex()) {
println("$index $element")
}

fun <T> Iterable<T>.withIndex(): List<IndexedValue<T>> {..}

There are lot of extensions added to Char and String Java library. Extensions are very powerful. They allow us to get new syntactic features for standard Java types.

Extensions to Child & Parent

How extensions interact with inheritance and whether extensions can hide members.

open class Parent
class Child: Parent()

//two extension functions
fun Parent.foo() = "parent"
fun Child.foo() = "child"

fun main(args:Array<String>) {
val parent: Parent = Child()
println(parent.foo())
}

Please note, extensions under the hood are Java static functions.

Java resolves static function statically. It finds the right function to be called during the compilation. It only uses the type of the argument to choose the right function, thus the parent function is chosen here since the parent variable has the parent type.

Member vs Extension

fun String.get(index: Int) = '*'

fun main(args: Array<String>) {
println("abc".get(1))
}

Member will always win. The answer here is ‘b’. If you try to define extension with the same signature as a member, then you get a warning that an extension is shadowed, so the member will always be chosen instead.

However, you can overload a member. If you define an extension with a different signature, different parameter types, or a different number of parameters, the new function will be called if it fits better.

class A {
fun foo() = "member"
}

fun A.foo(i:Int) = "extension($i)"

A().foo(2). //extension(2)

Extensions are among the most lovable features of Kotlin. Their main purpose is to keep the classes and interfaces APIs minimal.

PS: These notes are from Coursera: Kotlin for Java Developers

--

--

Henna Singh

Technical Speaker and an aspiring writer with avid addiction to gadgets, meet-up groups, and paper crafts. Currently pursuing Full Stack Bootcamp from Nucamp