Improving code readability with Kotlin extensions

On the eve of the start of the course “Kotlin Backend Developer” we invite future students and everyone who wants to watch an open lesson on the topic “Revisiting the” 12 factors “: creating a modern microservice on Kotlin”.

We also share with you the traditional translation of useful material.


Kotlin Terminology: Extension-Functions and Extension-Properties

When you used an API, did you want to add new functions or properties to it?

To solve this problem, you can use inheritance (create a new class based on an existing one) or a function that receives an instance of the class as an input parameter. In the Java programming language, this task is usually accomplished using the class Utilsbut it is not visible when using the code completion feature, which makes it difficult to find and makes using this class less intuitive. Both of these approaches can be used to solve our problem, but neither of them gives clear and readable code.

Fortunately, Kotlin comes to the rescue with extension functions and extension properties… They allow you to add the required functionality to a class without the need to use inheritance or create a function that takes an instance of the class as a parameter. In Android Studio, these extensions are visible when using the code completion feature, unlike the corresponding counterpart in the Java language. Extensions can be used in third party libraries, Android SDK, or custom classes.

Read on if you want to know how to make your code more readable with extensions!

Using extension functions

Let’s imagine you have a class Dogdescribing a dog that has a name, breed, and age.

<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

data class Dog(val name: String, val breed: String, val age: Int)

Let’s say an animal shelter wants to expand the class Dogto have a function that prints information about the dog in case someone wants to take it for themselves. To do this, we implement an extension function, which is declared as a regular function, but with one peculiarity: the name of the extended class with a dot is added before the function name. In the function code, you can use the function word this to access the receiving object, and you have access to all members of the receiving class within the function.

You can call the function printDogInformation() just like you call any other function in the class Dog

<!-- Copyright 2019 Google LLC. 
   SPDX-License-Identifier: Apache-2.0 -->

fun main() {
  val dog = Dog("Jen", "Pomeranian", 13)
  dog.printDogInformation()
}

Calling extension functions from Java code

Extension functions are not part of the extensible class, so when we try to call them from Java, we will not find them among the methods of the extensible class. As we’ll see later, extensions are decompiled into static methods of the file in which you defined them, and are given an instance of the class being extended as an input parameter. This is what an extension function call would look like printDogInformation() from Java:

<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

DogExtensionKt.printDogInformation(dog);

Extension functions for undefined types

Extensions can also be used to work with nullable types. Instead of checking for null before calling the extension function, we can create an extension function for the nullable type and implement a check for null in the code of this function. This is how the function will look like printInformation()that uses a nullable type.

<!-- Copyright 2019 Google LLC. 
   SPDX-License-Identifier: Apache-2.0 -->

fun Dog?.printInformation() {
  if (this == null){
    println("No dog found")
    return
  }
  println("Meet ${this.name} a ${this.age} year old ${this.breed}")
}

As you can see, you don’t need to check for null before calling the function. printInformation()

<!-- Copyright 2019 Google LLC. 
   SPDX-License-Identifier: Apache-2.0 -->

fun main() {
  val dog : Dog? = null
  dog.printInformation() // prints "No dog found"
}

Using extension properties

Imagine that our animal shelter also needs to know if a dog is age appropriate for transferring to a new family. To do this, we will implement the isReadyToAdopt extension property, which will indicate whether the dog is over 1 year old.

<!-- Copyright 2019 Google LLC. 
   SPDX-License-Identifier: Apache-2.0 -->

val Dog.isReadyToAdopt: Boolean
get() = this.age > 1

You can access this extension property the same way you access any other property in the class. Dog

<!-- Copyright 2019 Google LLC. 
   SPDX-License-Identifier: Apache-2.0 -->

fun main() {
  val dog1 = Dog("Jen", "Pomeranian", 13)
  if(dog1.isReadyToAdopt){
    print("${dog1.name} is ready to be adopted")
  }
}

Overriding extension functions

Cannot override an existing member function of a class. If you define an extension function with the same signature as an existing member function of the class, then the member function will always be called, since which function is called depends on the declared static type of the variable, and not on the value type of this variable. during code execution. For example, you cannot extend the function toUppercase()applied to the string type (String), but you can extend the function convertToUppercase()

The consequence of this behavior can be seen when trying to extend a library type that you do not own, when the library owner added a method to the library with the same signature as your extension. In this case, the library extension will be called, and you will only receive information that your extension function has become an unused method.

Internal structure of extensions

We can decompile the function printDogInformation() in Android Studio. To do this, select the item in the menu Tools / Kotlin / Show Kotlin Bytecode (Tools / Kotlin / Show Kotlin Bytecode) and click the button Decompile (Decompilation). Decompiled method printDogInformation() would look like this:

<!-- Copyright 2019 Google LLC. 
   SPDX-License-Identifier: Apache-2.0 -->

public static final void printDogInformation(@NotNull Dog $this$printDogInformation) {
  Intrinsics.checkParameterIsNotNull($this$printDogInformation, "$this$printDogInformation");
  String var1 = "Meet " + $this$printDogInformation.getName() + ", a " + $this$printDogInformation.getAge() + " year old " + $this$printDogInformation.getBreed();
  boolean var2 = false;
  System.out.println(var1);
}

In reality, extension functions are ordinary static functions, which are passed an instance of the receiving class as an input parameter. They have no other connection with the receiving classes. This is why there are no backing fields – such functions do not actually add members to the class.

Conclusion

In general, extensions are a useful tool that should be used carefully. Using them, follow the guidelines below and your code will become clearer and more readable.

Points to remember:

  • Extensions are converted to static functions.

  • Class member functions are always preferred.

  • Take your dog from the shelter!

Good luck in programming!


Learn more about the course “Kotlin Backend Developer”.

View an open lesson on the topic “Revising the” 12 factors “: creating a modern microservice on Kotlin”

GET A DISCOUNT

Similar Posts

Leave a Reply