Preface
The 2017 Google IO Conference announced the use of Kotlin as the official development language of Android. Compared with the typical JAVA language of facial objects, Kotlin is a new functional programming language, and some people call it the Swift language of the Android platform.
Let's first look at the comparison between Java and Kotiln implementing the same function:
// JAVA, more than 20 lines of code, full of meaningless code such as findViewById, type conversion, anonymous internal classes public class MainJavaActivity extends Activity { @Override public void onCreate(@Nullable Bundle savedInstanceState) { (savedInstanceState); setContentView(.activity_main); TextView label = (TextView) findViewById(); Button btn = (Button) findViewById(); ("hello"); (new () { @Override public void onClick(View v) { ("Glen","onClick TextView"); } }); (new (){ @Override public void onClick(View v) { ("Glen","onClick Button"); } }); } }
Let's look at Kotlin again
// Kotlin, without the redundant findViewById, we can directly operate on the resource id, and we do not need to declare anonymous internal class. We pay more attention to the implementation of the function itself and abandon the complex formatclass MainKotlinActivity:Activity() { override fun onCreate(savedInstanceState: Bundle?) { (savedInstanceState) setContentView(.activity_main) ("hello") { ("Glen","onClick TextView") } { ("Glen","onClick Button") } } }
To implement these, you need to use Kotlin's extension functions and higher-order functions. This article mainly introduces extension functions.
What is an extension function?
Extended function count refers to adding a new behavior to a class, and we even do not have access to the code of this class. This is a way to extend on classes that lack useful functions, and Kotlin can do things that are interesting for us that Java cannot.
In Java, many tool classes with static methods are usually implemented. One advantage of extending functions in Kotlin is that we do not need to pass the entire object as a parameter when calling the method. It acts like it belongs to this class, and we can use this keyword and call all public methods.
1. Kotlin extension functions and extension properties (Kotlin Extensions)
Kotlin can extend new functionality of a class without inheriting it, or use design patterns like "Decorator" for any class. These are achieved through special declarations called "extensions". Kotlin extension declaration supports both extension functions and extension properties. This article mainly discusses extension functions, and the mechanism of extension properties is similar.
The declaration of the extension function is very simple, its keyword is. In addition, we need a "recievier type" to prefix it. Taking the MutableList<Int> class as an example, now extend a swap method for it, as follows:
fun MutableList<Int>.swap(index1:Int,index2:Int) { val tmp = this[index1] this[index1] = this[index2] this[index2] = tmp }
MutableList<T> is the List container class in the basic library collection provided by kotlin. Here it is the "recipient type" in the declaration. As the declaration keyword, swap is the name of the extension function, and the rest is no different from Kotlin declaring an ordinary function.
To add in addition, Kotlin's this syntax is more flexible than JAVA. This in the extended function body represents the recipient type object.
If we want to call this extension function, we can do this:
fun use(){ val list = mutableListOf(1,2,3) (1,2) }
2. How is Kotlin extension function implemented
The call to an extension function looks as natural as a native method and is very easy to use, but will such a method bring performance constraints? It is necessary to explore how Kotlin implements extension functions. It is quite difficult to directly analyze Kotlin source code. Fortunately, Android Studio provides some tools. We can view the bytecode file converted by Kotlin language through the Kotlin ByteCode instruction. Still take MutableList<Int>, swap as an example, and the file after converting it to bytecode is as follows:
// ================com/example/glensun/demo/extension/ ================= // 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(<>, 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/ (Ljava/lang/Object;Ljava/lang/String;)V L1 LINENUMBER 8 L1 ALOAD 0 ILOAD 1 INVOKEINTERFACE java/util/ (I)Ljava/lang/Object; CHECKCAST java/lang/Number INVOKEVIRTUAL java/lang/ ()I ISTORE 3 L2 LINENUMBER 9 L2 ALOAD 0 ILOAD 1 ALOAD 0 ILOAD 2 INVOKEINTERFACE java/util/ (I)Ljava/lang/Object; INVOKEINTERFACE java/util/ (ILjava/lang/Object;)Ljava/lang/Object; POP L3 LINENUMBER 10 L3 ALOAD 0 ILOAD 2 ILOAD 3 INVOKESTATIC java/lang/ (I)Ljava/lang/Integer; INVOKEINTERFACE java/util/ (ILjava/lang/Object;)Ljava/lang/Object; POP L4 LINENUMBER 11 L4 RETURN L5 LOCALVARIABLE tmp I L2 L5 3 LOCALVARIABLE $receiver Ljava/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\u0006\u0010\u0005\u001a\u00020\u0003\u00a8\u0006\u0006"}, d2={"swap", "", "", "", "index1", "index2", "production sources for module app"}) // compiled from: } // ================META-INF/production sources for module app.kotlin_module =================
The bytecode here is quite intuitive. What is even more surprising is that Android Studio also has the ability to convert bytecode into JAVA file. Click the Decompile button above to get the following JAVA code:
import ; import ; import ; import ; @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\u0010\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) { ($receiver, "$receiver"); int tmp = ((Number)$(index1)).intValue(); $(index1, $(index2)); $(index2, (tmp)); } }
From the obtained JAVA file analysis, the implementation of the extension function is very simple. It does not modify the member of the recipient type, but is only implemented through static methods. In this way, although we don't have to worry about the additional performance consumption of the extension function, it will not bring performance optimization.
3. More complex situations
Let’s discuss some more special situations below.
3.1 When inheritance occurs, since the extension function is essentially a static method, it will execute calls strictly according to the parameter type, and will not prioritize or actively execute the parent class methods, as shown in the following example:
open class A class B:A() fun () = "a" fun () = "b" fun printFoo(a:A){ println(()) } println(B())
The output result of the above example is a, because the parameter type of the extension function is A, it will execute function calls strictly according to the parameter type.
3.2 If the extension function conflicts with existing class members, kotlin will use class members by default. This step is processed during the compilation period. The generated bytecode is a method that calls class members, as shown in the following example:
class C{ fun foo() {println("Member")} } fun () {println("Extension")} println(C().foo())
The above example will output Member. Kotlin does not allow extension of an existing member, and the reason is easy to understand. We do not want extension functions to become a vulnerability to call three-party SDKs, but this is possible if you try to create extension functions using overloaded methods.
3.3 Kotlin strictly distinguishes the entry parameter types that may be empty and not empty, and is also applied in the extended function. In order to declare a recipient type that may be empty, you can refer to 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 }
3.4 Sometimes we also hope to add an extension function similar to JAVA "static function". At this time, we need to use the "companion object" to achieve it, as shown in the following example:
class D{ companion object{ val m = 1 } } fun (){ println("$m in extension") } ()
The above example will output 1 in extension. Note that when calling the extension function foo here, an instance of class D is not required, similar to the static method of JAVA.
3.5 If we pay attention to the previous example, we will find that this syntax of kotlin is different from JAVA, and its scope of use is more flexible. We only take the extension function as an example. When this is called in the extension function, it refers to an instance of the recipient type. So if this extension function is declared inside a class, how can we obtain an instance of the class through this? You can refer to the following example:
class E{ fun foo(){ println("foo in Class E") } } class F{ fun foo(){ println("foo in Class F") } fun E.foo2(){ () this@() } } E().foo2()
This specifies this syntax of kotlin is used here. The keyword is @, followed by the specified type. The output result of the above example is
foo in Class E foo in Class F
4. Extend the scope of the function
Generally speaking, we are used to defining extension functions directly within packages, for example:
package fun MutableList<Int>.swap(index1:Int,index2:Int) { val tmp = this[index1] this[index1] = this[index2] this[index2] = tmp }
In this way, the extension function can be called directly in the same package. If we need to call the extension function across packages, we need to specify it through import. Taking the above example as an example, this extension function can be specified through import, or all extension functions in the package can be introduced through import .*. Thanks to the automatic association capability of Android Studio, we usually do not need to actively enter the import command.
Sometimes, we also define extension functions inside the class, for example:
class G { fun (){ println("foo in Class G") } }
Here () is an extension function defined inside class G. In this extension function, we directly use the Int type as the recipient type, because we define the extension function inside the class. Even if we set the access permission to public, it can only be accessed in the class or subclass of the class. If we set the access permission to private, then this extension function cannot be accessed in the subclass.
5. The actual application of extended functions
5.1 Utils tool class
In JAVA, we are used to naming tool classes as *Utils, such as FileUtils, StringUtils, etc. This is also the case. When calling these methods, you always feel that these class names are in the way, for example:
// Java (list, (list, (otherList)), (list)); (list));
With static references, you can make the situation look better, for example:
// Java swap(list, binarySearch(list, max(otherList)), max(list));
However, this does not have the automatic association prompt from the IDE, and the body of the method call seems unclear. It would be great if it could be done as follows:
// Java ((()), ());
However, list is the default basic class of JAVA. In the JAVA language, if inheritance is not used, it is definitely impossible to do this. In Kotlin, it can be implemented with the help of extension functions.
5.2 Android View Glue Code
Going back to the initial example, for Android development, you must be familiar with the method findViewById(). In order to obtain a View object, we have to call findViewById() first and then perform type conversion. Such meaningless glue code makes the Activity or Fragment look extremely bloated, for example:
// JAVA public class MainJavaActivity extends Activity { @Override public void onCreate(@Nullable Bundle savedInstanceState) { (savedInstanceState); setContentView(.activity_main); TextView label = (TextView) findViewById(); Button btn = (Button) findViewById(); ("hello"); (new () { @Override public void onClick(View v) { ("Glen","onClick TextView"); } }); (new (){ @Override public void onClick(View v) { ("Glen","onClick Button"); } }); } }
We consider using extended functions to combine generics to avoid frequent type conversions. The extended functions are defined as follows:
//kotlin fun <T : View> (@IdRes id: Int): T { return findViewById(id) as T }
When calling, as follows:
// Kotlin ... TextView label = find(); Button btn = find(); ...
But we still need to obtain label, btn, such meaningless intermediate variables. If you extend it on the Int class, you can directly operate on .*, which is more direct. Combined with higher-order functions, the function definition is as follows:
//Kotlin fun (str:String){ val label = find<TextView>(this).apply { text = str } } fun (click: ()->Unit){ val tmp = find<View>(this).apply { setOnClickListener{ click() } } }
We can call this:
//Kotlin ("hello") { ("Glen","onClick TextView") } { ("Glen","onClick Button") }
Usually these extension functions can be placed in the base class. According to the scope knowledge of the extension functions, we can call these methods in all subclasses, so the activity of kotlin can be written as:
// Kotlin class MainKotlinActivity:KotlinBaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { (savedInstanceState) setContentView(.activity_main) ("hello") { ("Glen","onClick TextView") } { ("Glen","onClick Button") } } }
From the original JAVA redundant more than 20 lines of code, it is streamlined to only 3 lines of code, and the code is more readable and more intuitive. This is the powerful power of the functional programming language Kotlin.
Summarize
The above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support.