2

我正在考虑制作一个值类,它对如何实例化有一定的警惕性。例如,假设我想要一个非负整数:

class NonNegInt private (val value: Int) extends AnyVal

object NonNegInt {
  def apply(value: Int): Try[NonNegInt] = Try {
    if (value >= 0) new NonNegInt(value) else throw new IllegalArgumentException("non-negative integers only!")
  }
}

我唯一担心的是私有构造函数可能使 scala 编译器无法将NonNegInt视为原始int。这是真的?

4

3 回答 3

3

如果这里的“视为原始”意味着“避免分配”,那么这确实行不通,但不是因为私有构造函数。

指导价值类中所述

此规则的另一个实例是值类用作类型参数时。例如,即使是对身份的调用,也必须创建实际的 Meter 实例。

def identity[T](t: T): T = t
identity(Meter(5.0))

基本上,因为 identity[T] 是参数化的,所以在值类型上调用它需要分配一个实例。Try[T] 是相同的情况:Try { ... }“块”是对参数化函数的调用,Try.apply[T]其中 T 为NonNegInt。此调用将需要分配NonNegInt实例。

于 2014-12-30T21:26:26.377 回答
1

这是一个提示:

scala> implicit class X private (val i: Int) extends AnyVal { def doubled = 2 * i }
<console>:7: error: constructor X in class X cannot be accessed in object $iw
       implicit class X private (val i: Int) extends AnyVal { def doubled = 2 * i }
                      ^

这是确定的:

$ scala -optimise
Welcome to Scala version 2.11.4 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_11).
Type in expressions to have them evaluated.
Type :help for more information.

scala> :pa
// Entering paste mode (ctrl-D to finish)

class X private (val i: Int) extends AnyVal { def doubled = 2 * i }
object X { @inline def apply(i: Int) = new X(i) }

// Exiting paste mode, now interpreting.

defined class X
defined object X

scala> X(42).doubled
warning: there was one inliner warning; re-run with -Yinline-warnings for details
res0: Int = 84

您可以使用:javap -prv -来验证是否有分配。

但这是一个更好的技巧:

scala> case class X private (val i: Int) extends AnyVal { def doubled = 2 * i }
defined class X

scala> X(42).doubled
res1: Int = 84

scala> :javap -prv -
[snip]
  public $line7.$read$$iw$$iw$();
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0       
         1: invokespecial #19                 // Method java/lang/Object."<init>":()V
         4: aload_0       
         5: putstatic     #21                 // Field MODULE$:L$line7/$read$$iw$$iw$;
         8: aload_0       
         9: getstatic     #26                 // Field $line6/$read$$iw$$iw$X$.MODULE$:L$line6/$read$$iw$$iw$X$;
        12: bipush        42
        14: invokevirtual #30                 // Method $line6/$read$$iw$$iw$X$.doubled$extension:(I)I
        17: putfield      #17                 // Field res1:I
        20: return  

脚注:

scala> case class X[A <: X[A]] private (val i: Int) extends AnyVal { def doubled = 2 * i }
defined class X

scala> X(42).doubled
res2: Int = 84
于 2014-12-30T21:30:24.333 回答
0

您的示例代码使您的实际问题模棱两可。您的示例代码IntTry. Try如果您在伴随对象中使用了语句而不是 using ,require那么我的理解是下面的代码可以工作(不会失去扩展AnyVal优惠的“原始”好处)。如果/当尝试产生负值时,这会给您一个运行时异常。private该代码在扩展的案例类上使用构造函数AnyVal。然后它使用案例类的伴随对象的apply方法通过require语句强制执行运行时约束。

如果您确实需要使用 a 包装该值Try,则可以提供一个附加的伴随对象构造函数来包装 apply 以捕获异常。但是,正如在其他答案中所指出的那样,AnyVal当它被 a TryOptionEither等“包含”时,您会失去“原始”质量。

警告:下面的代码不会在 REPL/Scala 工作表中编译。扩展 AnyVal 的案例类必须是顶级类;ie 不能嵌套在另一个类、特征或对象的范围内。REPL 和 Scala 工作表都是通过在执行之前将所有代码推入一个不可见的包含类来实现的。

object PositiveInt {
  def apply(value: Int): PositiveInt = {
    require(value >= 0, s"value [$value] must be greater than or equal to 0")
    new PositiveInt(value)
  }

  def tryApply(value: Int): Try[PositiveInt] =
    Try(apply(value))
}
case class PositiveInt private(value: Int) extends AnyVal

val positiveTestA = PositiveInt(0)
val positiveTestB = PositiveInt(1)
val positiveTestD = PositiveInt.tryApply(-1)) //returns Failure
val positiveTestD = Try(PositiveInt(-1))      //returns Failure
val positiveTestC = PositiveInt(-1)           //throws required exception
于 2019-05-04T13:19:59.173 回答