聊聊kotlin之Lambda表达式解析(二)

1.lambda类型解释

在kotlin中,lambda可以表示函数类型,也可以表示Java语言声明的函数式接口,但用kotlin语言声明的函数式接口是不能用lambda表示的,kotlin声明的函数式接口和lambda是不能互相转换的。
lambda表示函数:

val addMethod: (Int, Int) -> Int = { a, b ->
    a + b
}

lambda表示java声明的函数式接口:

val runnable: Runnable = Runnable { println("lambda代表Java函数式接口") }

2.lambda return 工作流

kotlin中lambda表达式默认最后一行表达式就是返回值,不能显式的return。其他的普通方法需要显示的return。只有当lambda作为内联函数的参数的时候,才可以显示的return,因为这时lambda的代码会被插入到引用内联函数的函数的代码体中,此时return的类型是引用这个内联函数的函数的返回类型。
非内联的lambda内是不允许显示return的, 但是可以使用标签局部返回

3.内联lambda

默认情况下,kotlin中每个lambda表达式都会被编译成一个匿名类,这对性能是有一定损耗的。如果把 lambda 传给了标记成 inline 的 Kotlin 函数,这时由于lambda的代码会被插入到内联函数中,这时就不会在额外创建匿名类了。

4.lambda捕捉方法中的局部变量

4.1 Java是如何在匿名内部类或者lambda表达式中捕获外部方法的局部变量?

先看一段再熟悉不过的Java代码:

    public void main() {
        //如果变量被内部类捕获,需要声明为final
        final int a = 0;
        new Runnable() {
            @Override
            public void run() {
            	   //内部类只能使用变量a,但是不能修改
                int b = a;
                //a++;   报错
            }
        };
    }

java7之前,匿名内部类想要使用外部的临时变量,必须是final的,不能修改。java8之后,引入了Lambda,并且在匿名内部类和Lambda中使用外部临时变量时,如果只是使用可以不加final。但是同样不能修改

4.2 java中匿名内部类和Lambda不能修改外部的临时变量的原因

原因1:生命周期问题,外部变量的声明周期是方法栈,匿名内部类和Lambda则不是
原因2:副本。匿名内部类编译会生成一个新的类,保留外部类的引用以及临时变量的副本,所以在内名内部类中修改外部的临时变量实际上是修改的副本,会造成歧义,所以java直接禁止在匿名内部类和Lambda中修改外部临时变量

4.3 如果就想在java的匿名内部类中修改外部变量怎么办?

两种技巧:要么声明一个单元素的教组,其中存储可变值;要么创建一个包装类的实例,其中存储要改变的值的引用。因为不管是数组还是包装类,他们都是引用类型,这个样当这个引用类型被捕获时,虽然他是final不可变的,但是它内部维护的数据是可变的。

4.3 kotlin是如何在lambda表达式中捕获外部方法的局部变量?

还是先看一段kotlin代码:

fun main() {
    //a 不必声明为final
    var a = 0
    Runnable {
        //可以直接使用a
        var b = a
        //也可以直接修改a
        a++
    }
}

kotlin的lambda是可以使用外部变量的,这一点和Java是相同的,但是神奇的是kotlin允许修改外部临时变量。上边说过Java中声明的int 类型的变量 a 是不允许修改的,因为在Java中int是基本类型,在匿名内部类中基本类型保存的是副本,所以对副本的修改并不能影响原来的外部变量。而kotlin却可以修改外部变量,原因如下:

他的原理就是会自动包裹一层引用。对于基本类型,kotlin提供了 IntRef、BooleanRef等引用类型来包裹基本类型,对于引用类型,kotlin提供了 ObjectRef来包裹,从而达到在lambda内部修改外部的变量

package kotlin.jvm.internal;

import java.io.Serializable;

public class Ref {
    private Ref() {}

    public static final class ObjectRef<T> implements Serializable {
        public T element;

        @Override
        public String toString() {
            return String.valueOf(element);
        }
    }

    public static final class IntRef implements Serializable {
        public int element;

        @Override
        public String toString() {
            return String.valueOf(element);
        }
    }

看一个kotlin在lambda中修改外部变量的例子:

fun test() {
        var a = 1
        run(Runnable {
            a = 2
        })
        println("a:$a")
    }

    private fun run(runnable: Runnable) {
        runnable.run()
    }

反编译为Java后的代码:

public final void test() {
      final IntRef a = new IntRef();
      a.element = 1;
      this.run((Runnable)(new Runnable() {
         public final void run() {
            a.element = 2;
         }
      }));
      String var2 = "a:" + a.element;
      boolean var3 = false;
      System.out.println(var2);
   }

   private final void run(Runnable runnable) {
      runnable.run();
   }

可见,kotlin对Int类型直接做了包装,这样就可以在匿名类中修改外部变量了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值