A Scala programozási nyelv

Kivételkezelés

Hibakezelés Scalában

A hibekezelés feladata annak megadása, hogy mi történjen akkor, ha a programban váratlan esemény lép fel. Az alábbiakban olvasható hogy a Scala hogy jelzi a hibát és hogy lehet a hibás helyzetekből normális állapotba térni.

Hibának tekintjük az alábbiakat:

Exception

Java-ban egy konstrukció van kivételkezelésre, az Exception-ök használata. Java-ban vannak ellenőrzött és nem ellenőrzött (checked, unchecked) kivételek. Scala ezt a kérdést egyszerűvé teszi, ugyanis itt nincsenek ellenőrzött kivételek.

A kivételkezelés – hasonlóan a Java nyelvhez – a try/ catch/ finally szerkezettel valósítható meg a Scala nyelvben. A catch blokkban a mintaillesztést használhatjuk a különböző kivételek elkapására, tehát nem kell több catch ágat felírni. Scala-ban is fontos azonban a sorrend, a case kapcsolóknál, hiszen mintaillesztésnél a leszármazott illeszthető az ősre, ezért az öröklési láncot itt is figyelembe kell venni. Használható az _ joker karakter bármilyen kivétel elkapására.

További különbség a Java-tól, hogy a Scala nem követeli meg a kötelező a kivételkezelést ellenőrzött kivételekre sem, illetve a throws kulcsszó deklarálását sem, de lehetőség itt is van a kivétel továbbdobására.

Míg Java-ban több catch blokkunk is lehet a különböző kivételek elkapásához, Scala-ban csak egy catch blokk megengedett. Mivel azonban Scala-ban mintaillesztéssel történik a kivételek elkapása, így nincs is szükség többre, hiszen egy catch blokkon belül tudunk kezelni különböző típusú kivételeket:

try { ... } catch { case e:SQLException => println("Database error") case e:MalformedURLException => println("Bad URL") case e => { println("Some other exception type:") e.printStackTrace() } } finally { ... }

Szintaktikailag elfogadott, hogy a try blokk után nem adunk catch ágat, csupán finally blokkot, ha nem számít, hogy a programunk kivételt dob (mert benne valahol máshol kezeljük), de valamilyen utasítást mindenképp szeretnénk végrehajtani.

val file = new FileReader("input.txt") try { // Use the file } finally { file.close() // Be sure to close the file }

Érdekes, hasznos különbség még, hogy a Scala nyelvben a try-catch-finally szerkezet visszatérési értékkel is rendelkezik, ha nem keletkezik kivétel a try blokkban, akkor az ott leírt kifejezés értékét, illetve kivétel kiváltása esetén, a catch ágban található kifejezés értéket adja vissza. Kezeletlen kivétel esetén nincs visszatérési érték.

def urlFor(path: String) = try { new URL(path) } catch { case e: MalformedURLException => new URL("http://www.scalalang.org") }

Figyelembe kell venni, hogy a finally ágban is adhatunk meg visszatérési értéket, explicit return utasítással, mivel a blokkra mindenképpen rákerül a vezérlés, ezért felülírja a try vagy catch ágban kapott értéket; azonban, ha nem írunk return kulcsszót, akkor a finally ág nem ad vissza semmit még akkor sem, ha kifejezést írunk bele a try vagy a catch blokkban található kifejezéssel tér vissza a szerkezet.

Kivételek dobása majdnem megegyezik a Java-ból ismerttel:

if (n < 1 || n > 10) { throw new IllegalArgumentException("Only values between 1 and 10") }

Van egy előredefiniált Throwable típus, és vannak ezt kezelő függvények is. Ezek a következők:

class Throwable with { def throw[a]: a } class ExceptOrFinally[a] with { def except (handler: PartialFunction[Throwable,a]): a def finally (def handler: Unit): a } def try [a] (def body: a): ExceptOrFinally[a]

A Throwable típus reprezentálja a kivételeket és hiba objektumokat. A throw metódus az adott kivételt okozza. A try függvény végrehajtja a törzset a kivétel kezelővel.

Option

Opcionális értékek reprezentálására használatos. Az Option elemei vagy a scala.Some leszármazottai, vagy None típusú objektumok.

val name: Option[String] = request getParameter "name" val upper = name map { _.trim } filter { _.length != 0 } map { _.toUpperCase } println(upper getOrElse "")

Ez a kód ekvivalens az alábbival:

name <- request getParameter "name" trimmed <- Some(name.trim) upper <- Some(trimmed.toUpperCase) if trimmed.length != 0 } yield upper println(upper getOrElse "")

Fontos az Option esetén megemlíteni, hogy a Some(null) kifejezés is valid, ez esetben None-t kapunk vissza.

Either

Az Either egy diszjunkt unió konstrukció. Visszatérési értéke vagy Left[L] vagy Right[R] egy példánya. Gyakran használják hibakezelésre, úgy, hogy konvenció szerint Left reprezentálja a hibát és Right pedig a sikert.

Gyakorlatban tökéletes a várható külső hibák kezelésére, mint például parzolási, vagy validációs hibák. Hasonló az Option-höz, azzal a különbséggel, hogy az Either esetében a scala.None helyett Left a hibaág.

Példa, ahol az Either[String, Int] segítségével döntjük l, hogy a kapott input String vagy Int típusú.

val in = Console.readLine("Type Either a string or an Int: ") val result: Either[String,Int] = try { Right(in.toInt) } catch { case e: Exception => Left(in) } println( result match { case Right(x) => "You passed me the Int: " + x + ", which I will increment. " + x + " + 1 = " + (x+1) case Left(x) => "You passed me the String: " + x })

Try

Egy olyan műveletet reprezentál, ami vagy exceptiont fog dobni, vagy egy helyes értéket tartalmaz.

A Try konstrukció hasonlít az Either-hez, azonban ahelyett, hogy valamilyen típust adna vissza Left vagy Right-ba csomagolva, helyette Failure[Throwable] vagy Success[T] típusokkal tér vissza.

Hasonló a try-catch blokkhoz, azonban a try-catch által használt verem alapú hibakezelést heap alapú kezelésre cseréli. Ahelyett hogy az exception dobását követően azonnal le kellene azt kezelni még ugyanabban a threadben, helyette elválasztja a hibakezelést és a helyreállítást.

import scala.util.{Try, Success, Failure} def divide: Try[Int] = { val dividend = Try(Console.readLine("Enter an Int that you'd like to divide:\n").toInt) val divisor = Try(Console.readLine("Enter an Int that you'd like to divide by:\n").toInt) val problem = dividend.flatMap(x => divisor.map(y => x/y)) problem match { case Success(v) => println("Result of " + dividend.get + "/"+ divisor.get +" is: " + v) Success(v) case Failure(e) => println("You must've divided by zero or entered something that's not an Int. Try again!") println("Info from the exception: " + e.getMessage) divide } }

Összefoglalás