将一个是 Success[A] 的 Try[A] 映射到 Try[B] 会得到 Success[B] 。 如果它是 Failure[A] ,就会得到 Failure[B] ,而且包含的异常和 Failure[A] 一样。
parseURL("http://danielwestheide.com").map(_.getProtocol) // results in Success("http") parseURL("garbage").map(_.getProtocol) // results in Failure(java.net.MalformedURLException: no protocol: garbage)如果链接多个 map 操作,会产生嵌套的 Try 结构,这并不是我们想要的。 考虑下面这个返回输入流的方法:
import java.io.InputStream def inputStreamForURL(url: String): Try[Try[Try[InputStream]]] = parseURL(url).map { u => Try(u.openConnection()).map(conn => Try(conn.getInputStream)) }由于每个传递给 map 的匿名函数都返回 Try,因此返回类型就变成了 Try[Try[Try[InputStream]]] 。
这时候, flatMap 就派上用场了。 Try[A] 上的 flatMap 方法接受一个映射函数,这个函数类型是 (A) => Try[B]。 如果我们的 Try[A] 已经是 Failure[A] 了,那么里面的异常就直接被封装成 Failure[B] 返回, 否则, flatMap 将 Success[A] 里面的值解包出来,并通过映射函数将其映射到 Try[B] 。
这意味着,我们可以通过链接任意个 flatMap 调用来创建一条操作管道,将值封装在 Success 里一层层的传递。
现在让我们用 flatMap 来重写先前的例子:
这样,我们就得到了一个 Try[InputStream], 它可以是一个 Failure,包含了在 flatMap 过程中可能出现的异常; 也可以是一个 Success,包含了最后的结果。
过滤器和 foreach
当然,你也可以对 Try 进行过滤,或者调用 foreach ,如果你已经学过 Option,对于这两个方法也不会陌生。
当一个 Try 已经是 Failure 了,或者传递给它的谓词函数返回假值,filter 就返回 Failure (如果是谓词函数返回假值,那 Failure 里包含的异常是 NoSuchException ), 否则的话, filter 就返回原本的那个 Success ,什么都不会变:
def parseHttpURL(url: String) = parseURL(url).filter(_.getProtocol == "http") parseHttpURL("http://apache.openmirror.de") // results in a Success[URL] parseHttpURL("ftp://mirror.netcologne.de/apache.org") // results in a Failure[URL]当一个 Try 是 Success 时, foreach 允许你在被包含的元素上执行副作用, 这种情况下,传递给 foreach 的函数只会执行一次,毕竟 Try 里面只有一个元素:
parseHttpURL("http://danielwestheide.com").foreach(println)当 Try 是 Failure 时, foreach 不会执行,返回 Unit 类型。
for 语句中的 Try既然 Try 支持 flatMap 、 map 、 filter ,能够使用 for 语句也是理所当然的事情, 而且这种情况下的代码更可读。 为了证明这一点,我们来实现一个返回给定 URL 的网页内容的函数:
import scala.io.Source def getURLContent(url: String): Try[Iterator[String]] = for { url <- parseURL(url) connection <- Try(url.openConnection()) is <- Try(connection.getInputStream) source = Source.fromInputStream(is) } yield source.getLines()这个方法中,有三个可能会出错的地方,但都被 Try 给涵盖了。 第一个是我们已经实现的 parseURL 方法, 只有当它是一个 Success[URL] 时,我们才会尝试打开连接,从中创建一个新的 InputStream 。 如果这两步都成功了,我们就 yield 出网页内容,得到的结果是 Try[Iterator[String]] 。
当然,你可以使用 Source#fromURL 简化这个代码,并且,这个代码最后没有关闭输入流, 这都是为了保持例子的简单性,专注于要讲述的主题。
在这个例子中,Source#fromURL可以这样用:
import scala.io.Source def getURLContent(url: String): Try[Iterator[String]] = for { url <- parseURL(url) source = Source.fromURL(url) } yield source.getLines()用 is.close() 可以关闭输入流。
模式匹配