PDA

View Full Version : [JAVA] Domanda sul polimorfismo HELPME :-(


D4rkAng3l
25-01-2009, 11:42
Ciao,
a breve ho l'esame di linguaggi di programmazione...ho un piccolo dubbio circa una questione teorica riguardante come funziona il polimorfismo in Java.

Praticamente da quello che ho capito il polimorfismo più genuino possibile è il cosidetto polimorfismo universale in cui una variabile (o una routine) può assumere una qualsiasi possibile forma.

Tuttavia Java per essere type safe adotta una restrizione a tale polimorfismo che viene detta POLIMORFISMO PER INCLUSIONE ed è basato sul meccanismo dell'ereditarietà mediante il quale è possibile definire sottotipi definendo delle sottoclassi.
In tale tipo di polimorfismo, dati un tipo T ed un suo sottotipo S, valgono le seguenti regole:

1) Gli oggetti di tipo S sono particolari oggetti di tipo T che devono soddisfare un certo numero di vincoli aggiuntivi (ad esempio ho una classe padre Rettangolo ed una classe figlia Quadrato...in quest'ultima c'è il vincolo aggiuntivo che base ed altezza devono essere uguali).

2) Tutti gli operatori del supertipo T sono anche operatori definiti per il sottotipo S, viceversa possono esistere operatori definiti per il sottotipo S che non sono definiti per il supertipo T (per esempio Quadrato potrebbe saper fare delle cose in più di Rettangolo come calcolare l'area del cerchio inscritto)

3) Taluni operatori definiti per il supertipo T hanno un comportamento diverso nel sottotipo e li devo andare a ridefinire nel sottotipo S mantenendo la stessa signature (è questo l'overriding degli operatori?).

Tramite il polimorfismo per inclusione posso definire una variabile di un certo tipo T e di ASSEGNARLE SOLTANTO IL RIFERIMENTO AD UN OGGETTO IL CUI TIPO è T O UN QUALUNQUE SOTTOTIPO DI T.

Possiamo ad esempio definire un metodo la cui signature sarà: nomeMetodo(T parametro) e poi, in qualche altro metodo, invovarlo passandogli come parametro attuale il riferimento ad un oggetto di tipo S dove S è sottotipo di T.
Da qua deriva che il compilatore non è in grado di conoscere i tipi effettivi dei parametri attuali che sono potenzialmente infiniti (perchè potrei derivare infiniti tipi di dato da un tipo T).

Nonostante il compilatore non sia in grado di conoscere i tipi effettivi degli oggetti è comunque STRONGLY TYPED e ciò è reso possibile proprio dalla restrizione imposta sull'uso delle variabili polimorfiche: "Una volta dichiarata una variabile di un tipo T, all'oggetto referenziato da quella variabile possono essere inviati solo messaggi il cui contenuto è un operatore del tip T e non di qualche suo sottotipo".

Per esempio nel main potrei avere qualcosa del genere:


Rettangolo R = new Quadrato(2); // Definisco R come quadrato ma lo creo come Rettangolo

double d = R.raggioCerchioInscritto();


Ed infatti la seconda istruzione darà errore di compilazione perchè il type checking avviene staticamente...

Ok...già quà ho il primo dubbio: praticamente che significa esattamete sta cosa? Semplicemente che ad un metodo definito in Rettangolo posso passare come parametro anche un elemento di tipo Quadrato ma che se dichiaro una variabile di tipo Rettangolo e la creo come Quadrato posso usare solo i metodi definiti in Rettangolo?
(non è molto limitante questa cosa? non mi torna...)
Tra l'altro se io dichiaro una variabile del supertipo Rettangolo e la creo come Quadrato non gli stò passando un messaggio che contiene il costruttore di Quadrato che è un metodo di Quadrato?!?!
Se dichiaro sempre R come Rettangolo ma poi lo costruisco come Quadrato e nella classe figlio ho ridefinito dei metodi presenti nella classe padre...vengono usati i metodi della classe padre? :eek:

Poi c'è la seconda cosa che non mi torna per niente :cry:

Mi dice che il polimorfismo in JAVA è particolarmente utile in quanto la JVM esegue il binding dinamico (a runtime) tra invocazioni dei metodi e corpo dei metodi invocati...cioè se in una classe che contiene il main() avessi qualcosa come:


Rettangolo R; // Dichiaro R come supertipo Rettangolo

if(Math.random()>0) // Se esce un numero positivo
R = new Rettangolo; // allora lo crea come Rettangolo
else
R = new Quadrato; // altrimenti lo crea come il sottotipo Quadrato

R.allarga(4);


La proff dice che si nota che il TIPO EFFETTIVO del rettangolo R è noto solo a runtime in quanto dipende dalla generazione del numero casuale e dice che se il binding tra l'invocazione del metodo allarga ed il codice avenisse staticamente allora l'istruzione R.allarga(4) causerebbe l'invocazione del metodo allarga() definito nella classe Rettangolo e quindi se il tipo effettivo referenziato da R fosse Quadrato si invocherebbe il metodo sbagliato.

Invece in JAVA è la JVM a fare questo binding dinamicamente ed invoca il metodo della classe Quadrato e lo fà in questo modo: controlla il tipo effettivo dell'oggetto referenziato da R ed invoca il metodo allarga() definito per il tipo effettivo che è Quadrato...

Ma questo non và palesemente in contrasto con quanto detto prima?!?! :eek:
Mi riferisco alla restrizione imposta sull'uso delle variabili polimorfiche: "Una volta dichiarata una variabile di un tipo T, all'oggetto referenziato da quella variabile possono essere inviati solo messaggi il cui contenuto è un operatore del tip T e non di qualche suo sottotipo".

Come funziona?!?! Mi pare che le due cose siano in conflitto e si contraddicano palesemente...

Vi prego aiutatemi sono abbastanza disperato :cry:

Grazie
Andrea

gugoXX
25-01-2009, 12:57
Quanto segue vale sia per Java, che per C#, che per C++, che per praticamente tutti i linguaggi di programmazione orientati agli oggetti che conosco

Ciao,

1) Gli oggetti di tipo S sono particolari oggetti di tipo T che devono soddisfare un certo numero di vincoli aggiuntivi (ad esempio ho una classe padre Rettangolo ed una classe figlia Quadrato...in quest'ultima c'è il vincolo aggiuntivo che base ed altezza devono essere uguali).

Diciamo che i sottotipi possono definire alcune caratteristiche aggiuntive


2) Tutti gli operatori del supertipo T sono anche operatori definiti per il sottotipo S, viceversa possono esistere operatori definiti per il sottotipo S che non sono definiti per il supertipo T (per esempio Quadrato potrebbe saper fare delle cose in più di Rettangolo come calcolare l'area del cerchio inscritto)

3) Taluni operatori definiti per il supertipo T hanno un comportamento diverso nel sottotipo e li devo andare a ridefinire nel sottotipo S mantenendo la stessa signature (è questo l'overriding degli operatori?).

Questo e' l'override in genere, non per forza override degli operatori che e' un'altra cosa, parente e simile, ma diversa.


Tramite il polimorfismo per inclusione posso definire una variabile di un certo tipo T e di ASSEGNARLE SOLTANTO IL RIFERIMENTO AD UN OGGETTO IL CUI TIPO è T O UN QUALUNQUE SOTTOTIPO DI T.

Possiamo ad esempio definire un metodo la cui signature sarà: nomeMetodo(T parametro) e poi, in qualche altro metodo, invovarlo passandogli come parametro attuale il riferimento ad un oggetto di tipo S dove S è sottotipo di T.
Da qua deriva che il compilatore non è in grado di conoscere i tipi effettivi dei parametri attuali che sono potenzialmente infiniti (perchè potrei derivare infiniti tipi di dato da un tipo T).

Al compilatore serve solo sapere che verra' passata una variabile di un tipo che avra' almeno tutti i metodi e i campi del tipo T.



Rettangolo R = new Quadrato(2); // Definisco R come quadrato ma lo creo come Rettangolo

double d = R.raggioCerchioInscritto();


Ed infatti la seconda istruzione darà errore di compilazione perchè il type checking avviene staticamente...

Ok...già quà ho il primo dubbio: praticamente che significa esattamete sta cosa? Semplicemente che ad un metodo definito in Rettangolo posso passare come parametro anche un elemento di tipo Quadrato ma che se dichiaro una variabile di tipo Rettangolo e la creo come Quadrato posso usare solo i metodi definiti in Rettangolo?
(non è molto limitante questa cosa? non mi torna...)

Posso usare tutti e solo i metodi definiti in Rettangolo. Non e' limitante


Tra l'altro se io dichiaro una variabile del supertipo Rettangolo e la creo come Quadrato non gli stò passando un messaggio che contiene il costruttore di Quadrato che è un metodo di Quadrato?!?!
Stai semplicemente creando un oggetto che e' un quadrato, ma che per come e' definito e' anche un rettangolo.
E se scriverai qul codice la sopra, lo starai usando come rettangolo, e in futuro ne potrai richiamare i soli metodi di rettangolo, e i soli campi di rettangolo.
Ovviamente scritta cosi' non serve a nulla. Nessuno crea un quadrato e poi lo usa come rettangolo fine a se stesso.

Potresti pero' avere un metodo che accetta 3 rettangoli generici, per calcolare una superficie totale.

double Superficie(Rettangolo[] rect)
{
double ret=0;
for(int t=0; t<rect.Length; t++)
{
ret+=rect[t].GetArea();
}
return ret;
}

A questo metodo potrai passare un array, i cui singoli elementi potranno essere indifferentemente Quadrati oppure Rettangoli, e il risultato sara' comunque corretto.
Senza il paradigma ad oggetti avresti dovuto creare un metodo che accetta solo quadrati, uno che accetta solo rettangoli,
acresti dovuto dividere la tua collezione tra quadrati e rettangoli, avresti dovuto invocare i due metodi separatamente e avresti dovuto alla fine sommare i due risultati.

Se dichiaro sempre R come Rettangolo ma poi lo costruisco come Quadrato e nella classe figlio ho ridefinito dei metodi presenti nella classe padre...vengono usati i metodi della classe padre? :eek:

Ovviamente no. Se un metodo e' ridefinito nella classe figlio, a runtime verra' usato il metodo ridefinito, e non quello della classe padre.


Poi c'è la seconda cosa che non mi torna per niente :cry:

Mi dice che il polimorfismo in JAVA è particolarmente utile in quanto la JVM esegue il binding dinamico (a runtime) tra invocazioni dei metodi e corpo dei metodi invocati...cioè se in una classe che contiene il main() avessi qualcosa come:


Rettangolo R; // Dichiaro R come supertipo Rettangolo

if(Math.random()>0) // Se esce un numero positivo
R = new Rettangolo; // allora lo crea come Rettangolo
else
R = new Quadrato; // altrimenti lo crea come il sottotipo Quadrato

R.allarga(4);


La proff dice che si nota che il TIPO EFFETTIVO del rettangolo R è noto solo a runtime in quanto dipende dalla generazione del numero casuale e dice che se il binding tra l'invocazione del metodo allarga ed il codice avenisse staticamente allora l'istruzione R.allarga(4) causerebbe l'invocazione del metodo allarga() definito nella classe Rettangolo e quindi se il tipo effettivo referenziato da R fosse Quadrato si invocherebbe il metodo sbagliato.

Invece in JAVA è la JVM a fare questo binding dinamicamente ed invoca il metodo della classe Quadrato e lo fà in questo modo: controlla il tipo effettivo dell'oggetto referenziato da R ed invoca il metodo allarga() definito per il tipo effettivo che è Quadrato...

Ma questo non và palesemente in contrasto con quanto detto prima?!?! :eek:

Esatto. Va in contrasto perche' era errato. Viene usato il codice del metodo ridefinito nella classe Quadrato.


Mi riferisco alla restrizione imposta sull'uso delle variabili polimorfiche: "Una volta dichiarata una variabile di un tipo T, all'oggetto referenziato da quella variabile possono essere inviati solo messaggi il cui contenuto è un operatore del tip T e non di qualche suo sottotipo".

Come funziona?!?! Mi pare che le due cose siano in conflitto e si contraddicano palesemente...

Potrai richiamare solo i metodi disponibili sulla classe rettangolo, ma il codice del metodo utilizzato di volta in volta sara' quello della classe figlia, quando ridefinito. Tale operazione si chiama dynamic binding ed e' proprio questo cio' che viene fatto a runtime dalla JVM. Sapendo lei il vero tipo della singola variabile ne andra' a richiamare i metodi corretti. Che sicuramente esisteranno, cosa assicurata dalla fase di compilazione.
Nell'esempio di prima, la GetArea() richiamata sara' quella di un rettangolo, oppure proprio quella di un quadrato, a seconda che il singolo elemento dell'array sia un rettangolo o proprio un quadrato.

D4rkAng3l
25-01-2009, 13:23
Ok...sei stato molto illuminante...e credo di aver capito...ma...

Se io ho un supertipo definito dalla classe Rettangolo ed un sottotipo di tale classe definito dalla classe Quadrato (c'è una relazione "is a", nel senso che un quadrato è un rettangolo e deve soddisfare un vincolo in più: che i lati siano uguali). Dentro la classe Quadrato avrò alcuni metodi di Rettangolo riscritti ed alcuni metodi in più che esprimono cose che un quadrato sa fare e che un rettangolo non sa fare come il calcolo del raggio del cerchio iscritto ad un quadrato.

Inizialmente mi si dice che in Java se nel main() avessi qualcosa del tipo:


Rettangolo R = new Quadrato();
double d = R.raggioCerchioInscritto();


mi verrà dato un errore in fase di compilazione poichè Java adotta una restrizione all'uso delle variabili polimorfiche che dice che una volta che dichiaro una variabile di tipo T (nel nostro esempio di tipo Rettangolo), agli oggetti referenziati da quella variabile posso inviare solomessaggi che contengono metodi di tipo T e non dei suoi possibili sottotipi.

Quì mi viene detto chiaramente che l'invocazione del metoro raggioCerchioInscritto causerà un errore in fase di compilazione perchè tale metodo è definito solo nel sottotipo Quadrato ma la variabile, benchè sia stata costruita come Quadrato, è stata dichiarata di tipo Rettangolo.

Poi mi viene detto che se nel main() avessi qualcosa del tipo:


Rettangolo R; // Dichiaro R come supertipo Rettangolo

if(Math.random()>0) // Se esce un numero positivo
R = new Rettangolo; // allora lo crea come Rettangolo
else
R = new Quadrato; // altrimenti lo crea come il sottotipo Quadrato

R.allarga(4);


dove allarga è un metodo che esiste sia in Rettangolo, sia ridefinito in maniera appropriata in Quadrato...

Mi si dice che se l'oggetto viene costruito a runtime come Rettangolo viene invocata la versione del metodo definita nella classe Rettangolo, mentre se l'oggetto viene costruito a runtime come Quadrato viene invocata la versione del metodo definita nella classe Quadrato....questo grazie alla JVM che esegue dinamicamente il binding tra invocazione del metodo e corpo del metodo invocante....

Tutto molto bello...ma mi pare in chiara contraddizione con quanto detto nell'esempio precedente in quanto anche in questo secondo caso l'oggetto viene definito come Rettangolo (prima di essere costruito a runtime o come Rettangolo o come Quadrato)...

Non dovrebbe valere quanto detto prima?!?! che se definisco la variabile R come Rettangolo anche se ci metto il riferimento ad un oggetto costruito come Quadrato (con tipo effettivo Quadrato) posso usare solo i metodi definiti in rettangolo per la famosa regola restrittiva sulle variabili polimorfe...

O l'una o l'altra cosa sono sbagliate...perchè quà dichiaro R come Rettangolo e se poi lo costruisco come Quadrato posso usare la versione del metodo dilata() definita nella classe Quadrato e prima che dichiaravo sempre R come Rettangolo e poi lo costruivo come Quadrato mi dava errore in fase di compilazione se provavo ad eseguire il metodo raggioCerchioInscritto() definito solo nella classe Quadrato?!?!?

E' questo che non mi torna...cioè nel secondo esempio, se l'oggetto viene costruito come Quadrato ed il suo riferimento assegnato ad R che è dichiaratadi tipo Rettangolo, viene usato tranquillamente il metodo allarga() ridefinito nella classe Quadrato poichè tale metodo è già nativamente presente nella classe Rettangolo mentre nel primo caso non posso usare il metodo raggioCerchioInscritto poichè questo è definito SOLO nella classe Quadrato?
ho capito bene? Se è effettivamente così per quale motivo pratico i progettisti di Java hanno deciso di non farmi usare un metodo definito solo nel tipo effettivo se la variabile è dichiarata con un altro tipo apparente?

Grazie
Andrea

gugoXX
25-01-2009, 13:32
Il dynamic binding viene fatto sulla base del vero tipo di un oggetto, e non sulla base del tipo della dichiarazione.
Quindi quando il vero tipo e' un quadrato, verra' richiamato il metodo Allarga() "eventualmente" ridefinito dentro la classe quadrato.
Non e' in contraddizione. Puoi richiamare tutti i metodi che vuoi, purche' siano presenti nella classe Rettangolo. Ma il codice eseguito sara' quello di volta in volta corretto, a seconda del vero tipo dell'oggetto.

Non e' in contraddizione, tu stesso avevi detto prima che il metodo Allarga() e' presente sia in classe Rettangolo che in classe Quadrato.
Se fosse presente solo in Quadrato, allora avresti un errore di compilazione.

D4rkAng3l
25-01-2009, 15:08
Il dynamic binding viene fatto sulla base del vero tipo di un oggetto, e non sulla base del tipo della dichiarazione.
Quindi quando il vero tipo e' un quadrato, verra' richiamato il metodo Allarga() "eventualmente" ridefinito dentro la classe quadrato.
Non e' in contraddizione. Puoi richiamare tutti i metodi che vuoi, purche' siano presenti nella classe Rettangolo. Ma il codice eseguito sara' quello di volta in volta corretto, a seconda del vero tipo dell'oggetto.

Non e' in contraddizione, tu stesso avevi detto prima che il metodo Allarga() e' presente sia in classe Rettangolo che in classe Quadrato.
Se fosse presente solo in Quadrato, allora avresti un errore di compilazione.

ah ecco perfetto ora ho capito...ma come mai è stat fatta questa scelta? per quale motivo pratico se dichiaro R come Rettangolo e lo costruisco come Quadrato Java mi dà errore in fase di compilazione se chiamo un metodo che è in Quadrato ma non in Rettangolo?
Si verrebbe a creare qualche problema che non renderebbe type safe il linguaggio? why?

gugoXX
25-01-2009, 15:41
ah ecco perfetto ora ho capito...ma come mai è stat fatta questa scelta? per quale motivo pratico se dichiaro R come Rettangolo e lo costruisco come Quadrato Java mi dà errore in fase di compilazione se chiamo un metodo che è in Quadrato ma non in Rettangolo?
Si verrebbe a creare qualche problema che non renderebbe type safe il linguaggio? why?

Potrebbe accadere che tu voglia lanciare un metodo di un oggetto che non ce l'ha.
In questo modo invece sai che il metodo esiste di sicuro.
D'altronde un modo per fare quello che vuoi c'e'. Basta dichiarare l'oggetto come quadrato e non come rettangolo. Perche' mai vorresti usare rettangolo in questo caso?

D4rkAng3l
25-01-2009, 15:52
Potrebbe accadere che tu voglia lanciare un metodo di un oggetto che non ce l'ha.
In questo modo invece sai che il metodo esiste di sicuro.
D'altronde un modo per fare quello che vuoi c'e'. Basta dichiarare l'oggetto come quadrato e non come rettangolo. Perche' mai vorresti usare rettangolo in questo caso?

Ok ora è tutto chiaro...anche questo argomento è andato...passo al prossimo :D