Extension Function in Kotlin

Learn via video courses
Topics Covered

Overview

Extension functions in Kotlin are a powerful language feature that allows you to add new functionality to existing classes without modifying their source code. They enable you to extend the capabilities of classes from the Kotlin Standard Library, third-party libraries, or even your custom classes. By defining an extension function, you can call it as if it were a member function of the extended class, making the code more expressive and concise.

Extension Function

Extension functions in Kotlin allow you to add new functionality to existing classes without modifying their source code. Extension functions in Kotlin functions provide a way to extend the behavior of a class without the need for inheritance or altering the original class implementation.

Code:

Explanation:

Here, we have a class Calculator with a member function add(a: Int, b: Int): Int that performs addition. We want to add a new operation, subtraction, to the Calculator class without modifying its original implementation. To achieve this, we use an extension function.

We declare the extension function subtract(a: Int, b: Int): Int. It is defined outside the Calculator class and starts with the receiver type (Calculator in this case) on which we want to add the functionality. We use the Calculator class as the receiver type, so the extension function will be available to any instance of Calculator. Inside the extension function, we provide the implementation for the subtraction.

In the main() function, we can call the extension functionsuntract() on the calculator instance just as we call an instance method add().

Extensions are Resolved Statically

An essential aspect regarding extension functions in Kotlin is that they are resolved statically. This means that the extension function executed is determined solely by the type of the expression on which it is invoked, not the type resolved during the expression's final execution at runtime.

Code:

Output:

Explanation:

In this example, we have a hierarchy of classes with a base class Shape, and two subclasses Circle and Square. We define three extension functions: printShapeType() for the Shape, Circle, and Square classes.

In the main() function, we create two instances shape1 and shape2, both declared as Shape but initialized as Circle and Square, respectively.

When we call printShapeType() on shape1, we might expect it to print "Circle" since it's referring to a Circle instance. However, the extension function called is determined statically based on the declared type of the variable (Shape), not the actual runtime type of the object (Circle). So, the output is "Shape".

Similarly, when we call printShapeType() on shape2, which is a Square, we get the same output, "Shape".

This behavior is known as static dispatch or static resolution of extension functions in Kotlin. It's important to be aware of this distinction to avoid unexpected results.

Dynamic dispatch is not possible for extension functions in Kotlin. In other words, when you call an extension function on an object, the compiler determines which function to call based on the declared type of the object, not the actual runtime type of the object.

Nullable Receiver

It is possible to define extension functions in Kotlin for nullable class types as well. In such cases, you can add a null check inside the extension function and return an appropriate value accordingly.

Code:

Output:

Explanation:

  1. In this code example, we have a simple data class Person representing a person with a name and age.
  2. We define an extension function getSafeName() for the Person class. It can be called on both nullable (Person?) and non-null Person objects.
  3. The getSafeName() extension function checks for null using the safe call operator (?.). If the Person object is not null, it returns its name; otherwise, it provides the default value "Unknown" using the elvis operator (?:).
  4. In the main() function, calling getSafeName() on person1 returns "Alice" as expected, and on person2, it returns "Unknown" safely.
  5. Using the getSafeName() extension function with a nullable receiver (Person?) ensures safer code without explicit null checks in every usage.

Extension Properties

Defining extension properties follows a similar approach as extension functions in Kotlin. They are declared outside of the class, just like extension functions in Kotlin. However, unlike regular properties, extension properties cannot have a backing field due to the way extensions work. As a result, initializers are not permitted for extension properties.

While extension properties cannot have custom setters, we can still create extension functions that act as getters and setters for properties of the extended class.

Code:

Explanation:

  1. In this example, we define a Book class representing a book with a title and a pageCount.
  2. We create an extension property halfPageCount for the Book class. The extension property enables us to access half of the book's page count as if it were a member property of the Book class.
  3. The custom getter of halfPageCount is defined using the get() function.
  4. In the main() function, we create a Book instance named book with the title "The Great Gatsby" and 180 pages. We print the original title and page count using the title and pageCount properties.
  5. Next, we update the pageCount property of the book object to 250. We then access the halfPageCount extension property and print the updated page count. The output shows that half of the updated page count (250) is 125.
  6. Finally, we use the halfPageCount extension property again and print the half-page count of the book, which is still 125.

This example demonstrates how extension properties provide additional computed properties to existing classes without modifying their original implementation. The halfPageCount extension property allows us to conveniently access half of the book's page count, enhancing the code's readability and conciseness.

Companion Object Extensions

A companion object is defined within a class and marked with the companion keyword. It allows calling class member functions directly using the class name, similar to static methods in Java.

A class containing a companion object can also have extension functions and properties for that companion object.

Code:

Output:

Explanation:

In this example, we have a class MyClass with a companion object. The companion object contains a function greeting(), which is called using the class name.

We also define an extension function hello() specifically for the companion object. It is called using MyClass.hello() and prints a message related to the companion object.

The example demonstrates how companion objects allow organizing related functions within a class, and extension functions in Kotlin enable adding functionalities to the companion object.

Scope of Extensions

Extensions are additional functionalities that can be added to existing classes without modifying their source code. When defining extension functions in Kotlin, it is common to declare them at the top level, directly under packages.

Code:

To use the extension outside its declaring package, you need to import it at the call site.

Code:

Output:

Explanation:

In this code snippet, we define an extension function getListLength() for the List<String> class in the org.example.definitions package. The extension function adds new behavior to the List<String> class, allowing us to perform a specific action on lists of strings.

To use the extension function getListLength() in the org.example.usage package, we import the function using import org.example.definitions.getListLength.

Within the main() function, we call the getListLength() extension function on the list, which performs the action defined in the extension function.

The getListLength() extension function can provide any specific behavior that we define for lists of strings. In this example, since the function implementation is represented as /*...*/, the actual behavior is not shown. You would provide the actual logic inside the extension function to perform the desired action on the list of strings.

Declaring Extensions as Members

Extensions of one class can be created inside another class, creating an extension inside another class with multiple implicit receivers. The members of these objects can be accessed without a qualifier. In this context, the dispatch receiver refers to the instance of the class in which the extension is declared, while the extension receiver refers to the instance of the receiver type of the extension method.

Code:

When a name conflict arises between the members of a dispatch receiver and an extension receiver, the extension receiver takes precedence. To refer to the member of the dispatch receiver, you can use the qualified this syntax.

Code:

Note on Visibility

Extensions in Kotlin follow the same visibility rules as regular functions declared in the same scope. For instance:

  1. An extension declared at the top level of a file can access other private top-level declarations in the same file.
  2. However, if an extension is declared outside its receiver type, it cannot access the receiver's private or protected members.

Conclusion

  1. Extensions are declared using the following syntax:

  2. They are declared outside the class, either at the top level or inside another object or class.

  3. Extension functions in Kotlin can be used just like regular member functions, even though they are not directly declared within the class.

  4. Extension functions in Kotlin can be called on nullable types, allowing convenient operations on nullable objects without explicit null checks.

  5. While extensions can access public members of the receiver class, they cannot access private or protected members, ensuring proper encapsulation.

  6. Extensions utilize the same visibility modifiers as regular functions declared in the same scope.

  7. Extension functions in Kotlin are resolved statically based on the expression type at the call site, ensuring predictability and avoiding runtime polymorphism.