覆盖默认的case类构造函数(Scala)

时间:2017-06-08 20:43:55

标签: scala case-class

如果我有一个案例类:

case class NonNegativeInt(i: Int)

如果参数为负,则将字段i设置为0。所以我不能只使用case类提供的默认构造函数。如果我在随播对象(或任何地方)中定义了apply()方法:

def apply(n: Int) = new NonNegativeInt(Math.max(0, n))

显然他们有相同的签名。是否有一种实用的方法/模式来处理字段上的约束?

3 个答案:

答案 0 :(得分:3)

case class NonNegativeInt(i: Int)

如果你不能使用申请,只需将其他名称命名。

object NonNegativeInt {
  def fromInt(i: Int): NonNegativeInt = NonNegativeInt(Math.max(0, i)
}

如果您愿意,可以获得更高级别的东西,使用Refinedshapeless对正向整数进行编译时限制的类型检查,通过密封case class或其他此类方法隐藏主构造函数,但在这种情况下感觉有点矫枉过正。

答案 1 :(得分:2)

虽然我大多同意@ flavian的回答,你应该为你的方法使用另一个名字,但你可以做的就是根本不做一个案例类。或者更确切地说,手动实现案例类构造为您提供的所有内容:

class NonNegativeInt private (val i: Int) {
  override def equals(that: Any): Boolean = that.isInstanceOf[NonNegativeInt] && that.asInstanceOf[NonNegativeInt].i == i
  override def hashCode = i.hashCode

  def copy(i: Int = this.i) = NonNegativeInt(i)  //use companion apply method, not private constructor
}

object NonNegativeInt {
  def apply(i: Int) = 
    new NonNegativeInt(if (i < 0) 0 else i)

  def unapply(that: NonNegativeInt): Option[Int] = Some(that.i)
}

答案 2 :(得分:-1)

据我所知,没有直接的方法来覆盖case类构造函数。但是,假设真实数据类型不是简单的int,你可以做一些考虑无效状态的类型,如下所示:

sealed abstract class NonNegativeInt { def isValid: Boolean }
final case class ValidNonNegativeInt(i: Int) extends NonNegativeInt { override def isValid: Boolean = true }
final case object InvalidNonNegativeInt extends NonNegativeInt { override def isValid: Boolean = false }
object NonNegativeInt {
  def apply(i: Int): NonNegativeInt = if (i < 0) InvalidNonNegativeInt else ValidNonNegativeInt(i)
}

这非常简单:

scala>   NonNegativeInt(0)
res5: NonNegativeInt = ValidNonNegativeInt(0)

scala>   NonNegativeInt(-1)
res6: NonNegativeInt = InvalidNonNegativeInt

然后你甚至可以进行模式匹配:

val ni = NonNegativeInt(10)
ni match {
    case ValidNonNegativeInt(i) => println(s"valid $i")
    case InvalidNonNegativeInt => println(s"invalid")
}

然后,您可以使用map / flatMap等进一步扩展您的功能。

当然,它仍然没有保护你免受负面情况的影响:

scala>   ValidNonNegativeInt(-10)
res7: ValidNonNegativeInt = ValidNonNegativeInt(-10)

但是scala Option例如也不会覆盖允许无效值的Some()情况的构造函数:

scala> Option(null)
res8: Option[Null] = None

scala> Some(null)
res9: Some[Null] = Some(null)

除非没有关键用例,否则对于简单的Int,我会保持原样,并确保其在使用中的正确性。对于更复杂的结构,上面的方法非常有用。

  

注意:我故意不使用你的Max(0,n)方式,就像在这种情况下一样   会造成比解决更多的问题。假设一些东西,和   在引擎盖下交换数据是不好的做法。想象一下,你会有一个   在你的代码的其他地方某处使用你的bug   用Max(0,n)实现。如果输入数据为-10,则最有可能,   问题是由传入数据中的一些其他问题引起的。当你   将其更改为默认值0,即使输入为-10,稍后当您输入时也是如此   将分析日志,转储或调试输出,你会错过它的事实   是-10。

其他解决方案,在我看来:

@flavian解决方案是最合乎逻辑的。明确的功能/验证

@Cyrille Corpet:非常Java&#39; ish

@jwvh解决方案将占用大量内存,因为它在内存中将是两个Int。并且也不会防止压倒一切:

scala>   case class NonNegativeInt1(private val x:Int)(implicit val i:Int = Math.max(0,x)) {
     |     override def toString: String = s"NonNegativeInt1($x, $i)"
     |   }
defined class NonNegativeInt1

scala>   NonNegativeInt1(5)
res10: NonNegativeInt1 = NonNegativeInt1(5, 5)

scala>   NonNegativeInt1(-5)
res11: NonNegativeInt1 = NonNegativeInt1(-5, 0)

scala>   NonNegativeInt1(-5)(-5)
res12: NonNegativeInt1 = NonNegativeInt1(-5, -5)