Kotlin is a new functional programming language, which is also known as Swift for The Android platform, compared to the typical JAVA language for face objects.

In this paper, byTencent BuglyPublished inTencent Cloud + community

Let’s take a look at the comparison between Java and Kotiln, which implements the same functionality:

// JAVA, 20 + lines of code, full of findViewById, type conversion, Public class MainJavaActivity extends Activity {@override public void onCreate(@nullable Bundle) savedInstanceState) { super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);

        TextView label = (TextView) findViewById(R.id.label);
        Button btn = (Button) findViewById(R.id.btn);

        label.setText("hello");
        label.setOnClickListener(new View.OnClickListener() {           
            @Override
            public void onClick(View v) {
                Log.d("Glen"."onClick TextView"); }}); btn.setOnClickListener(new View.OnClickListener(){            
            @Override
            public void onClick(View v) {
                Log.d("Glen"."onClick Button"); }}); }}Copy the code

Look at Kotlin

// Kotlin, without the redundant findViewById, we can directly operate on resource ids and do not need to declare anonymous inner classes. Instead, we can focus on the implementation of the function itself, instead of the complex format class MainKotlinActivity:Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        R.id.label.setText("hello")
        R.id.label.onClick { Log.d("Glen"."onClick TextView") }
        R.id.btn.onClick { Log.d("Glen"."onClick Button")}}}Copy the code

This requires the help of Kotlin’s extension functions and higher-order functions, which this article focuses on.

1. Kotlin extension functions and Extensions (Kotlin Extensions)

Kotlin can extend new functionality of a class without inheriting the class, or use design patterns like “Decorator” for arbitrary classes. This is done through special declarations called extensions. The Kotlin extension declaration supports both extension functions and extension properties. This article focuses on extension functions, but the mechanism for implementing extension properties is similar.

The extension function declaration is very simple, its keyword is., and we need a “recievier type” as its prefix. Take the MutableList

class as an example, now extend it with a swap method as follows:

fun MutableList<Int>.swap(index1:Int,index2:Int) {
    val tmp = this[index1]
    this[index1] = this[index2]
    this[index2] = tmp
}
Copy the code

MutableList

is the List container class in the base library collection provided by Kotlin, which is declared as the “recipient type”. As a declared key, swap is the extension function name, and the rest is no different from Kotlin declaring a normal function.

As an added bonus, Kotlin’s this syntax is more flexible than JAVA’s, where this in the body of the extension function represents an object of recipient type.

If we wanted to call the extension function, we could do this:

fun use(){val list = mutableListOf(1,2,3) list.swap(1,2)}Copy the code

2. How is the Kotlin extension function implemented

The extension function calls seem as natural and comfortable to use as the native method, but are there performance constraints? It is important to explore how Kotlin implements the extension function. It is difficult to directly analyze Kotlin’s source code. However, Android Studio provides some tools to view Kotlin ByteCode files using the Kotlin ByteCode command. Take MutableList

,swap as an example. The file converted to bytecode is as follows:

/ / = = = = = = = = = = = = = = = = com/example/glensun/demo/extension/MutableListDemoKt class = = = = = = = = = = = = = = = = = / / class version 50.0 (50) // access flags 0x31 public final class com/example/glensun/demo/extension/MutableListDemoKt { // access flags 0x19  // signature (Ljava/util/List<Ljava/lang/Integer; >; II)V // declaration: void swap(java.util.List<java.lang.Integer>, int, int) public final static swap(Ljava/util/List; II)V @Lorg/jetbrains/annotations/NotNull; () // invisible, parameter 0 L0 ALOAD 0 LDC"$receiver"INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object; Ljava/lang/String;) V L1 LINENUMBER 8 L1 ALOAD 0 ILOAD 1 INVOKEINTERFACE java/util/List.get (I)Ljava/lang/Object; CHECKCAST java/lang/Number INVOKEVIRTUAL java/lang/Number.intValue ()I ISTORE 3 L2 LINENUMBER 9 L2 ALOAD 0 ILOAD 1 ALOAD  0 ILOAD 2 INVOKEINTERFACE java/util/List.get (I)Ljava/lang/Object; INVOKEINTERFACE java/util/List.set (ILjava/lang/Object;) Ljava/lang/Object; POP L3 LINENUMBER 10 L3 ALOAD 0 ILOAD 2 ILOAD 3 INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer; INVOKEINTERFACE java/util/List.set (ILjava/lang/Object;) Ljava/lang/Object; POP L4 LINENUMBER 11 L4 RETURN L5 LOCALVARIABLE tmp I L2 L5 3 LOCALVARIABLE$receiverLjava/util/List; L0 L5 0 LOCALVARIABLE index1 I L0 L5 1 LOCALVARIABLE index2 I L0 L5 2 MAXSTACK = 4 MAXLOCALS = 4 @Lkotlin/Metadata; (mv={1, 1, 7}, bv={1, 0, 2}, k=2, d1={"\u0000\u0012\n\u0000\n\u0002\u0010\u0002\n\u0002\u0010! \n\u0002\u0010\u0008\n\u0002\u0008\u0003\u001a \u0010\u0000\u001a\u00020\u0001*\u0008\u0012\u0004\u0012\u00020\u00030\u00022\u0006\u0010\u0004\u001a\u00020\u00032\u000 6\u0010\u0005\u001a\u00020\u0003\u00a8\u0006\u0006"}, d2={"swap".""."".""."index1"."index2"."production sources for module app"})  
// compiled from: MutableListDemo.kt

}
// ================META-INF/production sources for module app.kotlin_module =================
Copy the code

The bytecode here is pretty straightforward, but what’s even more surprising is that Android Studio also has the ability to convert bytecode to JAVA files. Click the Decompile button above to get JAVA code like this:

import java.util.List;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

@Metadata(
   mv = {1, 1, 7},
   bv = {1, 0, 2},
   k = 2,
   d1 = {"\u0000\u0012\n\u0000\n\u0002\u0010\u0002\n\u0002\u0010! \n\u0002\u0010\b\n\u0002\b\u0003\u001a \u0010\u0000\u001a\u00020\u0001*\b\u0012\u0004\u0012\u00020\u00030\u00022\u0006\u0010\u0004\u001a\u00020\u00032\u0006\u0 010 \ u0005 \ u001a \ u00020 \ u0003 ¨ \ u0006 \ u0006"},
   d2 = {"swap".""."".""."index1"."index2"."production sources for module app"}
)

public final class MutableListDemoKt {   
    public static final void swap(@NotNull List $receiver, int index1, int index2) {
      Intrinsics.checkParameterIsNotNull($receiver."$receiver");      
    int tmp = ((Number)$receiver.get(index1)).intValue();
      $receiver.set(index1, $receiver.get(index2));
      $receiver.set(index2, Integer.valueOf(tmp)); }}Copy the code

From the analysis of the resulting JAVA files, the implementation of the extension function is very simple, it does not modify the member of the recipient type, only through static methods. In this way, we don’t have to worry about the additional performance cost of extending the function, but it also doesn’t lead to performance optimization.

3. More complex situations

Let’s talk about some more special cases.

3.1 When inheritance occurs, the extension function is static in nature, so it will execute the call strictly according to the parameter type, and will not override or actively execute the parent class method, as shown in the following example:

open class A

class B:A()

fun A.foo() = "a"

fun B.foo() = "b"

fun printFoo(a:A){
   println(a.foo())
}

println(B())
Copy the code

The output of the above example is a, because the extension function’s input parameter type is A, it will execute the function call exactly as the input parameter type.

3.2 If an extension function conflicts with an existing class member, Kotlin will use the class member by default. This step of selection is handled at compile time, and the generated bytecode will be the method that calls the class member, as shown in the following example:

class C{    
    fun foo() {println("Member")}
}

fun C.foo() {println("Extension")}

println(C().foo())
Copy the code

The above example will print Member. Kotlin doesn’t allow extending an existing member, and it’s easy to understand why. We don’t want the extension function to be a loophole for calling a third-party SDK, but it works if you try to overload the extension function.

3.3 Kotlin’s strict distinction between possibly empty and non-empty input parameter types applies to extension functions as well. To declare a possibly empty receiver type, consider the following example:

fun <T> MutableList<T>? .swap(index1:Int,index2:Int){if(this == null){
        println(null)
        return
    } 

    val tmp = this[index1]    
    this[index1] = this[index2]    
    this[index2] = tmp
}
Copy the code

3.4 We sometimes wish to add extensions to JAVA’s “static functions” with the help of the Companion Object, as shown in this example:

class D{
    companion object{
        val m = 1
    }
}

fun D.Companion.foo(){
    println("$m in extension")
}

D.foo()
Copy the code

The above example outputs 1 in extension. Note that the extension function foo is called without an instance of class D, similar to JAVA’s static methods.

3.5 If we look at the previous examples, we will see that Kotlin’s this syntax is different from JAVA’s, and the scope is more flexible. Just take the extension function as an example. When this is called in an extension function, it refers to an instance of the recipient type. How do we get an instance of the class from this? Consider the following examples:

class E{    
    fun foo(){
        println("foo in Class E")
    }

}
class F{    
    fun foo(){
        println("foo in Class F")
    }

    fun E.foo2(){        
        this.foo()        
        [email protected]()
    }
}

E().foo2()
Copy the code

Kotlin’s this syntax is used, with the keyword @ followed by the specified type, and the output from the above example is

foo in Class E
foo in Class F
Copy the code

4. Extend the scope of the function

In general, we tend to define extension functions directly within packages, for example:

package com.example.extension

fun MutableList<Int>.swap(index1:Int,index2:Int) {
    val tmp = this[index1]
    this[index1] = this[index2]
    this[index2] = tmp
}
Copy the code

Thus, in the same package can call to extension function directly, if we need to extend function called package, we need to import to indicate, in the above example, for example, can pass the import com. Example. The extension. Swap to specify the extension function, You can also import com.example.extension.* to indicate the import of all extension functions within the package. Thanks to Android Studio’s automatic association capabilities, we usually don’t need to actively enter import commands.

Sometimes we define extension functions inside a class, for example:

class G {
    fun Int.foo(){
        println("foo in Class G")}}Copy the code

Here int.foo () is an extension function defined inside class G. In this extension function, we use Int directly as the recipient type, because we defined the extension function inside the class. Even if we set access to public, it can only be accessed within that class or a subclass of that class. If we set access to private, the extension function cannot be accessed in subclasses.

5. Practical application of extension functions

5.1 Utils Tool Classes

In JAVA, it is customary to name utility classes *Utils, such as FileUtils,StringUtils, etc. The famous Java.util.Collections is also implemented this way. When calling these methods, the class name is always in the way, for example:

// Java

Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list));
Collections.max(list));
Copy the code

Static references can make things look better, for example:

// Java

swap(list, binarySearch(list, max(otherList)), max(list));
Copy the code

But without the IDE’s automatic associative cues, the body of the method call is unclear. It would be nice if we could do something like this:

// Java

list.swap(list.binarySearch(otherList.max()), list.max());
Copy the code

But list is the default base class in JAVA, and you can’t do this in the JAVA language without inheritance, which Kotlin can do with extension functions.

5.2 Android View Glue code

Going back to the original example, the findViewById() method is familiar to Android development. In order to retrieve a View object, we have to call findViewById() and then perform a conversion. Such meaningless glue code makes an Activity or Fragment look bloated, for example:

// JAVA

public class MainJavaActivity extends Activity {    
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {        
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView label = (TextView) findViewById(R.id.label);
        Button btn = (Button) findViewById(R.id.btn);

        label.setText("hello");
        label.setOnClickListener(new View.OnClickListener() {            
            @Override
            public void onClick(View v) {
                Log.d("Glen"."onClick TextView"); }}); btn.setOnClickListener(new View.OnClickListener(){           
             @Override
            public void onClick(View v) {
                Log.d("Glen"."onClick Button"); }}); }}Copy the code

We consider using extension functions in combination with generics to avoid frequent type conversions. Extension functions are defined as follows:

//kotlin

fun <T : View> Activity.find(@IdRes id: Int): T {    
    return findViewById(id) as T
}
Copy the code

When called, it looks like this:

// Kotlin ... TextView label = find(R.id.label); Button btn = find(R.id.btn); .Copy the code

If we extend the Int class, we can directly operate on r.id.*, which is more direct. If we add the higher-order function, the function definition is as follows:

//Kotlin

fun Int.setText(str:String){
    val label = find<TextView>(this).apply {
        text = str
    }
}

fun Int.onClick(click: ()->Unit){
    val tmp = find<View>(this).apply {
        setOnClickListener{
            click()
        }
    }
}
Copy the code

We can call this:

//Kotlin

R.id.label.setText("hello")
R.id.label.onClick { Log.d("Glen"."onClick TextView") }
R.id.btn.onClick { Log.d("Glen"."onClick Button")}Copy the code

Generally, these extension functions can be placed in the base class. Based on the scope knowledge of extension functions, we can call these methods in all subclasses, so Kotlin’s Activity can be written as:

// Kotlin
class MainKotlinActivity:KotlinBaseActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        R.id.label.setText("hello")
        R.id.label.onClick { Log.d("Glen"."onClick TextView") }
        R.id.btn.onClick { Log.d("Glen"."onClick Button")}}}Copy the code

The powerful power of the functional programming language Kotlin has been reduced from more than 20 redundant lines of Code in JAVA to only three lines of code, and the code is more readable and intuitive.


Question and answer

What is Kotlin’s “receiver”?

reading

Why do you need Kotlin

Handq Android thread deadlock monitoring and automated analysis practices

Why is Kotlin more readable than Java?


Has been authorized by the author tencent cloud + community release, the original link: https://cloud.tencent.com/developer/article/1146533?fromSource=waitui

Welcome toTencent Cloud + communityOr pay attention to the wechat public account (QcloudCommunity), the first time to get more massive technical practice dry goods oh ~