Scala案例类私有构造函数但公共应用程序方法

42
如果我有以下的 case class,它拥有私有构造函数并且我无法访问伴生对象中的 apply 方法。
case class Meter private (m: Int)

val m = Meter(10) // constructor Meter in class Meter cannot be accessed...

有没有办法使用具有私有构造函数的case类,但保持伴随对象生成的apply方法为公共的?

我知道在我的示例中这两个选项之间没有区别:

val m1 = new Meter(10)
val m2 = Meter(10)

但是我想禁止第一个选项。

-- 编辑 --

令人惊讶的是以下内容可以工作(但不是我想要的):

val x = Meter
val m3 = x(10) // m3  : Meter = Meter(10)

你使用的是哪个版本的Scala?我刚在我的2.10.0 REPL中尝试了一下,val m2 = Meter(10)没有产生任何错误。 - Luigi Plinge
@LuigiPlinge 我正在使用 Scala 2.10.3 版本。 - Erik
似乎是case class Meter private (m: Int)这一行引起了错误,当它作为顶级对象声明时(http://scalafiddle.net/console/eb6fdc36b281b7d5eabf33396c2683a2),但是当它在另一个对象或REPL中声明时,它可以正常工作(http://scalafiddle.net/console/cdc0d6e63aa8e41c89689f54970bb35f)。 - Luigi Plinge
4个回答

52

这是一个具有私有构造函数和公共apply方法的技巧。

trait Meter {
  def m: Int
}

object Meter {   
  def apply(m: Int): Meter = { MeterImpl(m) }
  private case class MeterImpl(m: Int) extends Meter { println(m) }
}

object Application extends App {
  val m1 = new Meter(10) // Forbidden
  val m2 = Meter(10)
}

背景信息 Scala中的私有和受保护构造函数


@senia,不是的。这是new Meter(10){}的编译错误: Trait Meter是一个trait,不接受构造函数参数。 - user425367
1
我错过了。你应该将Meter定义为trait Meter { def m: Int }以允许访问m。你也可以将其定义为sealed - senia
2
我认为这是一个不错的解决方法,因为你可以得到所需的行为以及一些 case classes 的好处(例如实现 equals 方法)。但与单个 case class 相比,它有点重。 - Erik
2
在这种情况下,println(Meter(1)) 不会导致 > MeterImpl(1) 吗?虽然这并没有回答问题,但我想要一个不会发生这种情况的解决方案。 - Ritwik Bose

5

看起来,所请求的行为(私有构造函数但公共.apply)可能是Scala 2.12实现这些的方式。

我从相反的角度来到这里 - 希望私有case类构造函数也阻止.apply方法。原因在这里:https://github.com/akauppi/case-class-gym

有趣的是,用例不同。


2

通过一些隐式技巧是可能的:

// first case 
case class Meter[T] private (m: T)(implicit ev: T =:= Int)
object Meter { 
  def apply(m: Int) = new Meter(m + 5) 
}

创建了另一个构造函数(并应用方法签名),但保证参数只能是Int

之后,您将拥有具有案例类特征的案例类(具有模式匹配、哈希码和等于),排除默认构造函数:

scala> val m = Meter(10)
m: Metter[Int] = Meter(15)

scala> val m = new Meter(10)
<console>:9: error: constructor Meter in class Meter cannot be accessed in object $iw
       val m = new Meter(10)

或者带有类型标记(天真的实现):

trait Private
case class Meter private (m: Integer with Private)
object Meter {
  def apply(m: Int) = new Meter((m + 5).asInstanceOf[Integer with Private])
}

它按预期工作:

val x = new Meter(10)
<console>:11: error: constructor Meter in class Meter cannot be accessed in object $iw
              new Meter(10)
              ^

val x = Meter(10)
x: Meter = Meter(15)

这里描述了一些关于原始类型和类型标签的可能问题在这里


1
以上的解决方案相对较为复杂。其实只需要:
case class Meter private (m: Int)
object Meter { 
 def apply(m: Int): Meter = new Meter(m)
}

测试

new Meter(10) // fails to compile
Meter(10) // compiles just fine

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