如果我有一个案例类:
case class NonNegativeInt(i: Int)
如果参数为负,则将字段i
设置为0。所以我不能只使用case类提供的默认构造函数。如果我在随播对象(或任何地方)中定义了apply()方法:
def apply(n: Int) = new NonNegativeInt(Math.max(0, n))
显然他们有相同的签名。是否有一种实用的方法/模式来处理字段上的约束?
答案 0 :(得分:3)
case class NonNegativeInt(i: Int)
如果你不能使用申请,只需将其他名称命名。
object NonNegativeInt {
def fromInt(i: Int): NonNegativeInt = NonNegativeInt(Math.max(0, i)
}
如果您愿意,可以获得更高级别的东西,使用Refined
或shapeless
对正向整数进行编译时限制的类型检查,通过密封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)