Kotlin中局部方法的深入探究

前言

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中的局部方法,我们可以去尝试来达到限定范围,拆分方法的目的,在使用时,尽量选择非捕获的形式的局部方法。

总结

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

(0)

相关推荐

  • Kotlin实现静态方法

    工具类 全都是静态方法的情况 : class 类名 改为 object 类名 即可 package redwolf.com.moreimageupload import okhttp3.MultipartBody import java.io.File /** * @作者 RedWolf * @时间 2017/5/20 10:52 * @简介 MoreImageUtils.kt */ object MoreImageUtils { fun filesToMultipartBodyParts(fi

  • 详解Kotlin中的变量和方法

    详解Kotlin中的变量和方法 变量 Kotlin 有两个关键字定义变量:var 和 val, 变量的类型在后面. var 定义的是可变变量,变量可以被重复赋值.val 定义的是只读变量,相当于java的final变量. 变量的类型,如果可以根据赋值推测,可以省略. var name: String = "jason" name = "jame" val max = 10 常量 Java 定义常量用关键字 static final, Kotlin 没有static,

  • Kotlin中局部方法的深入探究

    前言 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中,定义

  • Kotlin中let()with()run()apply()also()函数的使用方法与区别

    相比Java, Kotlin提供了不少高级语法特性.对于一个Kotlin的初学者来说经常会写出一些不够优雅的代码.在Kotlin中的源码标准库(Standard.kt)中提供了一些Kotlin扩展的内置函数可以优化kotlin的编码.Standard.kt是Kotlin库的一部分,它定义了一些基本函数. 这个源代码文件虽然一共不到50行代码,但是这些函数功能都非常强大. 一.回调函数的Kotin的lambda的简化 在Kotlin中对Java中的一些的接口的回调做了一些优化,可以使用一个lamb

  • Kotlin中双冒号::使用方法

    Kotlin 中 双冒号操作符 表示把一个方法当做一个参数,传递到另一个方法中进行使用,通俗的来讲就是引用一个方法.先来看一下例子: fun main(args: Array<String>) { println(lock("param1", "param2", ::getResult)) } /** * @param str1 参数1 * @param str2 参数2 */ fun getResult(str1: String, str2: Stri

  • kotlin中数据类重写setter getter的正确方法

    概述 在开发过程中,经常会创建一些数据里,其没有任何逻辑功能,仅仅来用来保存数据.在Kolin中,将这些类统一称为数据类,用关键字data标记. data class User(val name: String, val age: Int) 编译器会根据主构造器中声明的全部属性, 自动推断产生以下成员函数: equals()/hashCode()函数对, toString() 函数, 输出格式为 "User(name=John, age=42)" , componentN() 函数群,

  • 解决Kotlin 类在实现多个接口,覆写多个接口中相同方法冲突的问题

    一.首先来看一个例子 package net.println.kotlin.chapter4 /** * @author:wangdong * @description:类实现接口的冲突问题 */ interface B{ fun x(): Int = 1 } interface C{ fun x(): Int = 0 } /**一个类实现了两个接口,两个接口中的方法相同,这个类在覆写的时候就会出现冲突*/ class D: B,C{ //当下面两个方法同时存在的时候,就会报方法相同的冲突 ov

  • Kotlin中日志的使用方法详解

    1 引言 想必学过Java的人都知道一个@Slf4j使用得多么的舒服: @Slf4j public class TestController{ @GetMapping("/test") public String test(){ log.debug("debug"); return "test"; } } 但是很不幸在Kotlin中并没有这种注解,因此,本文给出了一种类似@Slf4j注解在Kotlin中的使用方法,以及介绍一个100%使用Kotl

  • Java8中Optional类型和Kotlin中可空类型的使用对比

    本文主要给大家介绍了关于Java8中Optional类型和Kotlin中可空类型使用的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍: 在 Java 8中,我们可以使用 Optional 类型来表达可空的类型. package com.easy.kotlin; import java.util.Optional; import static java.lang.System.out; /** * Optional.ofNullable - 允许传递为 null 参数 *

  • 详解Kotlin中的面向对象(二)

    详解Kotlin中的面向对象(二) 在Kotlin中的面向对象(一)中,介绍了Kotlin类的相关操作,本文将在上文的基础上,继续介绍属性.接口等同样重要的面向对象的功能. 属性 class AttrDemo{ private var attr1 : String = ""; protected var attr2 : String = ""; public var attr3 : String = ""; var varattr : Strin

  • 详解Swift编程中的方法与属性的概念

    方法 在 Swift 中特定类型的相关联功能被称为方法.在 Objective C 中类是用来定义方法,其中作为 Swift 语言为用户提供了灵活性,类,结构和枚举中可以定义使用方法. 实例方法 在 Swift 语言,类,结构和枚举实例通过实例方法访问. 实例方法提供的功能 访问和修改实例属性 函数关联实例的需要 实例方法可以写在花括号 {} 内.它隐含的访问方法和类实例的属性.当该类型指定具体实例它调用获得访问该特定实例. 语法 复制代码 代码如下: func funcname(Paramet

随机推荐