加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > 安全 > 正文

scala – lambda函数也不是具有Function1特征的对象吗?

发布时间:2020-12-16 18:42:37 所属栏目:安全 来源:网络整理
导读:object MyApp { def printValues(f: {def apply(x: Int): Int},from: Int,to: Int): Unit = { println( (from to to).map(f(_)).mkString(" ") ) } def main(args: Array[String]): Unit = { val anonfun1 = new Function1[Int,Int] { final def apply(x: In
object MyApp {
  def printValues(f: {def apply(x: Int): Int},from: Int,to: Int): Unit = {
    println(
      (from to to).map(f(_)).mkString(" ")
    )
  }

  def main(args: Array[String]): Unit = {
    val anonfun1 = new Function1[Int,Int] {
      final def apply(x: Int): Int = x * x
    }

    val fun1 = (x:Int)=>x*x
    printValues(fun1,3,6)
  }
}

我认为scala中的lambda函数也是扩展Function1特性的对象.但是这个代码对于printValues(fun1,6)而不是printlnValues(anonfun1,6)都是失败的.为什么会这样?

解决方法

这是一个非常有趣的问题需要探索.有一种说法,一个人不应该依赖于代码中的实现细节,我认为这是做这件事的边缘.

让我们尝试分解这里发生的事情.

结构类型:

当您需要结构类型时,例如在此方法中执行的操作:

def printValues(f: {def apply(x: Int): Int},to: Int): Unit = {
  println(
    (from to to).map(f(_)).mkString(" ")
  )
}

Scala所做的是使用反射来尝试在运行时找到apply方法,并动态调用它.它被转换为看起来像这样的东西:

public static Method reflMethod$Method1(final Class x$1) {
    MethodCache methodCache1 = Tests$$anonfun$printValues$1.reflPoly$Cache1.get();
    if (methodCache1 == null) {
        methodCache1 = (MethodCache)new EmptyMethodCache();
        Tests$$anonfun$printValues$1.reflPoly$Cache1 = new SoftReference((T)methodCache1);
    }
    Method method1 = methodCache1.find(x$1);
    if (method1 != null) {
        return method1;
    }
    method1 = ScalaRunTime$.MODULE$.ensureAccessible(x$1.getMethod("apply",(Class[])Tests$$anonfun$printValues$1.reflParams$Cache1));
    Tests$$anonfun$printValues$1.reflPoly$Cache1 = new SoftReference((T)methodCache1.add(x$1,method1));
    return method1;
}

这是发出的反编译Java代码.长话短说,它寻找apply方法.

在Scala< = 2.11中会发生什么 对于2.12之前的任何Scala版本,声明匿名函数会导致编译器生成一个扩展AbstractFunction *的类,其中*是函数的arity.这些抽象函数类依次继承Function *,并使用lambda的实现实现其apply方法. 例如,如果我们接受你的表达:

val fun1 = (x:Int) => x * x

编译器为我们发出:

val fun2: Int => Int = {
    @SerialVersionUID(value = 0) final <synthetic> class $anonfun extends scala.runtime.AbstractFunction1$mcII$sp with Serializable {
      def <init>(): <$anon: Int => Int> = {
        $anonfun.super.<init>();
        ()
      };
      final def apply(x: Int): Int = $anonfun.this.apply$mcII$sp(x);
      <specialized> def apply$mcII$sp(x: Int): Int = x.*(x)
    };
    (new <$anon: Int => Int>(): Int => Int)
  };
  ()

当我们查看字节码级别时,我们会看到生成的匿名类,它是应用方法:

Compiled from "Tests.scala"
public final class othertests.Tests$$anonfun$1 extends scala.runtime.AbstractFunction1$mcII$sp implements scala.Serializable {
  public static final long serialVersionUID;

  public final int apply(int);
    Code:
       0: aload_0
       1: iload_1
       2: invokevirtual #21                 // Method apply$mcII$sp:(I)I
       5: ireturn

  public int apply$mcII$sp(int);
    Code:
       0: iload_1
       1: iload_1
       2: imul
       3: ireturn

So what happens when you request the `apply` method at runtime? The run-time will see that theirs a method defined on `$anonfun` called `apply` which takes an `Int` and returns an `Int`,which is exactly what we want and invoke it. All is good and everyone's happy.

Lo和Behold,Scala 2.12

在Scala 2.12中,我们得到了一种称为SAM转换的东西. SAM类型是Java 8中的一项功能,它允许您缩写实现接口,而是提供lambda表达式.例如:

new Thread(() -> System.out.println("Yay in lambda!")).start();

而不是必须实现Runnable并覆盖public void run.
Scala 2.12设定了一个目标,即在可能的情况下通过SAM转换与SAM类型兼容.

在我们的特定情况下,SAM转换是可能的,这意味着我们得到scala.runtime.java8.JFunction1 $mcII $sp的专用版本,而不是Scala的Function1 [Int,Int].此JFunction与Java兼容,并具有以下结构:

package scala.runtime.java8;

@FunctionalInterface
public interface JFunction1$mcII$sp extends scala.Function1,java.io.Serializable {
    int apply$mcII$sp(int v1);

    default Object apply(Object t) { return scala.runtime.BoxesRunTime.boxToInteger(apply$mcII$sp(scala.runtime.BoxesRunTime.unboxToInt(t))); }
}

这个JFunction1已被专门化(就像我们在Scala中使用@specialized注释一样)为def apply(i:Int)发出一个特殊的方法:Int.注意这里的一个重要因素,该方法仅实现Object =>形式的apply方法.对象,而不是Int =>诠释.现在我们可以开始了解为什么这可能会有问题.

现在,当我们在Scala 2.12中编译相同的示例时,我们看到:

def main(args: Array[String]): Unit = {
  val fun2: Int => Int = {
    final <artifact> def $anonfun$main(x: Int): Int = x.*(x);
    ((x: Int) => $anonfun$main(x))
  };
  ()

我们不再看到扩展AbstractFunction *的方法,我们只看到名为$anonfun $main的方法.当我们查看生成的字节码时,我们在内部看到它将调用JFunction1 $mcII $sp.apply $mcII $sp(int v1);:

public void main(java.lang.String[]);
  Code:
    0: invokedynamic #41,0  // InvokeDynamic #0:apply$mcII$sp: ()Lscala/runtime/java8/JFunction1$mcII$sp;
    5: astore_2
    6: return

然而,如果我们自己显式扩展Function1并实现apply,我们会得到与之前的Scala版本类似的行为,但不完全相同:

def main(args: Array[String]): Unit = {
  val anonfun1: Int => Int = {
    final class $anon extends AnyRef with Int => Int {
      def <init>(): <$anon: Int => Int> = {
        $anon.super.<init>();
        ()
      };
      final def apply(x: Int): Int = x.*(x)
    };
    new $anon()
  };
  {
    ()
  }
}

我们不再扩展AbstractFunction *,但是我们确实有一个apply方法在运行时满足结构类型条件.在字节码级别,我们看到一个int apply(int),一个对象apply(对象)和一堆用于注释Function *的@specialization属性的情况:

public final int apply(int);
  Code:
     0: aload_0
     1: iload_1
     2: invokevirtual #183                // Method apply$mcII$sp:(I)I
     5: ireturn

public int apply$mcII$sp(int);
  Code:
     0: iload_1
     1: iload_1
     2: imul
     3: ireturn

public final java.lang.Object apply(java.lang.Object);
  Code:
     0: aload_0
     1: aload_1
     2: invokestatic  #190                // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
     5: invokevirtual #192                // Method apply:(I)I
     8: invokestatic  #196                // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
    11: areturn

结论:

我们可以看到Scala编译器在某些情况下如何处理lambda表达式的实现细节发生了变化.这是一个错误吗?我的感觉倾向于没有.在任何地方,Scala规范都不能保证需要一个名为apply的方法来匹配lambda的签名,这就是我们称之为实现细节的原因.虽然这确实是一个有趣的怪癖,但我不会在任何类型的生产环境中依赖这些代码,因为它可能会发生变化.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读