Once again about try and Try

Exceptions checked and not

In short, exceptions are needed to separate the positive scenario (when everything is going well) from the negative (when an error occurs and the positive scenario is interrupted). This is useful because very often there is little information to handle the error in the code and you need to convey information about what happened above.





For example, there is a function for reading a number from a file (or not a number, it doesn't matter):





String readStoredData(String id) throws FileNotFoundException, IOException {
    File file = new File(storage, id + ".dat");
    try (BufferedReader in = new BufferedReader(new FileReader(file))) {
        return in.readLine();
    }
}
      
      



As you can see, there is no code here that decides what to do in case of an error. And it's not clear what to do - terminate the program, return "", null or something else? Therefore, exceptions are declared in throws



and will be handled somewhere on the caller:





int initCounter(String name) throws IOException, NumberFormatException {
    try {
        return Integer.parseInt(readStoredData(name));
    } catch (FileNotFoundException e) {
        return 0;
    }
}
      
      



Exceptions in Java are divided into checked and unchecked. In this case, IOException



it is checked - you must declare it in throws



and then process it somewhere, the compiler will check it. NumberFormatException



unverifiable - its processing remains on the programmer's conscience and the compiler will not control you.





There is also a third type of exception - fatal errors ( Error



), but they usually don't make sense to handle, so you shouldn't care about them.





, – , .





:





  • ;





  • ( – ) .









- , . .





Scala?

Scala: ( ), .





Try[T]



– , , . Scala:





def readStoredData(id: String): Try[String] =
  Try {
    val file = new File(storage, s"$id.dat")
    val source = Source.fromFile(file)
    try source.getLines().next()
    finally source.close()
  }

def initCounter(name: String): Try[Int] = {
  readStoredData(name)
    .map(_.toInt)
    .recover {
      case _: FileNotFoundException => 0
    }
}
      
      



, , readStoredData



String



, Try[String]



– . Try Java – , .





:





  • ( Either[Error, T]



    , );





  • happy-path , (Try/get



    for/map/flatMap



    );





  • Java - , ( Java , ).





( Try[String]



– ). Option[T]



– , Future[T]



– ..





, – . / Java, ( ).





:





  1. FileNotFoundException



    ,





  2. IOException



    –





:





def readStoredData(id: String): Option[Try[String]] = {
  val file = new File(storage, s"$id.dat")
  if (file.exists()) Some(
    Try {
      val source = Source.fromFile(file)
      try source.getLines().next()
      finally source.close()
    }
  )
  else None
}
      
      



Option[Try[String]]



, , :





  1. None



    –





  2. Some(Success(string))



    –





  3. Some(Failure(exception))



    – ,





Try



. Java , null. .





The abundance of types creates more visual noise and often requires more complex code when working with multiple effects at the same time. But in return it provides self-documenting code and enables the compiler to find many errors.








All Articles