Preface
Kotlin is a programming language designed and open sourced by JetBrains, a famous IDE manufacturer that has developed IntelliJ IDEA, Android Studio, PyCharm and other IDEs. The Kotlin project launched in July 2011 was deeply influenced by "Effective Java". It was not until February 15, 2016 that the first official stable version of Kotlin v1.0 was officially released. At the 2017 Google I/O Developer Conference, Google announced that Kotlin has become a first-level language for Android development, and Kotlin has become a "released".
In Kotlin, it is interesting to define methods, not only because the keyword of the method is fun (first few characters of function), but because you will be surprised to find that it allows us to define methods in methods. as follows
fun methodA() { fun methodB() { } methodB() //valid } //methodB() invalid
in
- methodB is defined in the method body of methodA, that is, methodB is called a local method or a local function
- methodB can only be called in methodA
- Calling methodB outside the methodA method will cause a compilation error
Since Kotlin supports local methods, what special place should it use?
First of all, its characteristics are just like its name, partial, which means it has an incomparable, smaller scope limited capability. A small range of availability is guaranteed and the possibility of potential irrelevant calls is isolated.
As a golden rule in programming, the smaller the method, the better. Compared with the lengthy vertical code snippet, dividing it into small local methods with single functions according to responsibilities, and finally organizing it to call it will make our code appear more organized and clear.
As a programmer, curiosity should be one of his traits. We should want to study what the principle of implementing local methods is, at least we have never seen this concept in the Java era.
In fact, if you study this matter carefully, there are still many details. Because the local method can capture external variables or not capture external variables.
The following is a case of capturing external variables
fun outMethodCapture(args: Array<String>) { fun checkArgs() { if (()) { println("innerMethod check args") Throwable().printStackTrace() } } checkArgs() }
Among them, the local method checkArgs captures the parameter args of outMethodCapture.
Therefore, it is not difficult to understand that the external variables are not captured. As follows, checkArgs handles args by parameters.
fun outMethodNonCapture(args: Array<String>) { fun checkArgs(args: Array<String>) { if (()) { println("outMethodNonCapture check args") Throwable().printStackTrace() } } checkArgs(args) }
First, let’s analyze the implementation principle of the local method of capturing variables
public static final void outMethodCapture(@NotNull final String[] args) { (args, "args"); <undefinedtype> checkArgs$ = new Function0() { // $FF: synthetic method // $FF: bridge method public Object invoke() { (); return ; } public final void invoke() { Object[] var1 = (Object[])args; if( == 0) { String var2 = "innerMethod check args"; (var2); (new Throwable()).printStackTrace(); } } }; checkArgs$.invoke(); }
As the above implementation principle is, local method implementation is actually to implement an instance of an anonymous internal class and then call it again. For local methods that are not captured, we must first decompile and get the corresponding Java code
public static final void outMethodNonCapture(@NotNull String[] args) { (args, "args"); <undefinedtype> checkArgs$ = ; checkArgs$.invoke(args); }
What we get is an incomplete code, and at this time we need to go to the project project and analyze it in combination with some corresponding class files. First we find a file like MainKt$outMethodCapture$ (its class file is in accordance with the "file name$ method name$ internal class number" rule).
Use the javap method to decompile and analyze the file again, and note that the $ symbol needs to be processed simply.
➜ KotlinInnerFunction javap -c "MainKt\$outMethodNonCapture\$" Compiled from "" final class MainKt$outMethodNonCapture$1 extends implements .Function1<[], > { public static final MainKt$outMethodNonCapture$1 INSTANCE; public invoke(); Code: 0: aload_0 1: aload_1 2: checkcast #11 // class "[Ljava/lang/String;" 5: invokevirtual #14 // Method invoke:([Ljava/lang/String;)V 8: getstatic #20 // Field kotlin/:Lkotlin/Unit; 11: areturn public final void invoke([]); Code: 0: aload_1 1: ldc #23 // String args 3: invokestatic #29 // Method kotlin/jvm/internal/:(Ljava/lang/Object;Ljava/lang/String;)V 6: aload_1 7: checkcast #31 // class "[Ljava/lang/Object;" 10: astore_2 11: aload_2 12: arraylength 13: ifne 20 16: iconst_1 17: goto 21 20: iconst_0 21: ifeq 44 24: ldc #33 // String outMethodNonCapture check args 26: astore_2 27: getstatic #39 // Field java/lang/:Ljava/io/PrintStream; 30: aload_2 31: invokevirtual #45 // Method java/io/:(Ljava/lang/Object;)V 34: new #47 // class java/lang/Throwable 37: dup 38: invokespecial #51 // Method java/lang/Throwable."<init>":()V 41: invokevirtual #54 // Method java/lang/:()V 44: return MainKt$outMethodNonCapture$1(); Code: 0: aload_0 1: iconst_1 2: invokespecial #61 // Method kotlin/jvm/internal/Lambda."<init>":(I)V 5: return static {}; Code: 0: new #2 // class MainKt$outMethodNonCapture$1 3: dup 4: invokespecial #80 // Method "<init>":()V 7: putstatic #82 // Field INSTANCE:LMainKt$outMethodNonCapture$1; 10: return }
The above class is actually relatively simple, and more importantly, this is a singleton implementation. Because compared with the capture situation, the generation of anonymous internal classes and the creation of instances will be reduced, and the theoretical cost will be smaller.
Considering the above comparison, it would be more recommended if you are using local methods that do not capture external variables.
Notes on usage
Yes, there is a precaution when using local methods, which is a rule agreement, that is, it needs to be defined before use, otherwise an error will be reported, as shown below
fun outMethodInvalidCase(args: Array<String>) { checkArgs()//invalid unresolved reference fun checkArgs() { if (()) { println("innerMethod check args") Throwable().printStackTrace() } } checkArgs()//valid }
However, there are still some problems when defining local methods first and then using them. This problem is mainly reflected in the readability of the code.
Just imagine, if you enter a method and see a series of local methods, it may be more or less awkward.
But just imagine, since there is such a problem, why is it still designed like this? First, let's take a look at a small example
0fun outMethodInvalidCase(args: Array<String>) { checkArgs(args) var a = 0 //the reason why it's unresolved fun checkArgs(args: Array<String>) { if (()) { println("outMethodNonCapture check args") Throwable().printStackTrace() () } } }
Because local methods can capture local variables, checkArgs captures local variable a. When the first line of code checkArgs is called, checkArgs seems to be defined, but the second line has not been executed yet, resulting in compilation problems.
At present, both capture variables and non-capture local methods are used consistently, and they need to be defined first and then used.
Regarding the local methods in Kotlin, we can try to achieve the purpose of limiting scope and splitting methods. When using them, try to choose non-capturing local methods.
Summarize
The above is the entire content of this article. I hope that the content of this article has a 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.