Tra le principali funzionalità della programmazione orientata agli oggetti, vi è il meccanismo
dell'ereditarietà (già tipico del linguaggio Java),
ossia la possibilità di creare classi che non nascano assolutamente da zero, ma che costituiscano
l'evoluzione di un'altra classe. Anche in Kotlin, quando due classi sono legate da un rapporto di ereditarietà, sono denominate
classe base (quella da cui un'altra deriva) e classe derivata (quella che estende la classe base).
Ogni classe Kotlin deriva dalla superclasse Any
, che può pertanto essere considerata la classe "genitore" di qualsiasi altra definita e definibile nel linguaggio.
Any
può, secondo questa definizione, ricordare la classe Object
di Java, ma il paragone non calza alla perfezione in quanto
Any
non definisce metodi che, come nel caso di Object
, vengono ereditati da tutte le altre classi.
Definire classi derivate
In Kotlin, una classe può essere base di altre se viene definita come open
, altrimenti risulta di default final
e
pertanto non estendibile: questo rappresenta un importante tratto di differenza rispetto al linguaggio Java. Se, ad
esempio, pensiamo ad una classe Libro, questa potrebbe essere l'estensione di una più generica classe Opera contraddistinta da una
stringa denominata "titolo":
open class Opera(titolo:String){
var titolo=titolo
}
class Libro(titolo:String): Opera(titolo){
/*
* membri della classe
*/
}
Per specificare che la classe Libro deriva da Opera, si utilizza il simbolo due punti (class Libro: Opera
), rinunciando
pertanto al termine extends
tipico di Java. È importante notare le incombenze relative al costruttore. La classe Opera, infatti,
ne definisce uno primario per l'impostazione del titolo, e questo dovrà essere presente anche nella classe Libro. La classe derivata
può comunque munirsi di propri costruttori secondari per impostare altre sue proprietà come nel seguente caso:
class Libro(titolo:String): Opera(titolo){
var autore:String?=null
constructor(titoloLibro:String, autoreLibro: String): this(titoloLibro)
{
autore=autoreLibro
}
}
Si ricordi, inoltre, che al momento della creazione di un oggetto di classe derivata verrà comunque prima invocata la costruzione della classe base.
Overriding di metodi e proprietà
In una classe derivata si può ridefinire il comportamento dei membri interni della classe base. Il meccanismo prende anche
in Kotlin il nome di overriding, e si definisce con due parole chiave: open
per indicare il membro della classe base che può
essere ridefinito (stessa keyword usata per la classe) e override
per etichettare la nuova versione inserita in una classe derivata.
Nell'esempio seguente vediamo come ciò possa essere applicato per le classi Opera e Libro dove in entrambi viene definito il metodo
descrizione:
open class Opera(titolo:String){
var titolo=titolo
open fun descrizione():String{
return "Titolo: \"$titolo\"";
}
}
class Libro(titolo:String): Opera(titolo){
var autore:String?=null
get(){
return if (field!=null) field else "Anonimo"
}
constructor(titoloLibro:String, autoreLibro: String): this(titoloLibro)
{
autore=autoreLibro
}
override fun descrizione():String{
return return "Titolo: \"$titolo\"\nAutore: $autore";
}
}
Nel metodo main
proviamo entrambe le classi creando tre oggetti, uno di classe Opera e due di classe Libro. Su tutti e tre viene
invocato il metodo descrizione, che eseguirà, nel primo caso, la versione originale, e negli altri due quella rielaborata con
l'override:
fun main(args:Array<String>) {
var opera=Opera("Dipinto autore fiammingo")
var testo=Libro("Poesie armene")
var testo2=Libro("Manuale di giardinaggio", "Paul L. Collins")
println(opera.descrizione())
println()
println(testo.descrizione())
println()
println(testo2.descrizione())
}
L'output prodotto sarà il seguente:
Titolo: "Dipinto autore fiammingo"
Titolo: "Poesie armene"
Autore: Anonimo
Titolo: "Manuale di giardinaggio"
Autore: Paul L. Collins
Con open
/override
si può svolgere anche l'override di una proprietà purché la sua versione nella classe derivata
sia di un tipo compatibile e abbia un inizializzatore o un getter. Si ricordi, inoltre, che una proprietà della classe base
val
può essere ridefinita come var
, mentre non è consentito il contrario.
In caso di override, è possibile anche sfruttare quanto previsto nella classe base utilizzando la parola chiave super
. Se, ad esempio,
volessimo invocare nell'override del metodo descrizione
il funzionamento della versione originale, potremmo sfruttare super
in questo
modo:
open class Opera(titolo:String){
// versione base del metodo
open fun descrizione():String{
return "Opera titolo \"$titolo\"";
}
}
class Libro(titolo:String): Opera(titolo){
/*
* OMISSIS: ulteriori membri della classe
*/
// override del metodo che sfrutta la versione base mediante super
override fun descrizione():String{
return "${super.descrizione()} \nAutore: $autore";
}
}
L'effetto ottenuto in output sarebbe il medesimo.