Scala - 实例化类时处理空字符串



我有一个包含几个字符串字段的案例类。我使用字符串拆分的值结果来实例化类,但当任何字段为空字符串时,我想得到错误。例如:

case class SomeClass(a: String, b: String)
val line = ",a"
val values = line.split(",")
SomeClass(values(0), values(1))

将导致:

SomeClass = SomeClass(,a)

显然,我可以单独检查每个元素是否为空,但是有没有更优雅和实用的方法可以防止在有空字段时实例化?

这个答案需要外部库,如果你想坚持核心 Scala,可能不适合你,但我会鼓励这样做,主要是因为构造函数是一个合约。这里说You give me 2 strings and I will give you SomeClass::String -> String -> SomeClass.在我看来,即使输入符合标准,这也可能不会得到尊重,这一事实打破了合理性。有几件事,我会建议

1( 使用细化类型

import eu.timepit.refined.types.string._
import eu.timepit.refined._
import eu.timepit.refined.auto._
case class SomeClass(a: NonEmptyString, b: NonEmptyString)

将无法使用空字符串构造它。您可以对错误执行任何操作。

2(如果你不想使用精炼类型=>我认为Either给了你更多的权力,关于为什么事情失败了。一个不错的选择是Validated半组。

import cats.data._
import cats.data.Validated._
import cats.implicits._
sealed trait ValidationError {
def error: String
}
case object StringIsEmptyError extends ValidationError{
def error = "Argument Can't Be Empty String"
}
type ValidationResult[A] = ValidatedNec[ValidationError, A]
def validateArgument(s: String): ValidationResult[String] = if (s.nonEmpty) s.validNec else StringIsEmptyError.invalidNec
object SomeClass {
def apply(a: String, b: String) = (validateArgument(a), validateArgument(b)).mapN(SomeClass).toEither
}

这比您可能想要的代码多一点,但我认为如果您将来对参数有更多要求,则更容易推理 + 易于扩展。

OptionEither相反,Validated的一个好处是,它允许您累积错误。因此,在这种情况下,如果两个参数都为空,则可以收集两个错误并向用户报告,而不是模棱两可地失败

试一试。

在伴随对象中创建一个或多个用于构造和验证预期参数的工厂方法。将主构造函数设为私有,以便无法绕过工厂方法。

case class SomeClass private (a: String, b: String)
object SomeClass {
def apply(ab: String): Option[SomeClass] = util.Try{
val Array(a, b) = ab.split(",")
apply(a, b)
}.getOrElse(None)
def apply(a: String, b: String): Option[SomeClass] =
if (a.isEmpty || b.isEmpty) None
else Some(new SomeClass(a, b))
}

测试:

SomeClass("aa,bb") //res0: Option[SomeClass] = Some(SomeClass(aa,bb))
SomeClass(",cc")   //res1: Option[SomeClass] = None
SomeClass("g:g")   //res2: Option[SomeClass] = None
new SomeClass("x", "y")  //fails to compile

您可以使用应用函数定义配套对象。

case class SomeClass(a: String, b: String)
object SomeClass {
def apply(a: String, b: String): SomeClass = {
if (a.trim.isEmpty || b.trim.isEmpty) throw new IllegalArgumentException
SomeClass(a, b)
}
}
@ SomeClass("", "b")
java.lang.IllegalArgumentException
ammonite.$sess.cmd4$SomeClass$.apply(cmd4.sc:3)
ammonite.$sess.cmd5$.<init>(cmd5.sc:1)
ammonite.$sess.cmd5$.<clinit>(cmd5.sc)
case class SomeClass(a: String, b: String) {
require(a.nonEmpty && b.nonEmpty, "a and b cannot be empty")
}

您可以通过简单的要求来实现它。但是如果你想在编译时根据类型来限制它,你可以按照@sinanspd上述答案中的建议使用NonEmptyString

https://scastie.scala-lang.org/shankarshastri/uPk8ZMqCQUa5G3RcKfZtwA/

最新更新