Eccezioni in Kotlin

Il Kotlin non modifica la gestione delle eccezioni del Java, infatti essa viene delegata alla libreria del Java: essa presenta una gerarchia di classi che mette in relazione, la classe Exception e la classe Error. Infatti, entrambe queste classi estendono la superclasse Throwable. La seguente figura mostra tale gerarchia:


Immagine 6.1 - Gerarchia delle eccezioni in Java

Come abbiamo già detto, non bisognerà fare confusione tra il concetto di errore (problema che un programma non può risolvere) e di eccezione (problema non critico gestibile). Il fatto che sia la classe Exception sia la classe Error, estendano una classe che si chiama Throwable è dovuto al meccanismo con cui la Java Virtual Machine reagisce quando si imbatte in una eccezione-errore. Infatti, se il nostro programma genera un’eccezione durante il runtime, la JVM istanzia un oggetto dalla classe eccezione relativa al problema, e “lancia” l’eccezione appena istanziata (tramite la parola chiave throw). Se il nostro codice non la cattura (tramite la parola chiave catch) l’eccezione, il gestore automatico della JVM interromperà il programma generando in output informazioni dettagliate su sull’accaduto.

Ci sono 3 modi di gestire le eccezioni:

  • Ignorarle: Se un’eccezione è ignorata da un programma, questo terminerà producendo un messaggio opportuno, Il messaggio mostra la traccia dello stack delle chiamate dei metodi
  • Gestirle quando avvengono
  • Gestirle altrove nel programma

Ignorare l'eccezione

Fino ad adesso abbiamo sempre ignorato qualsiasi eccezione, anche perché la prima cosa che si nota in Kotlin, rispetto a Java, è che non è obbligatorio gestire le eccezioni che in Java vengono dette Checked.

Nonostante questo il Java differenziava i due tipi di eccezioni in quanto:

  • Le eccezioni controllate/Checked: sono dovute a eventi esterni al programma, es: cercare di accedere ad un file inesistente si chiamano controllate perché il compilatore controlla che vengano esplicitamente indicate e intercettate.
  • Le eccezioni non controllate: sono dovute al programma e che potrebbero essere evitate.

Proprio per questo è sempre buona norma gestire tutte le eccezioni che il Java definisce Checked. Questa riga è permessa:

File("file.txt").forEachLine { println(it) }

Se il file non esiste, il programma andrà in crash per via della propagazione dell'eccezione, proprio per questo è meglio gestire la suddetta eccezione.

Gestire l'eccezione

Possiamo comunque gestire le eccezioni in maniera simile a Java, tramite le parole chiave try, catch e finally:

fun main(args: Array<String>) {

try {

File("file.txt").forEachLine { println(it) }

} catch (e: FileNotFoundException) {

println(e.message)

} finally {

println("Fine esecuzione")

}

}

È anche possibile gestire le eccezioni multiple, ed il finally non è obbligatorio:

fun main(args: Array<String>) {

var numbers: MutableList<Int> = ArrayList()

try {

File("file.txt").forEachLine { numbers.add(it.toInt())}

} catch (e: FileNotFoundException) {

println(e.message)

} catch (e: NumberFormatException) {

println(e.message)

}

for(number in numbers){

print("$number ")

}

}

Nessuno comunque ci vieta di realizzare catch multipli insieme al finally:

try {

doSomething()

}

catch(e: FileSystemException) {

handle(e)

}

catch(e: NetworkException) {

handle(e)

}

catch(e: MemoryException) {

handle(e)

}

finally {

cleanup()

}

Inoltre in maniera molto simile all'if/else e al when è possibile far ritornare un calore dal costrutto try/catch:

val a: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }

Inoltre anche in questo caso è possibile usare il blocco finally:

val a: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null } finally { input = "" }

Oppure catch multipli:

val a: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null } catch (e : Exception) { 0 }

Nessuno comunque ci vieta di realizzare catch multipli insieme al finally.

Principali eccezioni

Java mette a disposizione molte eccezioni già confezionate, che descrivono la maggioranza dei problemi che possono verificarsi a run-time. Ognuna di queste è una classe.

RuntimeException: viene sollevata in caso di errore generico di runtime.

NullPointerException (NPE): viene sollevata con l’uso dell’operatore di accesso . ad una variabile che vale null oppure quando viene passato null come parametro di un metodo un argomento su cui il metodo in questione, al suo interno, controlla esplicitamente se il valore è null, la suddetta eccezione verrà analizzata in una futura lezione.

ArrayIndexOutOfBoundsException: viene sollevata se si cerca di accedere a una posizione inesistente in un array.

NumberFormatException: viene sollevata se si tenta di convertire in un numero qualcosa senza il giusto formato.

StringIndexOutOfBoundsException: viene sollevata se si cerca di accedere a una posizione inesistente in una stringa.

ClassCastException: viene sollevata se si tenta di fare un cast non lecito.

IllegalArgumentException: viene sollevata se ad una funzione vengono passati valori non validi.

IOException: viene sollevata in caso di errore di input/output.

EOFException: viene sollevata in caso di lettura una volta finito il file.

FileNotFoundException: viene sollevata in caso di lettura di un file inesistente.

Realizzare un’eccezione personalizzata

Talvolta, può presentarsi l’esigenza di definire delle eccezioni personalizzate, che si scatenino al verificarsi di determinati eventi. In tal caso, sarà sufficiente costruire una nuova classe di eccezioni che discenda da un’altra già esistente, specializzandone il contesto. Sarà, naturalmente, utile far derivare la propria classe da una già presente che più si avvicini alle nostre necessità, in modo da facilitarci il compito. In generale, l’implementazione di una nuova eccezione ha la seguente forma:

class MyException : Exception {

constructor() {

// eventuale codice

// richiamo il costruttore della classe padre in modo implicito nel costruttore di default

}

constructor(messaggio: String) : super(messaggio) {

// eventuale codice

// richiamo il costruttore della classe padre

}

constructor(cause: Throwable) : super(cause) {

// eventuale codice

// richiamo il costruttore della classe padre

}

constructor(messaggio: String, cause: Throwable) : super(messaggio, cause) {

// eventuale codice

// richiamo il costruttore della classe padre

}

}

Lanciare un'eccezione

Una volta realizzata la nostra eccezione personalizzata è necessario usarla, per usarla, come già detto bisogna usare la parola chiave throw:

class MyException : Exception {

constructor()

constructor(messaggio: String) : super(messaggio)

constructor(cause: Throwable) : super(cause)

constructor(messaggio: String, cause: Throwable) : super(messaggio, cause)

}

fun main(args: Array<String>) {

throw MyException("Mio errore")

throw MyException()

}

Naturalmente è possibile lanciare anche una eccezione generica e/o riutilizzare quelle standard del Java/Kotlin: throw ArrayIndexOutOfBoundsException().

Inoltre è possibile lanciare eccezioni anche in altri casi:

class MyException : Exception {

constructor()

constructor(messaggio: String) : super(messaggio)

constructor(cause: Throwable) : super(cause)

constructor(messaggio: String, cause: Throwable) : super(messaggio, cause)

}

fun funzione1(arg : String) {

var string = if(arg != "") arg else throw MyException("Stringa vuota")

}

fun funzione2(arg : String?) {

var string = arg ?: throw MyException("Stringa nulla")

}

fun funzione3(array : Array<String>, index : Int) {

if(index !in 0 until array.size) throw ArrayIndexOutOfBoundsException(index)

}

fun funzione4(array : Array<String>, index : Int) {

if(index in 0 until array.size) throw MyException("Index interno al range")

}

Nella funzione2() notiamo uno strano operatore (?:) si chiama Operatore Elvis o null coalescing. L'operatore sarà analizzato in una prossima lezione, ora ci basta sapere che queste due funzioni sono identiche:

fun funzione2(arg : String?) {

var string = arg ?: throw MyException("Stringa nulla")

}

fun funzione2(arg : String?) {

var string = if(arg != null) arg else throw MyException("Stringa nulla")

}

https://kotlinlang.org/docs/reference/

https://www.mattepuffo.com/blog/categoria/21-kotlin/0.html

https://stackoverflow.com/documentation/kotlin/topics

results matching ""

    No results matching ""