在地图中捕获异常

时间:2010-11-03 17:00:33

标签: scala loops dictionary exception scala-collections

在Scala中循环迭代时处理异常的最佳方法是什么?

例如,如果我有一个可能引发异常的convert()方法,我想捕获该异常,记录它并继续迭代。是否有“scala”方法可以做到这一点?

理想情况下,我喜欢...... ...

val points: Seq[Point] = ...
val convertedPoints: Seq[ConvertedPoint] = points.map(
   p => {
     try { p.convert() } 
     catch { case ex: Exception => logger.error("Could not convert", ex) }
})

您无法执行上述代码,因为它不是从一个列表到另一个列表的直接映射(您返回Seq[Any]而不是Seq[ConvertedPoint])。

4 个答案:

答案 0 :(得分:16)

flatMap可能就是你要找的东西,但map函数有记录副作用,如果点是视图,这些副作用可能不会立即发生:

val convertedPoints = points.view.flatMap { p =>
  try { 
    Some(p.convert) 
  } catch {
    case e : Exception =>
    // Log error
    None
  }
}
println("Conversion complete")
println(convertedPoints.size + " were converted correctly")

这将打印:

Conversion complete
[Error messages]
x were converted correctly

在你的情况下,删除视图,你可能没事。 :)

要使转换成为纯函数(无副作用),您可能会使用Either。虽然我不认为这是值得的(除非你真的想对错误做些什么),这里有一个非常完整的例子:

case class Point(x: Double, y: Double) {
  def convert = {
    if (x == 1.0) throw new ConversionException(this, "x is 1.0. BAD!")
    else ConvertedPoint(x, y)
  }
}
case class ConvertedPoint(x: Double, y: Double)
class ConversionException(p: Point, msg: String) extends Exception(msg: String)


val points = List(Point(0,0), Point(1, 0), Point(2,0))

val results = points.map { p =>
  try {
    Left(p.convert)
  } catch {
    case e : ConversionException => Right(e)
  }
}

val (convertedPoints, errors) = results.partition { _.isLeft }

println("Converted points: " + convertedPoints.map(_.left.get).mkString(","))
println("Failed points: " + errors.map( _.right.get).mkString(","))

答案 1 :(得分:14)

有趣的是我在解释使用scala.util.control.Exception优于try / catch的优势时遇到了很多麻烦,然后我开始看到一些问题,这些问题可以从中得到完美的例子。< / p>

下面:

import scala.util.control.Exception._
List(1, 23, 5, 2, 0, 3, 2) flatMap (x => catching(classOf[Exception]) opt (10 / x))

您自己的代码如下所示:

val points: Seq[Point] = ...
val convertedPoints: Seq[ConvertedPoint] = points.flatMap(
  p => handling(classOf[Exception]) by { ex =>
    logger.error("Could not convert", ex); None
  } apply Some(p.convert)
)

或者,如果你重构它:

val exceptionLogger = handling(classOf[Exception]) by { ex =>
    logger.error("Could not convert", ex); None
}
val convertedPoints: Seq[ConvertedPoint] = points.flatMap(p => exceptionLogger(Some(p.convert)))

答案 2 :(得分:6)

也许你想要一个flatMap。这是一个例子,应该看看它是如何适合的: - )

List(1,2,3,4).flatMap(x => if (x > 2) Some(x) else None)

以上将使用副作用记录(打印或放入可变的东西 - 如果这样做,请确保强制进行评估!)。为了解决副作用和警告,映射函数可能只有Point -> Either[CovertedPoint,Exception],然后结果可以用Seq.partition或类似的方式分隔。

答案 3 :(得分:0)

Scala 2.10开始,您可以使用Try来捕获flatMap的异常,以消除这些异常,并以tapScala 2.13Try记录异常:

List("34", "a", "1", "3", "1l")
  .flatMap(x => Try(x.toInt).tap(_.failed.foreach(println)).toOption)
// java.lang.NumberFormatException: For input string: "a"
// java.lang.NumberFormatException: For input string: "1l"
// res0: List[Int] = List(34, 1, 3)

此:

  • map将每个元素保存到Int(我们的生成异常的代码示例)中,并通过将其包装在Try中加以保护。这将产生一个Try[Int]Failure(NumberFormatException)的{​​{1}}。

  • 这些Success(12)Try踩到tap(或记录)print的错误。 tap在返回原始值时会对任何值产生副作用(在本例中为某些日志记录),因此,返回的Failure值就是它所应用的元素,即未修改的tap

  • 然后我们将Try转换为TryOption变成Success(12),而Some(12)变成Failure(NumberFormatException))应用flatMap来摆脱{{1} s(None s)并提取值(从NoneFailure)中提取12


Some(12)之前,没有Success(12)的等效版本可能是:

Scala 2.13