Extensions as Members : Kotlin


In continuation to my last Medium post Extensions in Kotlin where I explained what are Extensions and how do we use it, this medium post will cover the implementation of Extensions as members of some other class.

An extension can be defined as members of some other class also.
The benefit of defining an extension as a member of the other class is, we can access all the functions and properties of both the classes inside that extension method.
Let’s take an example
  
class BaseClass {
fun baseClassFunctionality() {

}
}

class OtherClass {

fun otherClassFunctionality() {

}

fun BaseClass.additionalFunctionality() {
baseClassFunctionality()
otherClassFunctionality()
}

}
We have two classes named BaseClass and OtherClass. We have a method called baseClassFunctionality in BaseClass and otherClassFunctionality in OtherClass. Now, we added an extension function of BaseClass in OtherClass naming additionalFunctionality. The benefit of adding an extension function of BaseClass in OtherClass is we can access all the methods of BaseClass and OtherClass in that method.

What if both the classes have some same method with the same signature?

It will call the method of the class for which the extension function is defined. But if we want to call the method of the class in which the extension function is defined, we can use the qualified this expression.
  
class BaseClass {

fun printString() {
println("I am in BaseClass")
}
}

class OtherClass {

fun otherClassFunctionality() {
BaseClass().additionalFunctionality()
}

fun printString() {
println("I am in OtherClass")
}

fun BaseClass.additionalFunctionality() {
printString()
this@OtherClass.printString()
}

}

// the output of
OtherClass().otherClassFunctionality()

//will be
I am in BaseClass
Extension function as members can also be declared as open and can be overridden in the derived class. Which function will be called will be decided at runtime for virtual methods but statically for extension methods.

Let me explain this with an example.

Let’s say we have a class named BaseClass and another class named DerivedClass which is extending the BaseClass.
Now we have another class called OtherClass having two open extension functions of both BaseClass and DerivedClass (one each). Also, we have one more class extending the OtherClass and overriding both the extension methods.
  
open class BaseClass {
}

class DerivedClass : BaseClass() {
}

open class OtherClass {

open fun BaseClass.someFunctionality() {
println("BaseClass.someFunctionality in OtherClass")
}

open fun DerivedClass.someFunctionality() {
println("DerivedClass.someFunctionality in OtherClass")
}

fun caller(baseClass: BaseClass) {
baseClass.someFunctionality()
}
}

class DerivedOtherClass : OtherClass() {
override open fun BaseClass.someFunctionality() {
println("BaseClass.someFunctionality in DerivedOtherClass")
}

override fun DerivedClass.someFunctionality() {
println("DerivedClass.someFunctionality in
DerivedOtherClass")
}
}
Now will consider four different cases which will clear the picture of overriding extension methods.
  
OtherClass().caller(BaseClass())

DerivedOtherClass().caller(BaseClass())

OtherClass().caller(DerivedClass())

DerivedOtherClass().caller(DerivedClass())

1. When we call the caller method of OtherClass with OtherClass reference object and pass the BaseClass object as the parameter.
  
OtherClass().caller(BaseClass())
//the output of the above code will be
BaseClass.someFunctionality in OtherClass
This will call the extension function of BaseClass defined in OtherClass. There was no confusion because both the references were of the superclasses.

2. When we call the caller method of OtherClass with DerivedOtherClass reference object and pass the BaseClass object as the parameter.
  
DerivedOtherClass().caller(BaseClass())

//the output of the above code will be
BaseClass.someFunctionality in DerivedOtherClass
This will call the extension function of BaseClass overridden in DerivedOtherClass.
The virtual dependency of OtherClass and DerivedOtherClass was resolved at runtime and it called the DerivedOtherClass’s overridden method.
3. When we call the caller method of OtherClass with OtherClass reference object and pass the DerivedClass object as the parameter.
  
OtherClass().caller(DerivedClass())

//the output of the above code will be
BaseClass.someFunctionality in OtherClass
This will call the extension function of BaseClass defined in OtherClass.
Even though we passed the DerivedClass object as the parameter, it called the BaseClass method only because the extensions are resolved statically.
In the caller method, we have defined the parameter type as BaseClass so it will always call BaseClass extension methods only.

4. When we call the caller method of OtherClass with DerivedOtherClass reference object and pass the DerivedClass object as the parameter.
  
DerivedOtherClass().caller(DerivedClass())

//the output of the above code will be
BaseClass.someFunctionality in DerivedOtherClass
This will call the extension function of BaseClass overridden in DerivedOtherClass.

The reason is the same.
The virtual dependency of OtherClass and DerivedOtherClass was resolved at runtime and it called the DerivedOtherClass’s overridden method and because the extensions are resolved statically, it called the BaseClass extension method only.

Also, extension utilizes the same visibility of other entities as regular functions declared in the same scope would.
  • If the extension is defined at the top level of the class, it can access all the private variables and functions of that class.
  • If the extension function is defined outside the class, it can not access the private variables or functions of that class.


Reference: Kotlin docs