如何在Scala REPL中重新加载类或包?

53
我几乎总是会打开一个 Scala REPL 会话或者两个,这样可以很方便地对 Java 或 Scala 类进行快速测试。但是如果我改变了一个类并重新编译它,REPL 会话仍然加载旧的类,有没有一种方法可以让它重新加载类,而不必重新启动 REPL 呢?
举个具体的例子,假设我们有一个名为 Test.scala 的文件:
object Test { def hello = "Hello World" }

我们编译它并启动REPL:

~/pkg/scala-2.8.0.Beta1-prerelease$ bin/scala
Welcome to Scala version 2.8.0.Beta1-prerelease
(Java HotSpot(TM) Server VM, Java 1.6.0_16).
Type in expressions to have them evaluated.
Type :help for more information.

scala> Test.hello
res0: java.lang.String = Hello World

然后我们更改源文件为

object Test {
  def hello = "Hello World"
  def goodbye = "Goodbye, Cruel World"
}

但是我们不能使用它:

scala> Test.goodbye
<console>:5: error: value goodbye is not a member of object Test
       Test.goodbye
            ^

scala> import Test;
<console>:1: error: '.' expected but ';' found.
       import Test;

我猜这就是工作表比 REPL 更好的一个好处。 - nafg
5个回答

41

如果目标是不必重复之前的命令,那么重新加载类的另一种选择是使用REPL命令。

:replay

该命令可以重新启动REPL环境并回放所有之前有效的命令(无效的命令将被跳过,因此如果之前是错误的,则不能突然生效)。当重置REPL时,它会重新加载类,因此新命令可以使用重新编译类的内容(实际上,旧命令也将使用这些重新编译的类)。

这不是一种通用解决方案,但它是一个有用的快捷方式,可扩展具有可重新计算状态的单个会话。

注意:此适用于裸的Scala REPL。如果您从SBT或其他环境运行它,则取决于SBT或其他环境如何打包类--如果您不更新实际正在使用的类路径上的内容,当然它不会工作!


显然对于密集的会话来说不太好,但我喜欢这个。这也意味着如果你的目标是测试/测试驱动开发,你会知道你的更改是否破坏了任何东西。 - Grogs
5
我认为这是错误的。":replay"命令不会重新加载类。 - Adam Mackler
我同意@AdamMackler的观点,至少在2.10.4中似乎不会重新加载。 - mitchus
@mitchus - 我刚刚验证了2.11.6的重新加载行为。然而,在SBT中它对我来说并不起作用,至少没有任何直接的方式。我还没有具体检查过2.10.4。 - Rex Kerr
@RexKerr 是的,我的观察也是针对 "sbt console"。 - mitchus
它可以工作!稍微调整一下。打开两个控制台窗口。在其中一个窗口中,使用“scalac *.scala”编译Scala文件。在另一个窗口中,在编译完成后只需使用:replay即可开始使用!编辑:现在添加新函数时它不起作用了!天哪 - Cristian

32

类重载并不是一个容易的问题。实际上,JVM使这个问题非常棘手。不过你还有几个选项:

  • 在调试模式下启动Scala REPL。JVM调试器具有一些内置的方法级别的重新加载功能。它虽然不能解决你提出的情况,但处理简单的改变一个方法实现还是没问题的。
  • 使用JRebel (http://www.zeroturnaround.com/jrebel)。JRebel基本上是一个超级强大的JVM类重装载解决方案。它可以处理成员添加/移除、新/删除的类、定义更改等,只有在类层次结构中进行更改(例如添加一个超级接口)时,它才无法处理。它不是一个免费工具,但他们提供了一个限制为Scala编译单元的免费许可证。

不幸的是,这两者都受到Scala REPL实现细节的限制。我使用JRebel,它通常能够解决问题,但还是有些情况下REPL不会反映重新加载的类。尽管如此,总比没有好。


12
你如何在 Scala REPL 中启动调试模式?我正在使用 SBT,运行 'console' 命令启动 Scala REPL,但不确定如何在调试模式下执行此操作。 - mmrobins

9

有一个指令符合您的要求

:load path/to/file.scala

这将重新加载Scala源文件并重新编译为类,然后您可以重放您的代码。


1
如果file.scala在顶部定义了一个package,这对您是否有效?谢谢。 - Peter Becich

7
这是我的解决方案...
如果您的新源文件 Test.scala 看起来像这样...
package com.tests

object Test {
  def hello = "Hello World"
  def goodbye = "Goodbye, Cruel World"
}

首先,您需要将新更改加载到Scala控制台(REPL)中。

:load src/main/scala/com/tests/examples/Test.scala

然后重新导入包,这样您就可以在Scala控制台中引用新代码。

import com.tests.Test

现在您可以在不重新启动会话的情况下享受您的新代码 :)
scala> Test.goodbye
res0: String = Goodbye, Cruel World

这仅适用于在Test.scala的顶部未定义package的情况下。有什么想法吗?谢谢。 - Peter Becich
不知道。可能是错误的包路径。 - cevaris

2
如果.scala文件在REPL启动的目录中,您可以省略完整路径,只需输入:load myfile.scala,然后进行导入。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接