1. Overview

In Kotlin, we already know the keyword this refers to the current object we are operating on.

In this article, we will discuss two concepts related to the use of this, namely implicit this and qualified this.

2. Receiver

The receiver of this is the object that is the context when this is used. When we use this in a function or method in Kotlin, we are referring to the object itself, or the object that receives the action:

class Outer { //<-------------------+
    fun checkThis() { //            |
        val b = this //<------------+
        // other code ...
    }
}

So, this, in this context, will refer to an instance of the Outer class.

If we use an IDE like Intellij IDEA, then we can easily find the receiver of this by quickly navigating to the symbol declaration.

3. Implicit this

Implicit this in Kotlin is a way to refer to the receiver of a class, function or lambda expression without needing to use explicit qualifiers.

The default receiver of implicit this is the instance of the class being used. For example, in a member function of a class, this implicitly refers to an instance of that class.

3.1. Accessing Current Class Instance

The simplest case of implicit this is that we access the current instance:

class Outer { //<-------+-----------+--------------+
    // ...              |           |              |
    val a = this //<----+           |              |
    // ...                          |              |
    fun checkThis() { //            |              |
        val b = this //<------------+              |
        //...                                      |
        fun checkThisToo(){ //                     |
            val c = this //<-----------------------+
        }
    }
}

We use this to refer to the instance of the Outer class itself. We can also see that the nesting functions does not affect the receiver of implicit this.

3.2. Accessing Member Objects of the Current Class

Let’s also look at using this to access class members:

class Outer {
    fun printLine() = "Member function" //<----------------------+
    val x = 0 //<--------------------------+                     |
    //                                     |                     |
    fun checkThis() {//                    |                     |
        // ...                             |                     |
        assertEquals(0, this.x)//<---------|                     |
        assertEquals(0, x) //<-------------+                     |
        //                                                       |
        assertEquals("Member function", this.printLine()) //<----|
        assertEquals("Member function", printLine()) //<---------+
    }
}

When we call a member object or function on this, we can skip the this part.

But if we have a non-member object or function with the same name, use this with caution because, in some cases it can be called instead:

val x = 5 //<-------------------------------------+
//                                                |
fun printLine() = "Top-level function" //<--------+------------------+
//                                                |                  |
class Outer {//                                   |                  |
    fun printLine() = "Member function" //<-------+----------------+ |
    val x = 0 //<-------------------------+       |                | |
    //                                    |       |                | |
    fun checkThis() {//                   |       |                | |
        // ...                            |       |                | |
        assertEquals(0, this.x) //<-------+       |                | |
        assertEquals(5, x) //<--------------------+                | |
        //                                                         | |
        assertEquals("Member function", this.printLine()) //<------+ |
        assertEquals("Top-level function", printLine()) //<----------+
    }
}

3.3. Inside Extension Functions

Extension functions will affect the behavior of the this contained within them.

The this receiver in an extension function in Kotlin refers to the object that is the receiver of the extension:

class Outer {
    inner class Inner { 
	// ...
        fun Int.foo() { //<---+-------------------------------+
            // ...            |                               |
            val y = this //<--+                               |
            assertEquals(42, this)//                          |
            // ...                                            |
            val funLit = fun String.() { //<------------+     |
                // ...                                  |     |
                assertEquals("test.funLit()", this) //<-+     |
            }   //                                            |
            //                                                |
            // ...                                            |
            val increase = fun(x: Int): Int { //              |
                return this + x //<---------------------------+
            }
	}
    }
}

val outer = Outer()
//...
val inner = outer.Inner()
//...
inner.run {
    42.foo()
}

3.4. Inside Lambda Expressions

If this is used in a lambda expression, then the receiver is the current context that could be:

  • Class: If a lambda is defined within a class, implicit this refers to an instance of that class.
  • Object: If a lambda is defined inside an object, implicit this refers to that object.
  • Extension Function: If a lambda is defined inside an extension function, implicit this refers to the function’s scope.
class Outer{
    inner class Inner { //<---------------------------+
        //                                            |
        fun checkInner(){ //                          |                   
            val funInnerLambda = { _: String ->//     |                   
                val r = this //<----------------------+
                assertEquals(Inner::class.java.name, r::class.java.name)
            }

            funInnerLambda("test inner lambda")
         
            val someNumber = 10

            someNumber.run {//<----------------------+
                val numberLambda = { _: String -> // | 
                    assertEquals(someNumber, this)<--+
                }
                numberLambda("numberLambda")
            }

            val someObject = object {
                val name = "John"
                val age = 30
            }

            someObject.run {//<---------------------+--+
                //                                  |  |
                assertEquals(someObject, this)//<---+  |
                //                                     |
                val someLambda = { _: String ->//      |
                    assertEquals(someObject, this)//<--+
                    assertEquals(30, this.age)
                    assertEquals("John", this.name)
                }

                someLambda("someLambda")
            }
        }
        
        fun Int.foo() { //<---------------------------+
	    val funExtLambda = { _: String -> //      |
		val r = this //<----------------------+
                assertEquals("Int", r::class.simpleName) 
            }

            funExtLambda("test funExtLambda")
	}
    }
}

The first this in the lambda expression inside the checkInner() function will point to the Inner class instance.

Below that, this in the someNumber object lambda refers to someNumber. Also this in the someObject object lambda refers to someObject.

Meanwhile, this in the lambda expression in the foo() extension function will refer to the Int object that is extended with the foo() function.

4. Qualified this

In Kotlin, qualified this refers to using the keyword “this@” to mark the qualification of the context “this”.

By using qualified this, we can explicitly indicate that we want to access a property or method from the class itself, not from a local variable or another class that may have the same name.

4.1. Accessing Specific Classes

By using this@ClassName, we explicitly state that we want to access the context of the particular ClassName class:

class Outer{//<--------------------------------------------------------------------+
    inner class Inner { //<----------------------------+                           |
	//                                             |                           |
        fun Int.foo() { //                             |                           |
	    // ...                                     V                           |
            assertEquals(Inner::class.java.name, this@Inner::class.java.name) //   |
            assertEquals(Outer::class.java.name, this@Outer::class.java.name) //<--+
            // ...
	}
    }
}

4.2. Accessing Specific Class Member

By using this@Outer, we can explicitly refer to the outer class context within the inner class to access its members:

class Outer{

    fun foo() : Int = 30 // <-------------------------+
    val x = 0 //<-------------------------------+     |
    //                                          |     |
    inner class Inner { //                      |     |
        //                                      |     |
        val x = 1 //<------------------+        |     |
        //                             |        |     |
        fun Int.foo() { //             |        |     |
	    // ...                     V        |     |
            assertEquals(1, [email protected]) //    |     |
            assertEquals(0, [email protected]) //<---+     |
            assertEquals(30, [email protected]())) //<---+
            // ...
	}
    }
}

4.3. Accessing Specific Extension Functions

We can also access specific extension functions with qualified this:

class Outer {
    // ...
    inner class Inner {
        // ...
        fun Int.foo() { //<--------------+----------+
            // ...                       |          |
            fun Int.bar() { //<------+   |          |
		// ...               V   |          |
                assertEquals(32, this) //V          |
                assertEquals(42, this@foo) //       |
                // ...                              |
                fun Int.baz(){ //<--------------+   |
		    // ...                      |   |
                    assertEquals(20, this) //<--+   |
                    assertEquals(42, this@foo) //<--+
                }
                20.baz()
            }
            32.bar()
        }
    }
}

Since extension functions will affect this, to access a specific context, we must use qualified this.

6. Conclusion

In this article, we have learned about how the this keyword behaves in various case examples. We also know the use of qualified this.

Use implicit this when there is no doubt about the meaning of this and it refers clearly to the owner of the current function or class instance.

Meanwhile, use qualified this to avoid ambiguity with parameters or other variables named this or to access certain class instances explicitly, especially inner classes or lambda expressions.

As always, code samples can be found over on GitHub.

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments