??斗地主捕鱼电竞提现秒到 广告位招租 - 15元/月全站展示
??支付宝搜索579087183领大额红包 ??伍彩集团官网直营彩票
??好待遇→招代理 ??伍彩集团官网直营彩票
??络茄网 广告位招租 - 15元/月全站展示
Scala闭包分析

转载   2018-05-10   浏览量:213


天下彩:Scala中闭包的实现机制

彩票开奖查询 www.kbyp.net 本文通过scala代码编译生成的class文件的角度来对Scala的闭包实现机制进行简单分析

首先以一个简单的例子开始:

class ClosureDemo {
  def func() = {
    var i = 2
    val inc: () => Unit = () => i = i + 1
    val add: Int => Int = (ii: Int) => ii + i
    (inc, add)
  }
}

在这个代码中,inc和add引用了func函数中的i变量,由于Scala中函数时头等值,因此inc和add将形成闭包来引用外部的i变量。

编译上述代码我们将得到三个class文件:

ClosureDemo.class
ClosureDemo$$anonfun$1.class
ClosureDemo$$anonfun$2.class

这三个文件分别是ClosureDemo类自身和两个闭包,Scala会为每个闭包生成一个Class文件,如果嵌套过深,可能会出现特别长的类名,从而在Windows上引起一些路径过长的错误。

在Spark源码中的ClosureCleaner类中,我们可以看到这样的代码,用来判断这个类是不是闭包:

// Check whether a class represents a Scala closure
private def isClosure(cls: Class[_]): Boolean = {
    cls.getName.contains("$anonfun$")
}

首先我们使用javap来看下ClosureDemo.class文件的内容:

class ClosureDemo {
  def func() = {
    def i = 2
    val j = 3
    var k = 4
    val add: Int => Int = (ii: Int) => ii + i + j + k
    k = k + 1
    add
  }
}

编译后会生成两个文件:

ClosureDemo.class
ClosureDemo$$anonfun$1.class

我们还是先来看ClosureDemo.class文件:

{
  public scala.Function1 func();
    descriptor: ()Lscala/Function1;
    flags: ACC_PUBLIC
    Code:
      stack=5, locals=4, args_size=1
         0: iconst_3
         1: istore_1
         2: iconst_4
         3: invokestatic  #16                 // Method scala/runtime/IntRef.create:(I)Lscala/runtime/IntRef;
         6: astore_2
         7: new           #18                 // class ClosureDemo$$anonfun$1
        10: dup
        11: aload_0
        12: iload_1
        13: aload_2
        14: invokespecial #22                 // Method ClosureDemo$$anonfun$1."":(LClosureDemo;ILscala/runtime/IntRef;)V
        17: astore_3
        18: aload_2
        19: aload_2
        20: getfield      #26                 // Field scala/runtime/IntRef.elem:I
        23: iconst_1
        24: iadd
        25: putfield      #26                 // Field scala/runtime/IntRef.elem:I
        28: aload_3
        29: areturn
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      30     0  this   LClosureDemo;
            2      27     1     j   I
            7      22     2     k   Lscala/runtime/IntRef;
           18      11     3   add   Lscala/Function1;
      LineNumberTable:
        line 4: 0
        line 5: 2
        line 6: 7
        line 7: 18
        line 8: 28
    Signature: #43                          // ()Lscala/Function1;

  public final int ClosureDemo$$i$1();
    descriptor: ()I
    flags: ACC_PUBLIC, ACC_FINAL
    Code:
      stack=1, locals=1, args_size=1
         0: iconst_2
         1: ireturn
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       2     0  this   LClosureDemo;
      LineNumberTable:
        line 3: 0

  public ClosureDemo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #38                 // Method java/lang/Object."":()V
         4: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LClosureDemo;
      LineNumberTable:
        line 10: 0
}

由于我们在func方法中定义了i函数,因此生成了一个叫做ClosureDemo$$i$1的方法。我们首先看下val j、var k两个变量的处理方式:
1、由于j是val修饰,因此它直接作为Int类型变量传入了ClosureDemo$$anonfun$1的构造函数里
2、由于k是var修饰,因此它被包装到了IntRef里并传入ClosureDemo$$anonfun$1的构造函数里,关注下后面对k加1的操作,它也是基于IntRef这个包装进行的。

之后我们来看下ClosureDemo$$anonfun$1.class文件:

{
  public static final long serialVersionUID;
    descriptor: J
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: long 0l

  private final ClosureDemo $outer;
    descriptor: LClosureDemo;
    flags: ACC_PRIVATE, ACC_FINAL, ACC_SYNTHETIC

  private final int j$1;
    descriptor: I
    flags: ACC_PRIVATE, ACC_FINAL

  private final scala.runtime.IntRef k$1;
    descriptor: Lscala/runtime/IntRef;
    flags: ACC_PRIVATE, ACC_FINAL

  public final int apply(int);
    descriptor: (I)I
    flags: ACC_PUBLIC, ACC_FINAL
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: iload_1
         2: invokevirtual #27                 // Method apply$mcII$sp:(I)I
         5: ireturn
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       6     0  this   LClosureDemo$$anonfun$1;
            0       6     1    ii   I
      LineNumberTable:
        line 6: 0

  public int apply$mcII$sp(int);
    descriptor: (I)I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: iload_1
         1: aload_0
         2: getfield      #32                 // Field $outer:LClosureDemo;
         5: invokevirtual #36                 // Method ClosureDemo.ClosureDemo$$i$1:()I
         8: iadd
         9: aload_0
        10: getfield      #38                 // Field j$1:I
        13: iadd
        14: aload_0
        15: getfield      #40                 // Field k$1:Lscala/runtime/IntRef;
        18: getfield      #45                 // Field scala/runtime/IntRef.elem:I
        21: iadd
        22: ireturn
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      23     0  this   LClosureDemo$$anonfun$1;
            0      23     1    ii   I
      LineNumberTable:
        line 6: 0

  public final java.lang.Object apply(java.lang.Object);
    descriptor: (Ljava/lang/Object;)Ljava/lang/Object;
    flags: ACC_PUBLIC, ACC_FINAL, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: invokestatic  #52                 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
         5: invokevirtual #54                 // Method apply:(I)I
         8: invokestatic  #58                 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
        11: areturn
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      12     0  this   LClosureDemo$$anonfun$1;
            0      12     1    v1   Ljava/lang/Object;
      LineNumberTable:
        line 6: 0

  public ClosureDemo$$anonfun$1(ClosureDemo, int, scala.runtime.IntRef);
    descriptor: (LClosureDemo;ILscala/runtime/IntRef;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=4
         0: aload_1
         1: ifnonnull     6
         4: aconst_null
         5: athrow
         6: aload_0
         7: aload_1
         8: putfield      #32                 // Field $outer:LClosureDemo;
        11: aload_0
        12: iload_2
        13: putfield      #38                 // Field j$1:I
        16: aload_0
        17: aload_3
        18: putfield      #40                 // Field k$1:Lscala/runtime/IntRef;
        21: aload_0
        22: invokespecial #65                 // Method scala/runtime/AbstractFunction1$mcII$sp."":()V
        25: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      26     0  this   LClosureDemo$$anonfun$1;
            0      26     1 $outer   LClosureDemo;
            0      26     2   j$1   I
            0      26     3   k$1   Lscala/runtime/IntRef;
      LineNumberTable:
        line 6: 0
      StackMapTable: number_of_entries = 1
        frame_type = 6 /* same */
}

从上述代码中我们可以看到,其含有四个字段和四个方法:

public static final long serialVersionUID=0L;
private final ClosureDemo $outer
private final int j$1;
private final scala.runtime.IntRef k$1
public final int apply(int)
public int apply$mcII$sp(int)
public final java.lang.Object apply(java.lang.Object)
public ClosureDemo$$anonfun$1(ClosureDemo, int, scala.runtime.IntRef)

我们还是从构造函数开始入手,它先检测了第一个入参是否是null,如果是null则抛出空指针异常,否则将其存入类的$outer字段里。之后将j: Int与k: IntRef存入类的j$1与k$1字段里。

由于apply方法只是简单调用apply$mcII$sp(int)方法,因此我们继续分析apply$mcII$sp(int)。首先它调用了ClosureDemo类的ClosureDemo$$i$1方法取i的值,然后取Int类型的j$1的值,再取IntRef类型的k$1中的elem值,将它们加在一起返回。

从这个例子我们可以看出:
1、闭包调用外部方法会把外层类对象存在该闭包的$outer字段中,并在使用到该函数时用$outer进行invokevirtual调用
2、闭包调用外部val变量时,仅仅把该变量存在对应名称的字段中,在使用时直接取值
3、闭包调用外部var变量时,如果变量为值(AnyVal)类型,则会创建对应的Ref对象将其包裹并存在字段中,如果为引用类型(AnyRef),则会创建ObjectRef对象来包裹。在使用时取其elem字段来取它的原始值。

在本篇博客中,只介绍了一层包装的闭包。在Scala中还可以实现很多曾包装的闭包,与一层包装的区别仅仅在于每一层闭包会在需要时将其最近的一层外包装对象的存储在其$outer字段里,有兴趣可以自己构造以下来看看其class文件。

转载自:https://www.2cto.com/net/201805/744441.html

招聘 不方便扫码就复制添加关注:程序员招聘谷,微信号:jobs1024


下一篇:

用scala实现二次排序
用scala实现二次排序,二次排序就是按照不同字段进行排序,类似于MapReduce里面的分组+排序的实现。
实现scala二次排序代码
二次排序就是按照不同字段进行排序代码。
Scala面向对象编程教程
如果只是希望拥有简单的getter和setter方法,那么就按照scala提供的语法规则,根据需求为field选择合适的修饰符就好:var、val、private、private[this],但是如果希望能够自己对getter与setter进行控制,则可以自定义getter与setter方法。
Scala闭包分析
由于apply方法只是简单调用apply$mcII$sp(int)方法,因此我们继续分析apply$mcII$sp(int)。首先它调用了ClosureDemo类的ClosureDemo$$i$1方法取i的值,然后取Int类型的j$1的值,再取IntRef类型的k$1中的elem值,将它们加在一起返回。
Scala写WordCount查看本地的前20条数据
Scala写WordCount,查看本地的前20条数据,并统计出现的次数,并且是多线程。代码。
scala教程之每日一练
创建一个Listvallst0=List(1,7,9,8,0,3,5,4,6,2)将lst0中每个元素乘以10后生成一个新的集合解析:lst0map(_*10)将lst0中的偶数取出来生成一个新的集合解析:lst0filter(_%2==0)将lst0排序后生成一个新的集合解析:lst0sorted
新手入门必看的Scala的基本使用
1函数式编程2变量类型val常量var变量lazyval惰性求值3数据类型AnyVal基本数据类型:NumerictypeBooleanCharUnit(void)
Scala练习九文件和正则表达式
Scala练习九文件和正则表达式。摘要:在本篇中,你将学习如何执行常用的文件处理任务,比如从文件中读取所有行或单词,或者读取包含数字的文件等。
Scala练习四映射和元组
Scala练习四映射和元组。摘要:一个经典的程序员名言是:"如果只能有一种数据结构,那就用哈希表吧"。哈希表或者更笼统地说映射,是最灵活多变的数据结构之一。映射是键/值对偶的集合。Scala有一个通用的叫法:元组,即n个对象的聚集,并不一定要相同类型的。
Scala练习四
Scala练习四。1设置一个映射,其中包含你想要的一些装备,以及它们的价格。然后构建另一个映射,采用同一组键,但在价格上打9折。