欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Kotlin中局部方法的深入探究

程序员文章站 2022-06-19 19:49:00
前言 kotlin是由开发过intellij idea、android studio、pycharm等ide的著名ide厂商jetbrains公司设计并开源的编程语言。2...

前言

kotlin是由开发过intellij idea、android studio、pycharm等ide的著名ide厂商jetbrains公司设计并开源的编程语言。2011年7月推出的kotlin项目深受《effective java》的影响,直到2016年2月15日第一个官方稳定版本kotlin v1.0才正式发布,2017年google i/o开发者大会中,google宣布kotlin成为android开发的一级语言,kotlin “转正”。

在kotlin中,定义方法很有趣,不仅仅因为方法的关键字是fun(function前几个字符),还是因为你会惊奇的发现,它允许我们在方法中定义方法。如下

fun methoda() {
 fun methodb() {

 }
 methodb() //valid
}

//methodb() invalid

其中

  • methodb定义在methoda的方法体中,即methodb被称为局部方法或局部函数
  • methodb只能在methoda中方法调用
  • methodb在methoda方法外调用,会引起编译错误

既然kotlin支持了局部方法,相比它应该有什么特殊的用武之地呢

首先它的特点还是像它的名字一样,局部,这就意味着它有着无可比拟的更小范围的限定能力。保证了小范围的可用性,隔绝了潜在的不相关调用的可能。

作为编程中的金科玉律,方法越小越好,相比纵向冗长的代码片段,将其按照职责切分成功能单一的小的局部方法,最后组织起来调用,会让我们的代码显得更加的有条理和清晰。

作为一个程序员,好奇应该是他的特质之一,我们应该会想要研究一下,局部方法的实现原理是什么,至少我们在java时代从来没有见过这种概念。

其实这件事仔细研究起来,还是有不少细节的。因为这其中局部方法可以捕获外部的变量也可以不捕获外部的变量。

下面就是捕获外部变量的一种情况

fun outmethodcapture(args: array<string>) {
 fun checkargs() {
 if (args.isempty()) {
  println("innermethod check args")
  throwable().printstacktrace()
 }
 }
 checkargs()
}

这其中,局部方法checkargs捕获了outmethodcapture的参数args。

所以,不捕获外部变量的情况也不难理解,如下,即checkargs处理args都是通过参数传递的。

fun outmethodnoncapture(args: array<string>) {
 fun checkargs(args: array<string>) {
 if (args.isempty()) {
  println("outmethodnoncapture check args")
  throwable().printstacktrace()
 }
 }
 checkargs(args)
}

首先我们分析一下捕获变量的局部方法的实现原理

public static final void outmethodcapture(@notnull final string[] args) {
 intrinsics.checkparameterisnotnull(args, "args");
 <undefinedtype> checkargs$ = new function0() {
 // $ff: synthetic method
 // $ff: bridge method
 public object invoke() {
 this.invoke();
 return unit.instance;
 }

 public final void invoke() {
 object[] var1 = (object[])args;
 if(var1.length == 0) {
  string var2 = "innermethod check args";
  system.out.println(var2);
  (new throwable()).printstacktrace();
 }

 }
 };
 checkargs$.invoke();
}

如上实现原理,就是局部方法实现其实就是实现了一个匿名内部类的实例,然后再次调用即可。 对于不捕获的局部方法要稍有不同,首先我们反编译得到对应的java代码

public static final void outmethodnoncapture(@notnull string[] args) {
 intrinsics.checkparameterisnotnull(args, "args");
 <undefinedtype> checkargs$ = null.instance;
 checkargs$.invoke(args);
}

我们得到的是一个不完整的代码,这时候需要我们前往项目工程,结合一些对应的class文件分析。首先我们找到类似这样的文件mainkt$outmethodcapture$1.class(其class文件按照”文件名$方法名$内部类序号”的规则)。

使用javap方法再次反编译分析该文件,注意对于$符号需要简单处理一下。

➜ kotlininnerfunction javap -c "mainkt\$outmethodnoncapture\$1.class"
compiled from "main.kt"
final class mainkt$outmethodnoncapture$1 extends kotlin.jvm.internal.lambda implements kotlin.jvm.functions.function1<java.lang.string[], kotlin.unit> {
 public static final mainkt$outmethodnoncapture$1 instance;

 public java.lang.object invoke(java.lang.object);
 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/unit.instance:lkotlin/unit;
  11: areturn

 public final void invoke(java.lang.string[]);
 code:
  0: aload_1
  1: ldc   #23     // string args
  3: invokestatic #29     // method kotlin/jvm/internal/intrinsics.checkparameterisnotnull:(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/system.out:ljava/io/printstream;
  30: aload_2
  31: invokevirtual #45     // method java/io/printstream.println:(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/throwable.printstacktrace:()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
}

上面的类其实比较简单,更重要的这是一个单例的实现。因为这样相比捕获的情况下,减少了匿名内部类的生成和实例的创建,理论上带来的代价也会更小。

考虑到上面的对比,如果在使用局部方法时,建议使用不捕获外部变量的方式会更加推荐。

使用注意

是的,使用局部方法有一个注意事项,也就是一种规则约定,那就是需要先定义才能使用,否则会报错,如下所示

fun outmethodinvalidcase(args: array<string>) {
 checkargs()//invalid unresolved reference
 fun checkargs() {
  if (args.isempty()) {
   println("innermethod check args")
   throwable().printstacktrace()
  }
 }
 checkargs()//valid
}

但是呢,先定义局部方法,再使用还是有一些问题,这种问题主要表现在代码可读性上。

试想一下,如果你进入一个方法,看到的是一连串的局部方法,可能或多或少有点别扭。

但是试想一下,既然有这样的问题,为什么还要被设计成这个样子呢。首先,我们先看个小例子

0fun outmethodinvalidcase(args: array<string>) {
 checkargs(args)
 var a = 0 //the reason why it's unresolved
 fun checkargs(args: array<string>) {
  if (args.isempty()) {
   println("outmethodnoncapture check args")
   throwable().printstacktrace()
   a.tostring()
  }
 }
}

因为局部方法可以capture局部变量,checkargs捕获了局部变量a,当第一行代码checkargs调用时,而checkargs看似定义了,但是第二行却还没有执行到,导致了编译问题。

目前,capture变量和非capture的局部方法使用都是一致的,都需要先定义,再使用。

关于kotlin中的局部方法,我们可以去尝试来达到限定范围,拆分方法的目的,在使用时,尽量选择非捕获的形式的局部方法。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。