SoFunction
Updated on 2025-04-11

In-depth study of local methods in Kotlin

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.