首页 > 解决方案 > 为什么 Scala 运行时反射不再适用于 lambda?

问题描述

以下简单代码:

import org.scalatest.FunSpec

class RuntimeMirrorSpike extends FunSpec {

  import org.apache.spark.sql.catalyst.ScalaReflection.universe._

  it("can reflect lambda") {

    val ll = { v: String =>
      v.toInt
    }

    val clazz = ll.getClass
    val mirror = runtimeMirror(clazz.getClassLoader)

    val sym = mirror.classSymbol(clazz)

    print(sym)
  }
}

曾经在 Scala 2.11 上完美运行。但现在它在 Scala 2.12 上中断了:

assertion failed: no symbol could be loaded from class <...>.spike.RuntimeMirrorSpike$$Lambda$124/78204644 in package spike with name RuntimeMirrorSpike$$Lambda$124/78204644 and classloader sun.misc.Launcher$AppClassLoader@18b4aac2
java.lang.AssertionError: assertion failed: no symbol could be loaded from class <...>.spike.RuntimeMirrorSpike$$Lambda$124/78204644 in package spike with name RuntimeMirrorSpike$$Lambda$124/78204644 and classloader sun.misc.Launcher$AppClassLoader@18b4aac2
    at scala.reflect.internal.SymbolTable.throwAssertionError(SymbolTable.scala:184)
    at scala.reflect.runtime.JavaMirrors$JavaMirror.classToScala1(JavaMirrors.scala:1061)
    at scala.reflect.runtime.JavaMirrors$JavaMirror.$anonfun$classToScala$1(JavaMirrors.scala:1019)
    at scala.reflect.runtime.JavaMirrors$JavaMirror.$anonfun$toScala$1(JavaMirrors.scala:130)
    at scala.reflect.runtime.TwoWayCaches$TwoWayCache.$anonfun$toScala$1(TwoWayCaches.scala:50)
    at scala.reflect.runtime.TwoWayCaches$TwoWayCache.toScala(TwoWayCaches.scala:46)
    at scala.reflect.runtime.JavaMirrors$JavaMirror.toScala(JavaMirrors.scala:128)
    at scala.reflect.runtime.JavaMirrors$JavaMirror.classToScala(JavaMirrors.scala:1019)
    at scala.reflect.runtime.JavaMirrors$JavaMirror.classSymbol(JavaMirrors.scala:231)
    at scala.reflect.runtime.JavaMirrors$JavaMirror.classSymbol(JavaMirrors.scala:68)

这里发生了什么?什么样的对象没有运行时类?

标签: scalascala-reflectscala-2.12

解决方案


相对于 2.11,lambda 的编码在 2.12 中发生了变化。请参阅2.12.0 发行说明

以前,lambdas 的编码方式基本上等同于 Dmytro Mitin 的答案,即扩展 aFunctionN并具有apply方法的对象。

在 Java 8 中,引入了 lambda,但方式与 Scala 略有不同。调用 bootstrap 方法,java.lang.invoke.LambdaMetaFactory.metafactory它将在运行时创建一个类,其中一个方法调用另一个类中的方法。

在 Scala 2.12 中,Scala lambdas 的编码移动以匹配 Java 实现(可能是因为 JVM(尤其是 HotSpot)识别出结果的LambdaMetaFactory.metafactory结果是 lambda 并且比其他方式进行了更多的优化)。但是,运行时生成的类似乎无法映射到 Scala 类,导致 Scala 的反射被炸毁。

编辑:在 Dmytro Mitin 的评论之后,对我来说唯一明显的解决方法是将 lambdas 手动编码为 的实例FunctionN

def delambdafy[Result](ll: () => Result): Function0[Result] = new Function0[Result] { def apply(): Result = ll() }

def delambdafy[In, Result](ll: In => Result): Function1[In, Result] =
  new Function1[In, Result] { def apply(in: In): Result = ll(in) }

等等对于必要的arities...

为 REPL 调整您的代码,这有效:

// Entering paste mode (ctrl-D to finish)

import scala.reflect.runtime.universe._         // Effectively what is being imported with your Spark import

def delambdafy[In, Result](ll: In => Result): Function1[In, Result] = new Function1[In, Result] { def apply(in: In): Result = ll(in) }

val ll: String => Int = { v: String => v.toInt }

val clazz = delambdafy(ll).getClass

val symbol = runtimeMirror(clazz.getClassLoader).classSymbol(clazz)

// Exiting paste mode, now interpreting.

import scala.reflect.runtime.universe._
delambdafy: [In, Result](ll: In => Result)In => Result
ll: String => Int = $$Lambda$5162/1405722527@114a083b
clazz: Class[_ <: String => Int] = class $anon$1
symbol: reflect.runtime.universe.ClassSymbol = $anon

是否$anon对您有用取决于您实际尝试做的事情,这导致您提出这个问题。


推荐阅读