Scala的case class私有apply方法(REPL漏洞?)

7

在Scala 2.10.0 REPL中

Welcome to Scala version 2.10.0 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_13).
Type in expressions to have them evaluated. 
Type :help for more information.

scala> case class A private(i:Int)
defined class A

scala> A(1)
res0: A = A(1)

但是如果编译

$ scala -version
Scala code runner version 2.10.0 -- Copyright 2002-2012, LAMP/EPFL
$ cat Main.scala 
package foo

case class A private (i:Int)

object Main extends App{
  println(A(1))
}

$ scalac Main.scala 
Main.scala:6: error: constructor A in class A cannot be accessed in object Main
  println(A(1))
          ^
one error found

A.apply(1) 是编译错误。

这是Scala2.10.0 REPL的bug吗?

请注意,Scala2.9.2 REPL如下:

Welcome to Scala version 2.9.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_13).
Type in expressions to have them evaluated.
Type :help for more information.

scala> case class A private(i:Int)
defined class A

scala> A(1)
<console>:10: error: constructor A in class A cannot be accessed in object $iw
              A(1)
              ^
3个回答

2
这绝对看起来像是REPL的一个bug。
请注意构造函数已经被正确标记为private(也就是说,new A(1)不会编译通过,正如预期的那样),只有工厂方法(A.apply)被错误地声明为public。

-2

我明白了...你认为当你写A(1)时,你在调用构造函数。但实际上不是这样的。你正在调用自动生成的(公共)伴生对象中为你添加的工厂和它的公共apply方法。

补充说明

我今天一直错了...

在2.10.0 REPL中:

scala> object REPL { case class CC1 private(i: Int); val cc1_1 = CC1(23) }
<console>:7: error: constructor CC1 in class CC1 cannot be accessed in object REPL
       object REPL { case class CC1 private(i: Int); val cc1_1 = CC1(23) }

1
我认为他并没有混淆工厂和构造函数,因为标题是“scala case class private apply method( repl bug ?)”。实际上,Scala编译器/REPL可能会让人感到困惑,并且在谈论构造函数时,我们实际上是在尝试调用工厂。 - Régis Jean-Gilles
@RégisJean-Gilles:他确实这样做。只有在使用new关键字时才直接使用构造函数。 - Randall Schulz
1
我认为你没有理解重点。这是一个case class。将构造函数设置为private应该也会使相应的工厂变成private。当你编译他的代码片段时,这正是发生的事情,但在REPL中行为出现了某些原因不同的情况。 - Régis Jean-Gilles

-3

REPL与普通编译器相比,有一个巨大的语义差异。

考虑能够做到这一点意味着什么:

scala> val v1 = 23
v1: Int = 23

scala> val v1 = 42
v1: Int = 42

你能用编译过的Scala代码来做到这一点吗?当然不能,因为这将是一种被禁止的重复定义。

REPL是如何做到这一点的呢?实际上,你输入的每一行代码都处于一个逐渐嵌套的作用域中。重新定义的出现实际上是遮蔽了以前的定义。就像你这样做:

object REPL1 {
  val v1 = 23
  object REPL2 {
    val v1 = 42
    object REPL3 {
      // Next REPL line here...
    }
  }
}

那么,如何获得伴侣?将它们放在显式对象(或其他形成作用域的结构)周围。记住,不要留空行。当你这样做时,REPL 将停止接受给定“行”或“块”的输入。


你的意思是什么?这在REPL之外作为Scala编译失败了。 - Randall Schulz
@Mef:哇...我完全没能通过使用v2来表达我的观点!感谢您的修复。 - Randall Schulz
1
我认为@Kenji Yoshida的观点恰恰是他的要点,即他的要旨模仿REPL的操作,但在REPL之外无法编译。换句话说,嵌套作用域与他的问题无关。此外,您解释了如何在REPL中定义伴生对象,但他并没有这样做:他的代码中只有一个case类,因此伴生对象实际上是自动生成的。最后,现在你可以在这种情况下使用“:paste”命令。 - Régis Jean-Gilles
是的,我第一次回答了错误的问题。第二个答案很到位(尽管有负面评价...)。 - Randall Schulz

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