Scala处理缺少嵌套的Optionals



假设我有一个对象A,它可能有一个Option B,它也可能引用另一个可选对象C(嵌套选项)

我看到了几种访问C的方法,比如用于理解、模式匹配或flatMap,它们看起来都很好。但是,如果我必须返回某种错误消息,或者在BC丢失时抛出异常,或者提供类似cannot execute because B is missingC not set的消息,该怎么办?我只能回到(嘲笑):

if(a.b.isDefined)
{
   if(a.b.c.isDefined)
   {
           //do work
   }
   else throw new Exception1 //or call something other
}
else throw new OtherException //or do something other

我如何才能以一种功能性或更流畅的方式处理?

您可以添加一个方法将Option转换为Try,如果值为none,则会失败,例如

implicit class OptionWrapper[T](val o: Option[T]) extends AnyVal {
    def tryGet(e: => Throwable): Try[T] = o match {
        case Some(x) => Success(x)
        case None => Failure(e)
    }   
}

然后你可以做

a.b.tryGet(new OtherException).flatMap(_.tryGet(new Exception1))

您可以使用getOrElse[B >: A](default: ⇒ B): B:

  a.b.getOrElse(throw new OtherException).c.getOrElse(throw new Exception1)

使用implicit包装器和AnyVal优化(如上所述)是个好主意,但代码的可读性会降低。我认为最好使用现有的方法,而不是每次都编写包装器。

关于map,它在这里真的没用。如果你不需要投掷Exception,这是有道理的。

与其将Option转换为其他内容,不如这样做:

    def getC(a: A) = a.b.map { b1 =>
        b1.c.getOrElse {
            throw new UndefinedC
        }
    }.getOrElse {
        throw new UndefinedB
    }

简而言之,如果定义了b,那么map将允许您使用它,然后您可以在c上使用getOrElse来返回它,否则抛出异常。最后一个getOrElse应用于b,因此如果map返回c的实例,它将返回它;否则,如果b未定义,它将允许您抛出异常。

我用Scalatest写了一个快速测试来证明这一点:

import org.scalatest._
class TestClass extends FlatSpec with Matchers with OptionValues with Inside with Inspectors {
    case class C( name: String ) {
        def echo = name
    }
    case class B( c: Option[C] )
    case class A( b: Option[B] )
    class UndefinedB extends Exception
    class UndefinedC extends Exception
    def getC( a: A ) = {
        a.b.map { b1 =>
            b1.c.getOrElse {
                throw new UndefinedC
            }
        }.getOrElse {
            throw new UndefinedB
        }
    }
    "With both b and c defined c.echo" should "be equal to the c.name value" in {
         val c = new C( name = "Alessandro" )
         val b = new B( Some( c ) )
         val a = new A( Some( b ) )
         assert( getC(a).echo.equals( "Alessandro" ) )
    }
    "With only b defined" should "throw UndefinedC" in {
        val c = new C( name = "Alessandro" )
        val b = new B( None )
        val a = new A( Some( b ) )
        intercept[UndefinedC] { getC(a).echo.equals( "Alessandro" ) }
    }
    "With only b not defined" should "throw UndefinedB" in {
        val c = new C( name = "Alessandro" )
        val b = new B( Some( c ) )
        val a = new A( None )
        intercept[UndefinedB] { getC(a).echo.equals( "Alessandro" ) }
    }
}

编辑:顺便说一句,Alexandr Dorokhin的答案比我的少,如果你在测试中使用它,它们就会通过

最新更新