L’ereditarietà è un principio fondamentale della programmazione orientata agli oggetti (OOP) che consente di:

  • Riutilizzare codice già scritto (evitando duplicazioni)
  • Specializzare comportamenti tramite sottoclassi
  • Organizzare logicamente le entità in una gerarchia

In parole semplici: una classe figlia eredita attributi e metodi da una classe madre. In Kotlin, l’ereditarietà è dichiarata esplicitamente usando la parola chiave open (a differenza di linguaggi come Java, dove le classi sono ereditabili di default).

Superclassi, sottoclassi e gerarchia delle classi

  • Superclasse (o classe base): la classe “generica” da cui altre classi possono ereditare
  • Sottoclasse (o classe derivata): estende la superclasse, ereditando comportamenti e proprietà
  • Gerarchia delle classi: struttura ad albero che rappresenta le relazioni tra superclasse e sottoclassi

📌 In Kotlin, l’ereditarietà non è implicita come in Java: le classi devono essere dichiarate open per poter essere estese.

Illustrazione in stile manga che rappresenta il concetto di ereditarietà in programmazione orientata agli oggetti. Un personaggio genitore etichettato "class Animale" tiene un cartello con "open fun verso()". Due personaggi figli, un cane e un gatto chibi, mostrano l’override del metodo con le nuvolette "Abbaia!" e "Miagola!". Una freccia etichettata "Ereditarietà" collega le classi, con un diagramma UML sullo sfondo.

Dichiarazione di una superclasse (open)

Per fare si che una classe possa essere estesa bisogna aggiungere la parola chiave OPEN

open class Animale(val nome: String) {
    open fun verso() {
        println("L'animale fa un verso")
    }
}

Estendere una superclasse, per estendere la superclasse bisogna definirne una nuova con i suoi valori, quelli che eredita dalla superclasse non possono avere ne val ne var, quelli nuovi invece si. Bisogna richiamare il nome della superclasse preceduto dai due punti :

class Cane(nome: String) : Animale(nome) {
    override fun verso() {
        println("$nome abbaia!")
    }
}
class Gatto(nome: String) : Animale(nome) {
    override fun verso() {
        super.verso()  // chiama il metodo della superclasse
        println("$nome miagola.")
    }
}

di seguito un esempio abbastanza completo in cui si estendono sia le classi che i relativi metodi, e in cui vengono richiamati sia i metodi originali che quelli modificati

package Class

open class animal(var name: String, var specie: String, var age: Int) {
    open fun printdata() {
        println("Nome animale: $name, apprtenente alla specie $specie, eta $age")
    }

    open fun eat() {
        println("$name in eating")
    }

    open fun sleep() {
        println("$name i'm sleeping")
    }
}

class felini (name:String, specie: String, age:Int, var zoo: String ) : animal (name,specie,age) {
    fun divora(){
        println("il felino $name divora uno gnu")
    }
}

class mammiferi (name: String, specie: String, age:Int, var colore: String ) : animal (name,specie,age) {
    /*esempio di override dell funzione della superclasse e cioè andiamo a modificare la funzzione della classe madre
     per le nostre esigenze il costrutto è il termine super che richiama la superclasse.
     Attenzione quando si richiama il metodo i n questo caso sleep si esegue anche il codice gia presente nel metodo sleep nella superclasse
     piu quello che viene specificato nel override del metodo
     in questo caso produce:

     il mammifero Amedeo pascola nella savana
     Amedeo i'm sleeping
     il mammifero la cui specie è zebra, e l'eta è 45 sta doromendo
    */
    override fun sleep() {
        super.sleep() // il costrutto super in base alle esigenze potrebbe anche non essere la prima riga di codice in questo caso stare sotto il println
        println("il mammifero la cui specie è $specie, e l'eta è $age sta doromendo")
    }
    fun pascola(){
        println("il mammifero $name pascola nella savana")
    }
}

fun main() {
    val felix = felini("FelixilLeone", "leonis", 15, "napolizoo" )
    felix.divora()
    val unmammifero = mammiferi("Amedeo", "zebra", 45, "bianconera" )
    unmammifero.pascola()
    // richiamo un metodo della superclasse
    unmammifero.sleep()

    /* per capire se una classe deve eridetare un'altra classe bisogna farsi la domanda "è un?"
    ad esempio la sottoclasse mammiferi sono animali? se si allora si può usare l'ereditarietà */
}

Uso della parola chiave super
Quando una classe estende (:) un’altra classe e ne sovrascrive un metodo, super permette di richiamare la versione originale del metodo definita nella superclasse.
Perché usarlo?
Riutilizzare la logica già esistente nella superclasse.
Estendere il comportamento della superclasse invece di sostituirlo completamente.
Esempio Base

open class Animale {
    open fun faiVerso() {
        println("L'animale fa un verso generico.")
    }
}

class Cane : Animale() {
    override fun faiVerso() {
        super.faiVerso() // Chiama la versione della superclasse
        println("Bau Bau!") // Aggiunge nuovo comportamento
    }
}

fun main() {
    val cane = Cane()
    cane.faiVerso() 
    // Output:
    // L'animale fa un verso generico.
    // Bau Bau!
}

super con le Interfacce (Multi-Implementazione), si usa:
Se due interfacce hanno metodi con lo stesso nome e vuoi usarli entrambi.
Se vuoi personalizzare l’ordine di esecuzione.

interface Volante {
    fun vola() {
        println("Sto volando in modo generico.")
    }
}

interface Supereroe {
    fun vola() {
        println("Sto volando con un mantello!")
    }
}

class Superman : Volante, Supereroe {
    override fun vola() {
        super<Volante>.vola() // Chiama vola() di Volante
        super<Supereroe>.vola() // Chiama vola() di Supereroe
        println("Superman vola a velocità supersonica!")
    }
}

fun main() {
    val superman = Superman()
    superman.vola()
    // Output:
    // Sto volando in modo generico.
    // Sto volando con un mantello!
    // Superman vola a velocità supersonica!
}

Un altro esempio in cui ereditiamo una classe ne sovrascriviamo i metodi e li richiamo sia quelli sovrascitti che quelli originali con la parola SUPER

open class Animale {
    fun respira() { // Questo metodo non è open, quindi non può essere sovrascritto (ma può essere ereditato)
        println("L'animale sta respirando.")
    }

    open fun faiSuono() { // Questo metodo è open, quindi può essere sovrascritto
        println("L'animale fa un suono generico.")
    }

    val nome: String = "Senza Nome" // Proprietà non open
}

class Cane : Animale() {
    // Non abbiamo sovrascritto 'respira()'
    // Non abbiamo sovrascritto 'nome'

    override fun faiSuono() { // Qui sovrascriviamo 'faiSuono()'
        super.faiSuono() // E qui usiamo super per chiamare la versione della superclasse
        println("Il cane abbaia!")
    }

    fun riportaPalla() {
        println("Il cane riporta la palla.")
    }
}

fun main() {
    val cane = Cane()

    // Chiamata a 'respira()': non è stato sovrascritto, quindi non serve 'super'
    cane.respira() // Output: L'animale sta respirando.

    // Accesso a 'nome': non è stato sovrascritto, quindi non serve 'super'
    println("Il nome dell'animale è: ${cane.nome}") // Output: Il nome dell'animale è: Senza Nome

    // Chiamata a 'faiSuono()': questo è stato sovrascritto, ma internamente usiamo 'super'
    cane.faiSuono()
    /* Output:
    L'animale fa un suono generico.
    Il cane abbaia!
    */

    // Metodo specifico della sottoclasse
    cane.riportaPalla() // Output: Il cane riporta la palla.
}

Torna all’indice

Di Admin

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *