1. Introduction

In Kotlin, a companion object is a special object that we create inside a class, and we use it to define static methods and properties. Nonetheless, sometimes we may wish to access methods or properties that have been declared outside the companion object from within the companion object.

In this tutorial, we’ll explore various approaches to accessing outside methods inside a companion object.

2. Using a Reference to the Outer Class

To access methods outside the companion object, we can define a reference to the outer class within the companion object and use it to call the required method:

class OuterClass {
    companion object {
        val outerClass: OuterClass = OuterClass()
        fun companionMethod(): String {
            return outerClass.outerClassMethod()
        }
    }

    fun outerClassMethod(): String {
        return "This is a method outside the companion object"
    }
}

In this code, we define a reference to the OuterClass inside the companion object via class instantiation. We then use it to call outerClassMethod() from within companionMethod().

Let’s verify our method for correctness:

@Test
fun `calls outer method using reference to outer class`() {
    val result = OuterClass.companionMethod()
        
    assertEquals("This is a method outside the companion object", result)
}

Here, we simply call the companionMethod() statically on the OuterClass. Finally, we assert that the companionMethod() method returns the correct string.

3. Passing a Reference to the Outer Class as a Parameter

We can also choose to pass an instance of the outer class to our companion object’s method as a parameter to access methods outside the companion object. For better design purposes, let’s use an interface that our outer class implements. This interface declares the method we seek to invoke in our companion object:

interface OuterClassInterface {
    fun outerClassMethod(): String
}

class OuterClassWithInterface : OuterClassInterface {
    companion object {
        fun companionMethod(outerClassInterface: OuterClassInterface): String {
            return outerClassInterface.outerClassMethod()
        }
    }

    override fun outerClassMethod(): String {
        return "This is a method outside the companion object"
    }
}

In this code, we define an interface OuterClassInterface that declares outerClassMethod(). Next, we implement this interface in the OuterClassWithInterface class and use it to access the method from inside companionMethod().

Now, let’s test this as well:

@Test
fun `calls outer method using interface`() {
    val myClass = OuterClassWithInterface()
    val result = OuterClassWithInterface.companionMethod(myClass)

    assertEquals("This is a method outside the companion object", result)
}
In this test, we create an instance of the OuterClassWithInterface class and pass it as a parameter to the companionMethod() method. Subsequently, this method calls the outerClassMethod() method using the interface.
Note that this approach is different from the previous one. In this approach, we don’t create an instance of the outer class inside the companion object. Instead, we pass a reference of the outer class as a parameter to the method of the companion object.

4. Using an Object Declaration or Singleton Class

We can also access methods declared on other objects. Objects are singletons that can be accessed from anywhere in the code without the need to instantiate first. Therefore, by using an object, we can statically invoke any method within that object from our companion object:

class ClassWithObject {
    companion object {
        fun companionMethod(): String {
            return ObjectClass.outerClassMethod()
        }
    }

    object ObjectClass {
        fun outerClassMethod(): String {
            return "This is a method outside the companion object"
        }
    }
}

In this code, our ClassWithObject class has a companion object and an object ObjectClass. Our ObjectClass defines an outerClassMethod() method, which we access statically and invoke inside the companion object.

Of course, we also need to test this code:

@Test
fun `calls outer method using object declaration`() {
    val result = ClassWithObject.companionMethod()

    assertEquals("This is a method outside the companion object", result)
}

We see that we can just call the companionMethod() on the ClassWithObject class. This, in turn, invokes the outerClassMethod() method from our ObjectClass object and returns the desired string.

5. Using a Function Reference

Finally, we can use function references to access outside methods within the companion object. In brief, we pass a reference to the required method outside as a parameter to the method in the companion object:

class ClassWithFunctionReference {
    companion object {
        fun companionMethod(outerClassMethod: () -> String): String {
            return outerClassMethod()
        }
    }

    fun outerClassMethod(): String {
        return "This is a method outside the companion object"
    }
}

In this example, our companionMethod() method takes a function reference to the outerClassMethod() method as a parameter. Next, we call this function reference from within the companionMethod() method.

This approach is flexible in that we can pass in a reference to any function with the same signature to our companion object’s method.

Let’s now test this code for correctness:

@Test
fun `calls outer method using function reference`() {
    val myClass = ClassWithFunctionReference()
    val result = ClassWithFunctionReference.companionMethod(myClass::outerClassMethod)

    assertEquals("This is a method outside the companion object", result)
}

In this test, we create an instance of the ClassWithFunctionReference class. Next, we use this instance to obtain a reference to the outerClassMethod() using the double-colon (::) operator. We then pass this reference to the companionMethod() method. Finally, we assert that this companion method returns the correct string.

6. Conclusion

In this article, we’ve explored various ways we can access methods that are declared outside a companion object from inside the companion object. Some of the approaches may be more convenient than others in certain scenarios.

For example, by passing function references as parameters, we can reuse the same method implementation with different functions. This can save time and effort by eliminating the need to write and maintain multiple similar methods. As always, we should pick the approach that best addresses our project needs.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.