Try … catch … finally return value

后端 未结 4 1685
心在旅途
心在旅途 2021-02-07 01:24

I found that it\'s a shame that I can\'t return a return value from a such simple construction as try ... catch ... finally

 def foo: String = {
            


        
相关标签:
4条回答
  • 2021-02-07 02:03

    In a scala try-catch-finally block, the finally block is evaluated only for side effects; the value of the block as a whole is the value of the last expression in the try (if no exception was thrown) or catch (if one was).

    If you look at the output from the compiler, you'll note that it's complaining about the contents of the catch block, not the finally:

    $ scala test.scala
    /tmp/test.scala:12: error: type mismatch;
     found   : Unit
     required: String
        case e: Exception => e.printStackTrace()
    

    This is because Exception.printStackTrace() returns Unit, so the return type of the function would have to be String if the try succeeded and Unit otherwise.

    You can address this by having the catch block evaluate to a String as well:

    catch {
      case e: IOException => {
        e.printStackTrace()
        e.toString()
      }
    }
    

    Of course, this means there has to be some useful string value you can return even when an error occurs (perhaps ""?); a more idiomatic approach might be to return an Option[String], with the try block returning Some(in.readLine) and the catch block returning None. In either case, though, the value of both the try and catch blocks must match the function signature. The finally block's type is irrelevant.

    For reference, here's a version that passes type checking and works:

    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() returns Unit, but that's ok because the value of the finally block is ignored. The try and catch blocks both return String.

    0 讨论(0)
  • 2021-02-07 02:06

    This is happening because in Scala unlike Java, try-catch are expressions. In your code, try block returns a String and your catch block return type is Unit (as you are just printing and returning nothing).

    The type-inference hence takes the return type T such that T >: Unit and T >: String. Hence T is of type Any. And because you have declared foo as def foo: String. The compiler throws an error as it was expecting String but found Any.

    What you can do is to return a default value under you catch block:

    try{
      in.readLine
    }
    catch {
      case e: IOException => {
                 e.printStackTrace()
                 "error string"
             }
    }
     finally{
         in.close
     }
    
    0 讨论(0)
  • 2021-02-07 02:08

    This method returns a Unit; you have to return something in the catch block; or I'd rather suggest to change the return type to e.g. Option; or use Try class.

    What is obvious - you don't always have a string in this situation. In Java people tend to ignore the realities, but this is Scala, one can do better.

    0 讨论(0)
  • 2021-02-07 02:13

    I think it would help to start with the Java conception of an exception. A Java method is basically a contract to do something (return a value or cause a side effect) if it is called. The method makes certain assumptions (for example, that the operating system will cooperate with a request to read a file). Sometimes, if these conditions are not met, it will return a null value, and sometimes it will stop execution completely and "throw an exception".

    This can be a problem, because the contract of a Java method is not always clear. A Java method that declares a return type of String really has three outcomes: a String value, null, or an exception. One problem with exceptions is that they halt execution of the code, possibly masking other problems further down in the method or possibly failing to close resources that were opened (which is the reason for try, catch, finally)

    Scala seeks clarity regarding the return type. One way to do this would be to collect all the exceptions that occur in the method and then pass that list of exceptions as a return value. But then, we need to have a return type that says "We're going to return something, or we might return nothing" (scala.Option), or perhaps, "We're going to return Either the expected answer or we'll return information about why the expected answer is not being returned" (scala.util.Either), or perhaps, "We're going to Try to do a risky operation, which might result in Success or Failure." (scala.util.Try)

    Scala deals with the possibility of a null value with Option. Option is class that has two subclasses: None and Some, which is a container that holds exactly one element. For example:

    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")
    }
    

    This codes make a phone call to John, and it will log the fact that it was unable call Alex because it couldn't find his phone number in the phone book. But an Option doesn't provide information about why no value was returned. If we want to collect those reasons, we can use an Either. The Either has two subclasses: A Left could store all of the exceptions that were collected in the process of doing the "risky operation" while a Right would be similar to a Some and contain the expected value.

    Doing a fold operation on an Either to convert it to a Left or a Right is somewhat counter-intuitive, and thus we come to scala.util.Try.

    @marius, a scala developer at Twitter, wrote a very good post about the rationale for adopting scala.util.Try. I think this is what you're looking for.

    The essence of scala.util.Try is that a risky action can result in Success or Failure. Before scala.util.Try, developers would use an Option or Either. Here's what it would look like if you did a buffered reader from a file:

    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
    

    Hope this helps!

    0 讨论(0)
提交回复
热议问题