Kotlin Vocabulary | use the extensions in kotlin to improve code readability

Keywords: Android kotlin

Have you ever wanted to add new functions or properties to the API of a class?

You can usually solve this problem by inheriting the class or creating a new function that takes an instance of the class as a parameter. Java programming language usually uses Utils class to solve such problems, but this method does not support automatic code completion, which will make the written code more difficult to find and not intuitive to use. Although both methods can solve the problem, it is still difficult to write concise and readable code after all.

Fortunately, Kotlin took Extension functions and properties To "save" us. It allows you to add functionality to a class without using inheritance or creating functions that receive class instances. Unlike programming languages such as Java, the automatic completion function of Android Studio supports Kotlin extension. The extension can be used for third-party code libraries, Android SDK and user-defined classes.

Read on and explore how extensions can improve the readability of your code.

Use of extension functions

Let's assume that you have a class called Dog, which has three attributes: name, feed and age.

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

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

The adoption agency hopes to expand the Dog class to have the function of printing Dog information, so as to facilitate interested people to adopt. To this end, we implement an extension function in the same way as an ordinary function, except that you need to add the class name to be extended and a "." symbol in front of the function name. In the function body, you can use this to reference the receiver object, and all member objects of the class to which the receiver belongs can be accessed within the scope of the function.

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

fun Dog.printDogInformation() {
  println("Meet ${this.name}, a ${this.age} year old ${this.breed}")
}

Calling the printDogInformation() method is the same as calling functions in other Dog classes.

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

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

Calling extension function from Java code

The extension function is not part of the class we want to extend, so when we try to call this method in the Java language, we can't find it in other methods of the class. As we'll see later, the extension decompiles into a static method in its defined file and receives an instance of the class we want to extend as a parameter. The following is the example code for calling printDogInformation() extension function in Java.

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

DogExtensionKt.printDogInformation(dog);

Define extension functions for nullable types

You can also define extension functions for nullable types. Instead of null checking before calling the extension function, we can directly define the extension function for nullable type and let the extension function itself contain null checking. The following is the sample code for defining the extension function printInformation() for 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}")
}

You can see that null checking is not required when calling the printInformation() function.

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

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

Use of extended attributes

As an adoption agency, it may also want to know whether the dog's age meets the conditions for adoption. Therefore, we have implemented an extended attribute called isReadyToAdopt to check whether the dog's age is over 1 year old.

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

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

Calling this property is the same as calling other properties in the Dog class.

<!-- 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")
  }
}

Replication in extension function

You cannot duplicate existing member functions in a class in an extension function. If the extension function you define has the same signature as the existing member function, only the existing member function will be called normally, because the function call depends on the static type when the variable is declared, not the runtime type stored in the value of the variable. For example, you cannot extend the toUppercase() method on a String, but you can extend a method called convertToUppercase().

When you extend a type that does not belong to your definition, and there is an extension function with the same signature as your extension in the code base of the type, the above behavior will show consequences. In this case, the extension function in the code base will be called, and the only information you get is that the extension function you defined has become an unused method.

working principle

We can Decompile printDogInformation() in Android Studio by clicking the Decompile button in Tools/Kotlin/Show Kotlin Bytecode. The following is the code generated after decompiling printDogInformation():

<!-- 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 fact, the extension function looks like an ordinary static function that receives a class instance as a parameter, and has no other connection with the receiving class. That's why the code doesn't have Backing Fields -- they don't actually insert any members into the class.

summary

In general, extensions are a useful tool. Think carefully when using extensions. Keep the following tips in mind to make your code more intuitive and readable.

Tips:

  • Extensions are statically distributed;
  • Member functions are always "winners";
  • Adopt a dog!

Have a nice coding!

Posted by dhorn on Wed, 10 Nov 2021 19:34:50 -0800