Scala脚本:在Windows上向ScalaClassLoader解释此类强制转换错误

Ksh*_*rma 7 scala scala-2.12

请考虑以下scala脚本:

import scala.reflect.internal.util.ScalaClassLoader

object Test {
  def main(args: Array[String]) {
    val classloaderForScalaLibrary = classOf[ScalaClassLoader.URLClassLoader].getClassLoader
    println(classloaderForScalaLibrary)
    val classloaderForTestClass = this.getClass.getClassLoader
    println(classloaderForTestClass)
    this.getClass.getClassLoader.asInstanceOf[ScalaClassLoader.URLClassLoader]
  }
}
Run Code Online (Sandbox Code Playgroud)

输出是:

scala.reflect.internal.util.ScalaClassLoader$URLClassLoader@71c8becc
scala.reflect.internal.util.ScalaClassLoader$URLClassLoader@71c8becc
java.lang.ClassCastException: scala.reflect.internal.util.ScalaClassLoader$URLClassLoader cannot be cast to scala.reflect.internal.util.ScalaClassLoader$URLClassLoader
        at Main$.main(Test.scala:8)
        at Main.main(Test.scala)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at scala.reflect.internal.util.ScalaClassLoader.$anonfun$run$2(ScalaClassLoader.scala:98)
        at scala.reflect.internal.util.ScalaClassLoader.asContext(ScalaClassLoader.scala:32)
...
Run Code Online (Sandbox Code Playgroud)

为什么我不能投ScalaClassLoader$URLClassLoaderScalaClassLoader$URLClassLoader

在此输入图像描述

编辑:

在跑步时:

scala -J-verbose:class Test.scala | grep ScalaClassLoader

输出是:

[Loaded scala.reflect.internal.util.ScalaClassLoader$URLClassLoader from file:/C:/Development/Software/scala-2.12.2/lib/scala-reflect.jar]
...
...
[Loaded scala.reflect.internal.util.ScalaClassLoader$URLClassLoader from file:/C:/DEVELO~1/Software/SCALA-~1.2/lib/scala-reflect.jar]
Run Code Online (Sandbox Code Playgroud)

所以肯定会有一些阴暗的类加载.现在试着调查为什么会这样

Ser*_*gGr 3

如果您将代码进一步扩展如下:

\n\n
import scala.reflect.internal.util.ScalaClassLoader\n\nobject test {\n\n  def main(args: Array[String]) {\n    val cl1 = this.getClass.getClassLoader\n    println(cl1)\n    val c1 = cl1.getClass \n    println(cl1.getClass)\n    println(cl1.getClass.getClassLoader)\n\n    println("-------")\n\n    var c2 = classOf[ScalaClassLoader.URLClassLoader]\n    println(c2)\n    println(c2.getClassLoader)\n    println("-------")\n    println(c1 == c2)\n\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

你将得到以下输出:

\n\n
\n

scala.reflect.internal.util.ScalaClassLoader$URLClassLoader@5cee5251\n
类 scala.reflect.internal.util.ScalaClassLoader$URLClassLoader\n
sun.misc.Launcher$AppClassLoader@4554617c\n
-------\n
类scala.reflect.internal.util.ScalaClassLoader$URLClassLoader\n
scala.reflect.internal.util.ScalaClassLoader$URLClassLoader@5cee5251\n
-------\n
false

\n
\n\n

注意匹配的哈希值@5cee5251。\n这意味着首先 Scala 解释器ScalaClassLoader$URLClassLoader使用 root Java 类加载器加载,然后使用该类加载器加载脚本中的所有类,当您ScalaClassLoader$URLClassLoader在代码中请求时,它会加载另一个(已加载)的实例ScalaClassLoader$URLClassLoader。通过这种方式,您的脚本与执行它的“运行时环境”隔离。

\n\n

您可能会在ScalaClassLoader.asContext方法中找到一些详细信息,您可以在堆栈跟踪中看到该方法,该方法使用Thread.setContextClassLoader将自身设置为执行脚本的线程的主类加载器。

\n\n

更新(为什么它在 Mac 上工作但在 Windows 上不起作用)

\n\n

scala*nix 和 Windows 的 shell 脚本之间的主要区别scala.bat在于,默认情况下,在 *nix 平台上,标准 Scala 库被添加到引导类路径(请参阅usebootcp脚本中),而在 Windows 上,它们被添加到“常用类路径”。scala.reflect.internal.util.ScalaClassLoader这很重要,因为它定义了将加载所使用的类加载器scala.tools.nsc.MainGenericRunner:它是根类加载器(表示为null调用getClassLoader)​​还是应用程序类加载器(即 的实例sun.misc.Launcher$AppClassLoader)。这很重要,因为创建了一个使用 just withoutCommonRunner.run的实例ScalaClassLoaderurlsparent

\n\n
def run(urls: Seq[URL], objectName: String, arguments: Seq[String]) {\n  (ScalaClassLoader fromURLs urls).run(objectName, arguments)\n} \n
Run Code Online (Sandbox Code Playgroud)\n\n

这意味着“main”的父类加载器ScalaClassLoader将是引导类加载器,而不是引导类加载器sun.misc.Launcher$AppClassLoader,因此当您向该“main”请求ScalaClassLoader类时scala.reflect.internal.util.ScalaClassLoader,它无法在其类加载器链加载的类中找到它,因此必须加载再来一次。这就是为什么脚本中有两个不同的ScalaClassLoader 实例的原因。

\n\n

有两种明显的解决方法(而且都不是很好):

\n\n
    \n
  • 更改CommonRunner.runScala 源以实际将当前上下文类加载器作为新上下文类加载器的父级传递给新的ScalaClassLoader(可能不是那么容易 \xe2\x98\xba)
  • \n
  • 更改scala.bat为 use-Xbootclasspath/a:而不是-cpfor %_TOOL_CLASSPATH%。然而,查看 usebootcp*nix 脚本中我可以看到以下评论:
  • \n
\n\n
# default to the boot classpath for speed, except on cygwin/mingw/msys because\n# JLine on Windows requires a custom DLL to be loaded.\nunset usebootcp\nif [[ -z "$cygwin$mingw$msys" ]]; then\n  usebootcp="true"\nfi\n
Run Code Online (Sandbox Code Playgroud)\n\n

所以我怀疑如果你想使用scala.batREPL,将所有 Scala 库移动到引导类路径可能是一个坏主意。如果是这种情况,您可能需要创建一个副本scala.bat(例如scala_run_script.bat)更改它并使用它来运行 Scala 脚本,保留scala.batREPL 标准。

\n