22

我发现很遗憾我不能从这样一个简单的结构中返回一个返回值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 作为编程语言的能力来做到这一点。

4

4 回答 4

42

在 scala try-catch-finally 块中,该块针对副作用finally进行评估;整个块的值是(如果没有抛出异常)或(如果有)中最后一个表达式的值。trycatch

如果您查看编译器的输出,您会注意到它在抱怨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。但是,在任何一种情况下,trycatch块的值都必须与函数签名匹配。块的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()返回单位,但这没关系,因为finally块的值被忽略了。和块trycatch返回字符串。

于 2013-09-08T16:02:52.273 回答
14

我认为从 Java 的异常概念开始会有所帮助。如果调用 Java 方法,那么它基本上是做某事(返回值或引起副作用)的契约。该方法做出某些假设(例如,操作系统将配合读取文件的请求)。有时,如果不满足这些条件,它会返回一个空值,有时它会完全停止执行并“抛出异常”。

这可能是个问题,因为 Java 方法的契约并不总是清晰的。声明返回类型 String 的 Java 方法实际上具有三种结果:String 值、null 或异常。异常的一个问题是它们会停止代码的执行,可能会掩盖方法中的其他问题,或者可能无法关闭打开的资源(这是 的原因try, catch, finally

Scala 寻求关于返回类型的清晰性。一种方法是收集方法中发生的所有异常,然后将该异常列表作为返回值传递。但是,我们需要有一个返回类型,表示“我们将返回一些东西,或者我们可能什么也不返回”(scala.Option),或者也许,“我们将返回预期的答案或者我们”将返回有关未返回预期答案的原因的信息”(scala.util.Either),或者可能是“我们将尝试进行有风险的操作,这可能会导致成功或失败。” ( scala.util.Try )

Scala 使用 Option 处理空值的可能性。Option 是具有两个子类的类:Noneand 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")
}

这个代码给 John 打了一个电话,它会记录下它无法呼叫 Alex 的事实,因为它在电话簿中找不到他的电话号码。但是 anOption没有提供关于为什么没有返回值的信息。如果我们想收集这些原因,我们可以使用Either. 有Either两个子类: ALeft可以存储在执行“风险操作”过程中收集的所有异常,而 aRight将类似于 aSome并包含预期值。

对 Either进行折叠操作以将其转换为 Left 或 Right 有点违反直觉,因此我们来到 scala.util.Try。

@marius是 Twitter 的 scala 开发人员,他写了一篇非常好的文章,介绍了采用scala.util.Try的理由。我想这就是你要找的。

scala.util.Try 的本质是一个有风险的操作可能会导致Successor 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

希望这可以帮助!

于 2013-09-09T23:01:19.330 回答
3

这是因为在 Scala 中与 Java 不同,try-catch 是表达式。在您的代码中, try 块返回 aString并且您的 catch 块返回类型是Unit(因为您只是打印并且什么都不返回)。

因此,类型推断采用返回T类型T >: UnitT >: String。因此T是类型Any。而且因为您已将 foo 声明为def foo: String. 编译器会抛出一个错误,因为它期待 String 但找到了 Any。

你可以做的是在你的 catch 块下返回一个默认值:

try{
  in.readLine
}
catch {
  case e: IOException => {
             e.printStackTrace()
             "error string"
         }
}
 finally{
     in.close
 }
于 2013-09-08T16:04:20.860 回答
1

此方法返回一个Unit; 你必须在 catch 块中返回一些东西;或者我宁愿建议将返回类型更改为 eg Option;或使用Try类。

很明显 - 在这种情况下你并不总是有一个字符串。在 Java 中人们倾向于忽略现实,但这是 Scala,可以做得更好。

于 2013-09-11T16:03:49.513 回答