覆盖默认大小写 class 构造函数 (Scala)
Overwriting the default case class constructor (Scala)
如果我有案例class:
case class NonNegativeInt(i: Int)
其中字段 i
如果参数为负,则设置为 0。所以我不能只使用案例 class 提供的默认构造函数。如果我在伴随对象(或任何地方)中定义一个 apply() 方法:
def apply(n: Int) = new NonNegativeInt(Math.max(0, n))
显然他们有相同的签名。有没有实用的way/pattern来处理字段上的约束?
case class NonNegativeInt(i: Int)
如果您不能使用 apply,只需将其命名为其他名称即可。
object NonNegativeInt {
def fromInt(i: Int): NonNegativeInt = NonNegativeInt(Math.max(0, i)
}
如果你愿意,你可以变得更有趣,使用 Refined
或 shapeless
对正整数进行编译时限制的类型检查文字常量,通过密封 case class
或其他来隐藏主构造函数这样的手段,但在这种情况下感觉有点大材小用了。
据我所知,没有直接的方法来覆盖 case class 构造函数。但是,假设实际数据类型不是简单的 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我会保持原样,并确保其用法的正确性。对于更复杂的结构,上述方法非常有用。
Note: I intentionally not used your Max(0, n) way, as in this case it
will cause more problems than it would solve. Assuming something, and
swapping data under the hood is bad practice. Imagine you will have a
bug somewhere in other place of your code which will use your
implementation with Max(0, n). If input data would be -10, most likely,
problem was caused by some other problem in incoming data. When you
change it to default 0, even through input was -10, later when you
will analyze logs, dumps or debug output, you will miss the fact that it
was -10.
其他解决方案,以我的观点:
@flavian 解决方案是最合乎逻辑的。显式 functionality/validation
@Cyrille Corpet:非常Java'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)
尽管我基本同意@flavian 的回答,即您应该为您的方法使用另一个名称,但您可以做的是根本没有理由 class。或者更确切地说,手动实现 case class 构造为您提供的所有内容:
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)
}
如果我有案例class:
case class NonNegativeInt(i: Int)
其中字段 i
如果参数为负,则设置为 0。所以我不能只使用案例 class 提供的默认构造函数。如果我在伴随对象(或任何地方)中定义一个 apply() 方法:
def apply(n: Int) = new NonNegativeInt(Math.max(0, n))
显然他们有相同的签名。有没有实用的way/pattern来处理字段上的约束?
case class NonNegativeInt(i: Int)
如果您不能使用 apply,只需将其命名为其他名称即可。
object NonNegativeInt {
def fromInt(i: Int): NonNegativeInt = NonNegativeInt(Math.max(0, i)
}
如果你愿意,你可以变得更有趣,使用 Refined
或 shapeless
对正整数进行编译时限制的类型检查文字常量,通过密封 case class
或其他来隐藏主构造函数这样的手段,但在这种情况下感觉有点大材小用了。
据我所知,没有直接的方法来覆盖 case class 构造函数。但是,假设实际数据类型不是简单的 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我会保持原样,并确保其用法的正确性。对于更复杂的结构,上述方法非常有用。
Note: I intentionally not used your Max(0, n) way, as in this case it will cause more problems than it would solve. Assuming something, and swapping data under the hood is bad practice. Imagine you will have a bug somewhere in other place of your code which will use your implementation with Max(0, n). If input data would be -10, most likely, problem was caused by some other problem in incoming data. When you change it to default 0, even through input was -10, later when you will analyze logs, dumps or debug output, you will miss the fact that it was -10.
其他解决方案,以我的观点:
@flavian 解决方案是最合乎逻辑的。显式 functionality/validation
@Cyrille Corpet:非常Java'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)
尽管我基本同意@flavian 的回答,即您应该为您的方法使用另一个名称,但您可以做的是根本没有理由 class。或者更确切地说,手动实现 case class 构造为您提供的所有内容:
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)
}