From the readability and ease points of view, having application-specific convenience methods on third-party classes will make the lives of your fellow programmers better. And, that’s possible because Kotlin permits you to inject methods and properties into any class, including classes written in other JVM languages. Unlike other languages that offer metaprogramming, Kotlin performs injection without patching the runtime or class loading. In Kotlin, classes are open for extension, even though they may not be available for inheritance. Extension functions and extension properties are techniques to add methods and properties, but without altering the bytecode of the targeted classes. When you create an extension function for a class, it gives an illusion that you’ve implemented an instance method for that class. Don’t create an extension function for a method if that method already exists in the class. Members of a class always win over extension functions if there’s a conflict. When the Kotlin compiler sees a method call, it checks to see if an instance method is available and uses it if found. If an instance method isn’t found, Kotlin looks for an extension function for the targeted class.

You can inject methods and properties into existing classes, including final classes, and even those classes that you didn’t write—that’s the spirit of a free society.

Let’s explore injecting methods into existing classes first, then look into injecting an operator, followed by injecting a property.

Injecting methods using extension functions

Suppose our large enterprise application has two classes Point and Circle, defined like so:

// circle.kts
data class Point(val x: Int, val y: Int)
data class Circle(val cx: Int, val cy: Int, val radius: Int)

These classes don’t have any methods at this time, and the two classes are independent of each other.

Suppose we want to find if a point is located within a circle. It would be nice to have a convenience method in either of the classes for that. But we don’t have to bribe the creator of the Circle class or the Point class to introduce that method. We can add methods, right from outside, to any of these classes. Let’s inject an extension function named contains() into the Circle class, like so:

// circle.kts
fun Circle.contains(point: Point) = 
  (point.x - cx) * (point.x - cx) + (point.y - cy) * (point.y - cy) < 
    radius * radius

This code sits outside any of the classes, at the top level of a package—a default package in this example. Within the contains() extension function, we access the member of the implicit Circle instance exactly like we’d access it if this extension function were written as an instance method within the class. If we’d written this method within the Circle class, the only difference is that we would have written fun contains(point: Point) instead of fun Circle.contains(point: Point).

As long as this method is visible—that is, it’s either in the same file or we’ve imported it from the package where it resides—we can use it, like so:

Get hands-on with 1200+ tech skills courses.