Docsity
Docsity

Prepara i tuoi esami
Prepara i tuoi esami

Studia grazie alle numerose risorse presenti su Docsity


Ottieni i punti per scaricare
Ottieni i punti per scaricare

Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium


Guide e consigli
Guide e consigli


Algoritmi e programmazione: overloading, cast e abstract, Appunti di Algoritmi E Programmazione Avanzata

Appunti del corso di ALGORITMI E PROGRAMMAZIONE del prof. Fabio Sartori. Overloading, cast e abstract

Tipologia: Appunti

2022/2023

In vendita dal 05/06/2023

rossimartaa
rossimartaa 🇮🇹

4.3

(18)

156 documenti

1 / 7

Toggle sidebar

Questa pagina non è visibile nell’anteprima

Non perderti parti importanti!

bg1
Il costruttore è un “metodo” particolare che serve a definire gli attributi di un particolare oggetto,
dove ricordiamo un oggetto è un’istanza di una classe, un esemplare. Il costruttore non può essere
considerato un metodo perché non ha tipo di ritorno, la sua unica funzione è quella di inizializzare gli
attributi.
Come detto, la classe raggruppa a fattor comune le caratteristiche di un insieme di oggetti e,
viceversa, a partire dalla classe vengono definiti i singoli oggetti come istanze. Le caratteristiche
specifiche di tali istanze verranno date attraverso i costruttori che vengono definiti all’interno della
definizione di classe.
Il costruttore viene invocato nell’istante in cui a run time creo l’oggetto attraverso l’operatore new.
Le eventuali modifiche di un attributo vengono fatte con il setter, che viene richiamato in caso di
necessità, non direttamente sugli attributi.
!!! Stampe, messagginon competono al costruttore.
Il costruttore ha senso solo nell’istante in cui l’oggetto viene creato. Il costruttore costruisce l’oggetto
secondo quelle che sono le specifiche date dal dominio di riferimento; nel fare ciò il costruttore può
accettare parametri.
Nel modellare un dominio potrò avere, a seconda della complessità dell’insieme di oggetti che ho,
più attributi per definirne le caratteristiche. Non è detto che tali caratteristiche siano note tutte allo
stesso istante (ossia immediatamente alla creazione dell’oggetto stesso), potrebbero divenire note in
seguito. Per questo diventa essenziale la possibilità di specificare più costruttori: consente di creare
oggetti a differenti livelli di granularità a seconda delle conoscenze che io ho a un certo istante di
evoluzione, è un vantaggio da un punto di vista semantico.
Ricordiamo inoltre che c’è anche costruttore di default per dare valori standardizzati, ma deve essere
la nostra ultima risorsa: è scorretto pensare che tutti gli oggetti in un dominio nascano con gli stessi
valori standardizzati.
Che rapporto c’è fra i costruttori creati? Costruendo più costruttori all’interno di una classe ho
l’overloading: questo rappresenta il legame esistente fra un costruttore più specifico e uno meno
specifico. Se ho un costruttore di default nella classe e uno con più parametri, devo richiamare il
costruttore con più parametri anche se il costruttore di default viene specificato sempre con i valori
di default.
Non effettuando l’overloading ma ridefinendo sempre il codice del costruttore di default, devo
riprodurre il codice: il codice funziona ma questo non è il metodo ottimale. Il rischio infatti è quello di
riprodurre dei punti di errore. Lobiettivo della programmazione è ridurre i punti di errore:
realizzando overloading riusciamo a ridurre i margini di errore; definiamo un unico punto in cui il
codice viene espresso e le modifiche si ripercuotono in maniera automatica dove c’è bisogno e se
correggo errore una volta è corretto ovunque.
Anche se il costruttore che devo ridefinire è quello di default, va fatto l’overloading.
pf3
pf4
pf5

Anteprima parziale del testo

Scarica Algoritmi e programmazione: overloading, cast e abstract e più Appunti in PDF di Algoritmi E Programmazione Avanzata solo su Docsity!

Il costruttore è un “metodo” particolare che serve a definire gli attributi di un particolare oggetto, dove ricordiamo un oggetto è un’istanza di una classe, un esemplare. Il costruttore non può essere considerato un metodo perché non ha tipo di ritorno, la sua unica funzione è quella di inizializzare gli attributi. Come detto, la classe raggruppa a fattor comune le caratteristiche di un insieme di oggetti e, viceversa, a partire dalla classe vengono definiti i singoli oggetti come istanze. Le caratteristiche specifiche di tali istanze verranno date attraverso i costruttori che vengono definiti all’interno della definizione di classe. Il costruttore viene invocato nell’istante in cui a run time creo l’oggetto attraverso l’operatore new. Le eventuali modifiche di un attributo vengono fatte con il setter, che viene richiamato in caso di necessità, non direttamente sugli attributi. !!! Stampe, messaggi… non competono al costruttore. Il costruttore ha senso solo nell’istante in cui l’oggetto viene creato. Il costruttore costruisce l’oggetto secondo quelle che sono le specifiche date dal dominio di riferimento; nel fare ciò il costruttore può accettare parametri. Nel modellare un dominio potrò avere, a seconda della complessità dell’insieme di oggetti che ho, più attributi per definirne le caratteristiche. Non è detto che tali caratteristiche siano note tutte allo stesso istante (ossia immediatamente alla creazione dell’oggetto stesso), potrebbero divenire note in seguito. Per questo diventa essenziale la possibilità di specificare più costruttori: consente di creare oggetti a differenti livelli di granularità a seconda delle conoscenze che io ho a un certo istante di evoluzione, è un vantaggio da un punto di vista semantico. Ricordiamo inoltre che c’è anche costruttore di default per dare valori standardizzati, ma deve essere la nostra ultima risorsa: è scorretto pensare che tutti gli oggetti in un dominio nascano con gli stessi valori standardizzati. Che rapporto c’è fra i costruttori creati? Costruendo più costruttori all’interno di una classe ho l’overloading: questo rappresenta il legame esistente fra un costruttore più specifico e uno meno specifico. Se ho un costruttore di default nella classe e uno con più parametri, devo richiamare il costruttore con più parametri anche se il costruttore di default viene specificato sempre con i valori di default. Non effettuando l’overloading ma ridefinendo sempre il codice del costruttore di default, devo riprodurre il codice: il codice funziona ma questo non è il metodo ottimale. Il rischio infatti è quello di riprodurre dei punti di errore. L’obiettivo della programmazione è ridurre i punti di errore: realizzando overloading riusciamo a ridurre i margini di errore; definiamo un unico punto in cui il codice viene espresso e le modifiche si ripercuotono in maniera automatica dove c’è bisogno e se correggo errore una volta è corretto ovunque. Anche se il costruttore che devo ridefinire è quello di default, va fatto l’overloading.

Tornando ora all’ereditarietà, quando faccio extends avrò un nuovo costruttore che va a definire attributi in più. Siamo obbligati a richiamare il costruttore della superclasse perché gli attributi ereditati saranno definiti in superclasse. Abbiamo dunque visto due parole chiave: THIS, usata per fare overloading di costruttori all’interno di una stessa classe, e SUPER, usata per richiamare il costruttore di una classe base in una sottoclasse. Entrambe le keyword devono rappresentare la prima istruzione del costruttore quando compaiono, quindi non possono essere utilizzate insieme. Ma posso usare super per estendere il costruttore più specifico e poi invocare this per fare overloading degli altri costruttori interni della classe. Si fa Overriding su costruttore più specifico e overloading su costruttori meno specifici. Se la superclasse ha costruttore di default, anche ridefinito, viene richiamato in maniera automatica nelle sottoclassi, anche se non lo invoco esplicitamente. Invece se la superclasse esporta unicamente costruttori con parametri sono obbligato a invocarlo esplicitamente. Ci sono tanti casi in cui potrei ottenere dei side effects: soprattutto quando ho dei costruttori di default che stampano dei messaggi. Dunque sui metodi uso super con dot notation, nei costruttori super() Si ricordi inoltre che con i metodi si fa al contrario che con i costruttori, si parte dal meno specifico e poi si va al più specifico. Punto chiave: il nostro obiettivo è quello di MODELLARE I PROBLEMI! ANCORA UN ESEMPIO Realizziamo il seguente diagramma di classi. La relazione è quella di is-a. Creiamo una classe in cui definiamo un array di 3 animali ed inseriamo un gatto, un cane ed una papera Per ciascuno di essi invochiamo rispettivamente: dormi, parla e mangia. public abstract class Animale{ private String nome; public void dormi(){ System. out .println("Animale dorme"); } public void mangia(){ System. out .println("Animale mangia"); } public abstract void parla(); System. out .println("Dipendeeee") } }

Ciò che verrà stampato a video sarà: Animale dorme Animale dorme Animale dorme Animale mangia Animale mangia Animale mangia Miaoooo Qua quaaaa Bau Bauuuu Se non avessimo definito il metodo parla a livello di animale, il codice non avrebbe funzionato. definendolo, quello che si osserva nella stampa però è che non viene invocato: la stampa dovrebbe essere dipende, invece nel diagramma ho il metodo parla definito nei singoli oggetti. Ciò in realtà è coerente con il nostro obiettivo: il mio intento è quello di definire come viene implementato il metodo a livello della singola classe.

  1. Perchè ho un errore togliendo il metodo parla dalla classe Animale? Quello che il compilatore mi dice è “cannot find symbol”: il compilatore si trova un errore che non si aspetta. Nella definizione del reference della classe Animale ho l’attributo e il metodo dormi e mangia. Nel reference di oggetto Gatto ho anche il metodo parla: mi aspetto di poter invocare il metodo parla ma il compilatore non se lo aspetta. Questo è lo stesso problema del cast: assegno qualcosa di troppo grande a qualcosa di troppo piccolo.
  2. Perché il compilatore ha permesso l’assegnamento di un reference di una sottoclasse a uno della superclasse? Dal punto di vista della rappresentazione ciò è corretto: la sottoclasse ha tutte le caratteristiche della superclasse. Ho dei problemi quando il reference della superclasse cerca di accedere a qualcosa che non appartiene alla sua rappresentazione. La soluzione è portare a livello della superclasse la definizione del metodo parla. Il metodo parla viene ridefinito all’interno della classe specifica.
  3. Perchè a video non viene stampato “dipende” ma il singolo verso? Per stampare il metodo della superclasse, dovrei nascondere quello delle sottoclassi. Se “cancellassi” il metodo su gatto, non avrei ridefinito il metodo parla a livello della sottoclasse e quindi manterrei la definizione a livello della superclasse. In questo caso è il run time support che definisce il metodo da invocare.

IL CAST

Permette di convertire un reference ad un tipo inferiore in gerarchia di parentela. Posso assegnare un reference ad un altro reference sempre purchè gli assegnamenti avvengano in un’opportuna gerarchia di ereditarietà. Sintassi: NomeClasseFiglia ref2 = (NomeClasseFiglia) ref1; Esempio: Animale a = new Gatto(“Miguel”); ... Gatto g = (Gatto)a; g.faiLeFusa(); Se so che l’oggetto creato è di classe gatto, posso assegnarlo ad un gatto g e invocare su g il metodo. Meglio se si effettua il controllo prima if (a instanceof Gatto) { Gatto g = (Gatto)a; g.faiLeFusa(); } Non posso scrivere a = new Gatto(); perchè il reference statico rimane animale: è necessario il cast esplicito anche se a tutti gli effetti ho l’oggetto gatto. Il metodo invocato è quello di gatto, non di animale. CONCETTI AVANZATI: CLASSI ASTRATTE Una classe può essere astratta: questo significa che non corrisponde a un concetto concreto del dominio applicativo, ma che è fatta apposta per funzionare da superclasse Esempio: non esiste un veicolo, esiste un'auto, una moto, ... Il metodo parla non viene invocato se ho animali veri. Devo definire il metodo parla, sennò ho un errore. Quindi sospetto che la classe animale sia utila a livello di documentazione per rappresentare correttamente il dominio ma che questa non debba essere effettivamente istanziabile. L’idea è quella di avere la possibilità all'interno di un dominio di rappresentare certe classi come indispensabili per la rappresentazione di un dominio ma all’atto pratico rendere impossibile la loro creazione effettiva. Una classe astratta è una classe che non può essere istanziata e ha senso solo in quanto ne vengono create delle sottoclassi. Viene definita solo per avere la funzione di superclasse: portano a fattor comune quelle che sono le caratteristiche comuni. Una classe deve essere dichiarata astratta se definisce almeno un metodo astratto. Una classe può essere dichiarata astratta anche se non definisce metodi astratti. Una delle caratteristiche principali è quella che definisce le caratteristiche di un gruppo di oggetti in maniera astratta. (Il metodo parla, al posto di essere definito in modo poco specifico “dipende” viene definito in termini astratti. La classe animale definirà il comportamento degli animali che sarà astratto nel senso che ogni animale mangia e dorme, ma poichè il comportamento di mangiare e dormire è concreto, lo rappresento in modo concreto. Siccome il modo in cui gli animali si esprimono non è unico, lo devo

In questo caso Felino non ridefinisce i metodi e quindi per definizione rimane astratta. L’EREDITARIETÀ MULTIPLA? Sulle classi astratte, posso estendere sempre e comunque una e una sola classe astratta (solo un extends). Ho un concetto analogo a quello della classe astratta: le interface. Queste vengono implementate e ci permettono di simulare l’ereditarietà multipla. L’interface realizza quasi un’associazione del tipo “is-a” al test instanceof restituisce true o false Le interface contengono solo intestazioni di metodi e al più definizioni di costanti Sintassi: public interface NomeInterface { // definizione nomi metodi } public class Classe implements NomeInterface { // definizione del corpo dei metodi ereditati } Si usa implements perché i metodi vanno completamente implementati, non ho nulla di concreto al loro interno. Posso implementare quante interface voglio. Esempio famoso: Runnable. CONFRONTARE LO STATO DI 2 OGGETTI L’operatore == applicato a 2 reference permette di testare se essi referenziano lo stesso oggetto. Ma se volessi sapere se due oggetti si trovano nello stesso stato, come faccio? Effettuo una comparazione fra i valori degli attributi. Modalità classica:

  • overridare il metodo equals della classe Object
  • intestazione: public boolean equals(NomeClasse obj) Esempio: public boolean equals(Object obj){ Animale a = (Animale) obj; boolean esito = false; if ((this.nome).equals(a.nome)){ il this mi da il livello della gerarchia dove mi trovo esito = true; } return esito; } La classe object ritorna true se gli hash code coincidono. Se trovo in un testo il fatto che una classe definisce equals, questo andrà ridefinito in modo opportuno. Se non lo ridefinissi, applicherei il metodo equals della classe object.