我发现很遗憾我无法从try ... catch ... finally
这样简单的结构中返回返回值
def foo: String = {
val in = new BufferedReader(.....)
try {
// val in = new BufferedReader(.....) -- doesn't matter
in.readLine
}
catch {
case e: IOException => e.printStackTrace()
}
finally {
in.close()
}
}
此代码无法编译。有没有办法让编译期望使用任何库,高级结构等?我想只使用纯Scala作为编程语言的能力来做到这一点。
答案 0 :(得分:38)
在scala try-catch-finally块中,finally
块仅计算 的副作用;整个块的值是try
中的最后一个表达式的值(如果没有抛出异常)或catch
(如果有的话)。
如果你查看编译器的输出,你会注意到它抱怨catch
块的内容,而不是finally
:
$ scala test.scala
/tmp/test.scala:12: error: type mismatch;
found : Unit
required: String
case e: Exception => e.printStackTrace()
这是因为Exception.printStackTrace()
返回Unit
,因此如果String
成功,则函数的返回类型必须为try
,否则为Unit
。
您可以通过将catch
块评估为字符串来解决此问题:
catch {
case e: IOException => {
e.printStackTrace()
e.toString()
}
}
当然,这意味着即使发生错误(也许是""
?),您仍然可以返回一些有用的字符串值。更惯用的方法可能是返回Option[String]
,try
块返回Some(in.readLine)
,catch
块返回None
。但在任何一种情况下,try
和catch
块的值都必须与函数签名匹配。 finally
块的类型无关紧要。
供参考,这是一个通过类型检查并运行的版本:
import java.io.BufferedReader
import java.io.InputStreamReader
import java.io.IOException
def foo: String = {
val in = new BufferedReader(new InputStreamReader(System.in))
try {
in.readLine
}
catch {
case e: IOException => { e.printStackTrace(); e.toString() }
}
finally {
in.close()
}
}
System.out.println("Return value: " + foo)
in.close()
返回Unit,但这没关系,因为忽略了finally
块的值。 try
和catch
块都返回String。
答案 1 :(得分:13)
我认为从一个例外的Java概念开始会有所帮助。如果调用它,Java方法基本上是做某事(返回值或引起副作用)的契约。该方法做出某些假设(例如,操作系统将与读取文件的请求合作)。有时,如果不满足这些条件,它将返回一个空值,有时它将完全停止执行并“抛出异常”。
这可能是个问题,因为Java方法的契约并不总是很清楚。声明返回类型为String的Java方法实际上有三个结果:String值,null或异常。异常的一个问题是它们停止执行代码,可能会掩盖方法中的其他问题,或者可能无法关闭打开的资源(这是try, catch, finally
的原因)
Scala寻求关于返回类型的清晰度。一种方法是收集方法中出现的所有异常,然后将该异常列表作为返回值传递。但是,我们需要有一个返回类型,说“我们要返回一些东西,或者我们可能什么也不返回”(scala.Option),或者也许,“我们将返回预期答案或我们将返回有关为什么没有返回预期答案的信息“(scala.util.Either),或者可能,”我们将尝试进行有风险的操作,这可能会导致成功或失败。“ (scala.util.Try)
Scala使用Option处理null值的可能性。 Option是具有两个子类的类:None
和Some
,它是一个只包含一个元素的容器。例如:
val contacts = Map("mark" -> 1235551212, "john" -> 2345551212, "william" -> 3455551212)
val phoneOfJohn: Option[Int] = contacts.get("john")
val phoneOfAlex: Option[Int] = contacts.get("alex")
phoneOfJohn match {
case Some(number) => callPhone(number)
case None => Logger.debug("unable to call John, can't find him in contacts")
}
phoneOfAlex match {
case Some(number) => callPhone(number)
case None => Logger.debug("unable to call Alex, can't find him in contacts")
}
此代码打电话给约翰,它会记录无法拨打Alex的事实,因为它无法在电话簿中找到他的电话号码。但Option
不提供有关未返回任何值的原因的信息。如果我们想收集这些原因,我们可以使用Either
。 Either
有两个子类:A Left
可以存储在执行“冒险操作”过程中收集的所有异常,而Right
类似于Some
并包含期望值。
对Either执行fold操作将其转换为Left或Right有点违反直觉,因此我们来scala.util.Try。
Twitter上的scala开发人员@marius写了一篇非常好的post关于采用scala.util.Try的理由。我认为这就是你要找的东西。
scala.util.Try的本质是冒险行为可能导致Success
或Failure
。在scala.util.Try之前,开发人员将使用Option或Either。如果你从文件中做了一个缓冲的阅读器,它会是什么样子:
import scala.util.{Try, Failure, Success}
def foo(fileName: String): Try[String] = Try {
scala.io.Source.fromFile(fileName).bufferedReader().readLine()
}
def bar(file: String): Unit = foo(file) match {
case Success(answer) => Logger.info(s"$file says the answer is $answer")
case Failure(e) => Logger.error(s"couldn't get answer, errors: ${e.getStackTrace}")
}
bar("answer.txt") \\ will log the answer or a stack trace
希望这有帮助!
答案 2 :(得分:3)
这种情况正在发生,因为在Scala中,与Java不同,try-catch是表达式。在您的代码中,try块返回String
,并且您的catch块返回类型为Unit
(因为您只是打印并且不返回任何内容)。
因此,类型推断采用返回类型T
,使得T >: Unit
和T >: String
。因此T
的类型为Any
。因为你已将foo声明为def foo: String
。编译器抛出错误,因为它期望String但找到Any。
您可以做的是在catch块下面返回一个默认值:
try{
in.readLine
}
catch {
case e: IOException => {
e.printStackTrace()
"error string"
}
}
finally{
in.close
}
答案 3 :(得分:1)
此方法返回Unit
;你必须在catch块中返回一些东西;或者我建议将返回类型更改为例如Option
;或使用Try
类。
显而易见的是 - 在这种情况下你并不总是有一个字符串。在Java中,人们倾向于忽略现实,但这是Scala,人们可以做得更好。