Syntax of Kotlin's double colon operator

The :: symbol in Kotlin addresses specific syntax needs. We’ve seen that functions are first-class citizens, but getting a function reference is different from Python or JavaScript. In Kotlin, we use ::. This might be because Kotlin allows invoking functions without parentheses in some cases, making it tricky to determine if a reference or invocation is intended.

Function References. To use an existing function elsewhere, get its reference using ::.

1
2
3
4
5
6
7
 `fun sayHi(from: String, to: String) {
    println("Hi from $from to $to")
}

    // the compiler creates a Singleton class for this
    val greet = ::sayHi
    greet("Xose", "Francois")` 

The Kotlin compiler generates a singleton class implementing the relevant “Function Type Interface” (here, kotlin.jvm.functions.Function2), which calls the sayHi function through its invoke method.

[

You can invoke the function reference directly. In this case, no “Function object” is needed, so no class is created; the generated bytecode is the same as a direct function call:

1
2
 `(::sayHi)("Xose", "Francois")
    sayHi("Xose", "Francois")` 

Method References. We can obtain references to methods. When retrieved from a class, it’s an Unbound Method Reference (requiring the receiver as the first parameter during invocation). When retrieved from an instance, it’s a Bound Method Reference.

Here’s an example of Unbound Method References:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 `class Formatter constructor(val wrap: String) {
    fun format(msg: String): String {
        return "${wrap}-${msg}-${wrap}"
    }
}

fun testUnboundMethodReference2(){
    // a new Singleton class is created by the compiler
    val format: (Formatter, String) -> String = Formatter::format
    println(format(Formatter("|"), "AA"))
}` 

The compiler generates a Singleton class implementing the Function2 interface. Its invoke method accepts two parameters, using the first as the receiver to call format.

[

Direct invocation of method references is also possible, avoiding new class creation and behaving like a regular call:

1
2
3
4
 `fun testUnboundMethodReference1(){
    // no new class is created, bytecodes are similar to a normal method call
    println((Formatter::format)(Formatter("|"), "AA"))
}` 

Now, an example of Bound Method References:

1
2
3
4
5
6
 `fun testBoundMethodReference(){
    // the compiler creates a new class (not a singleton), holding the receiver (formatter) as a property
    val formatter = Formatter("|")
    val format: (String) -> String = formatter::format
    println(format("AA"))
}` 

A new class is created with a property referencing the receiver. The invoke method takes a string and uses the receiver to invoke format.

[

Constructor references are obtained using ::ClassName (e.g., myFn = ::ClassName). This creates a new singleton class. Since Kotlin doesn’t use “new” for instantiation, this reference acts as a factory, similar to Python.

1
2
3
4
5
 `fun testConstructorReference(){
    val factory: (String) -> Formatter = ::Formatter
    val formatter = factory("|")
    println(formatter.format("AA"))
}` 

We can also obtain bound and unbound references to properties. This explanation is similar to Callable References" documentation., with different examples and insights into the compiler’s actions.

Another use of :: is getting an object (KClass instance) with class information. We can obtain this directly from the class (Person::class) or an instance (myPerson::class). Note that a KClass is not a Python metaclass; it only holds class information, unlike a metaclass which is involved in class creation (a concept absent in Kotlin).

Licensed under CC BY-NC-SA 4.0
Last updated on Mar 26, 2024 04:20 +0100