scala – lambda函数也不是具有Function1特征的对象吗?
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. 在我们的特定情况下,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的签名,这就是我们称之为实现细节的原因.虽然这确实是一个有趣的怪癖,但我不会在任何类型的生产环境中依赖这些代码,因为它可能会发生变化. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |