PDA

View Full Version : [JAVA] Parola chiave " this "


gokan
05-11-2004, 15:03
Salve ragazzi, premetto che sto tentanto di imparare il java ed in generale la filosofia della programmazione ad oggetti. Sto studiando sul testo Thinking in Java di Bruce Eckel. Ho difficoltà a capire per bene l'uso della parola chiave this. Mi riferirò all'esempio del testo (perchè prima di spiegare l'uso di this, l'autore compie la premessa che sto per scrivervi), vediamo se mi aiutate voi a chiarire :)
L'autore inizia così in breve:

Se avete due oggetti dello stesso tipo a e b, potreste chiedervi come è possibile che si possa chiamare un metodo f() per entrambi gli oggetti.

Poi fa l'esempio

class Prova {
void f(int i) {/*..... */}
}

Prova a =new Prova(), b =new Prova();

a.f(1);
b.f(2);

L'autore prosegue poi:
Se c'è un solo metodo chiamato f(), come può quel metodo sapere se è stato chiamato per l'oggetto a o b?


Non capisco perchè l'autore si pone questo problema, non è un operazione lecita? La chiamata
a.f(1)
che problema può creare?

Mi potete spiegare questa cosa?
L'autore dice che effettivamente il compilatore è come se facesse una chiamata del tipo (non scrivibile in questo modo però):
Prova.f(a,1) //al posto di a.f(1)

Non capisco il significato di tutte questo lavoro, non ho capito ancora dove sta il problema :(

Scusate la lunghezza :) e grazie

anx721
05-11-2004, 15:45
Un metodo (non statico) lavora con l'istanza su cui è invocato. Ad esempio, supponi che la tua classe Prova contenga una stringa, e crei un primo oggetto a1 che contiene la stringa "uno", e un secondo oggetto a2 che contiene la stringa "due". Supponi infine che il metodo f() si limiti a stapare la stringa.

a1.f() provocherà la stampa di "uno"

mentre

a2.f() provocherà la stampa di "due"

proprio perche un metodo lavora con le variabili dell'oggetto su cui è invocato.

Se paragoni un metodo alle funzioni globali del C, puoi notare che in java invochi un metodo su un pggetto con la sintassi

oggetto.metodo();

mentre in C solitamente avrai una variabile oggetto che passi come argomento della funzione:

metodo(oggetto)


In realtà questo è anche ciò che avviene con la programmazione ad oggetti, perche quando in java invochi un metodo su un oggetto:

oggetto.metodo(....);

il compilatore è come se lo trasformasse in

metodo(oggetto, ....)

proprio perche il metodo deve sapere qualè l'oggetto su cui è stato invocato.

gokan
05-11-2004, 19:34
grazie Piè adesso è più chiaro il fatto della chiamata del metodo come intendeva Eckel. :)
Faccio un altro esempio:

public class Leaf {
int i=0;

Leaf increment() {
i++;
return this; /////////////////
}

void print() {
System.out.println("i= " + i);
}

public static void main(String args[]) {
Leaf x= new Leaf();

x.increment().increment().increment().print();
}
} //ritorna i=3


Il return this nel metodo Leaf serve ovviamente per potere ritornare un riferimento all'oggetto per il quale chiamo il costruttore.
Effettivamente mi stavo perdendo in un bicchiere d'acqua
:muro:
Adesso devo approfondire l'uso di this nella fase di chiamata di costruttori all'interno di altri costruttori. Se dovessi avere prob mi faccio risentire.
Da quanto ne ho capito this è molto utilizzato nel caso in cui si usano dei costruttori, giusto?

Ciao

anx721
05-11-2004, 20:45
this rappresenta il riferimento all'oggetto corrente, cioè su cui è stato invocato il metodo in esecuzione;

x.increment()

ritorna proprio x stesso perche increment è invocato su x e all'interno di increment si ritorna l'oggetto su cui è in esecuzione il metodo stesso.


In java per invocare un metodo su un oggetto utilizzi la sintassi

oggetto.metodo();

se non indichi l'oggetto, cioè se scrivi solo:

metodo();

allora è come se metodo() venisse invocato sull'oggetto corrente.

Esempio:


Leaf increment() {
i++;
print();
return this; /////////////////
}


in questa versione di increment puoi notare che ho aggiunto la chiamata a print() all'interno del metodo; ma su chi è invocato print? Non ho usato la normale notazione oggetto.print(). Ma la regola che ho detto prima è che se un metodo non è invocato su un oggetto è come se lo invoco sull'oggetto corrente, ovvero l'oggetto su cui è stato invocato increment(); quindi se scrivo:

x.incremet();

durante l'esecuzione di increment, quando si arriva alla chiamata di print(), print sarà chiamata su x, che è l'oggetto corrente.


this raprpesenta proprio l'oggetto corrente; cosi ad esempio, la seguente versione di increment è del tutto equivalente alla precedente:


Leaf increment() {
i++;
this.print();
return this; /////////////////
}



qui ho semplicemente reso escplicito il fatto che print lo sto esegunedo sull'oggetto corrente, anche se di norma non si usa questa sintassi.


Quando allora si usa this, se tanto lo si puo omettere? Alcuni casi sono i seguenti:

- ho un argomento di un metodo che ha lo stesso nome di un campo della classe; esempio:

supponi che la classe Leaf abbia un metodo

setta(int a);

che serve a dare al campo "i" della classe Leaf il valore di a:


public setta(int a){
i = a;
}

tutto ok. Che c'entra this? E' presto detto: immagina di cambiare il prototipo del metodo in:

public setta(int i)

come puoi notare in questo caso l'argomento del metodo ha lo stesso nome del campo "i" della classe; questo significa che il metodo diventa:


public setta(int i){
i = i;
}

in particolare hai l'istruzione

i = i;

in cui stai assegnando al primo "i" che raprpesenta il campo, il valore dell' "i" che è l'argomento. Queste almeno sarebbero le intenzioni; ma il compilatore non è in grado di conoscere le tue intenzioni e sapere quale "i" rappresenta il campo e quale invece l'argomento. Se l'argomento di un metodo ha lo stesso nome di un campo di classe, allora all'interno del metodo l'argometno OSCURA il campo di classe; all'interno del metodo setta(int i), con "i" puoi solo indicare l'argomento; come si fa allora ad indicare il campo "i" di Leaf e non l'argomento? La risposta l'avrai intuita:

this.i

quindi il modo corretto di scrivere il metodo setta è:


public setta(int i){
this.i = i;
}

cosi imposti il campo "i" con il valore "i" dato come argomento.


Questo è l'uso più frequente di this.


- un altro caso in cui this è usato e quando si deve invocare un metodo a cui passare l'oggetto corrente, e quidni lo si invoca come:

metodo(this);




Infine, tu stesso accennavi all'uso di this nei costruttori, in cui this viene invocato come se fosse un metodo. Una classe puo avere piu costruttori, l'importante è che i costruttori si distinguano per la lista degli argomenti; quindi all'interno di un costruttore, come prima istruzione, puoi invocare un costruttore con this:

this(<lista argomenti>);

in questo modo eviti di dover riscrivere tutte le istruzioni che erano contenute nel costruttore che stai invocando.


Esempio:

Hai una classe


class Saluto{
String tipoDiSaluto;

public Saluto(String tipoDiSaluto){
this.tipoDiSaluto = tipoDiSaluto;
}

public saluta(){
System.out.ptintln(tipoDiSaluto);
}
}

Questa classe riceve come argomento del costruttore una stringa che rappresenta un saluto; il metodo saluta() non fa altro che stampare questa stringa; cosi il seguente codice:

Saluto s = new Saluto("Ciao");
s.saluta();

farà stampare la stringa:

"Ciao"


Ora aggiungiamo un altro campo di tipo stringa che rappresenta il destinatario del saluto:


class Saluto{
String tipoDiSaluto;
String destinatario;

public Saluto(String tipoDiSaluto){
this.tipoDiSaluto = tipoDiSaluto;
}

public Saluto(String tipoDiSaluto, String destinatario){
this.tipoDiSaluto = tipoDiSaluto;
this.destinatario = destinatario;
}

public saluta(){
System.out.ptintln(tipoDiSaluto + "" + destinario);
}
}


oltre al campo destinatario è stato aggiunto un nuovo costruttore per settare anche il destinatario; inoltre il metodo saluta() stampa il tipo di saluto più uno spazio più il destinatario; il seguente codice:


Saluto s = new Saluto("Ciao", "Gokan");
s.saluta();

farà satmpare la stringa:

"Ciao Gokan"


Ora, un modo alternativo di scrivere il secondo costruttore è questo:


public Saluto(String tipoDiSaluto, String destinatario){
this(tipoDiSaluto);
this.destinatario = destinatario;
}


come vedi l'istruzione

this.tipoDiSaluto = tipoDiSaluto;

è stato sostituito da this(tipoDiSaluto); this(tipoDiSaluto) significa:

"Invoca il costruttore di questa calsse che ha un argomento di tipo stringa passandogli l'argoemento tipoDiSaluto".


In questo caso invocare il costruttore con this() o settare direttametne il campo non fa differenza; immagina pero una classe che ha un costruttore costituito da 100 righe di codice e un secondo costruttore che deve fare tutte le 100 righe di codice più altre ancora: in questo caso anzicche riscrivere le 100 righe di codice ti limiti a richiamare il costruttore opportuno con this() e la lista di argomenti che distingue quel costruttore dagli altri.

gokan
06-11-2004, 08:37
Perfetto :D
Adesso è tutto mooooolto più chiaro ;)
Ho trovato particolarmente utile la parte in cui mi spieghi l'effettivo utilizzo di questa parola chiave. Sebbene avessi intuito il suo uso non capivo bene quando era facolatativo usarla e quando diventa obbligatoria. Posso dire quindi che, in linea teorica si potrebbe anche non usare (nel primo caso, dando all'argomento del metodo/costruttore un nome diverso dal campo della classe), ma che risulta tanto utile nell'uso con i costruttori, evitando tanta riscrittura di codice ridondante!!!!

Tante grazie
:ave: :mano:

gokan
06-11-2004, 09:45
Scrivo l'esempio successivo di uso di this:

public class Flower {
int petalCount = 0;
String s = new String("null");

Flower(int petals) {
petalCount = petals;
System.out.println(
"Constructor w/ int arg only, petalCount= "
+ petalCount);
}
Flower(String ss) {
System.out.println(
"Constructor w/ String arg only, s=" + ss);
s = ss;
}
Flower(String s, int petals) {
this(petals);
//! this(s); // Can't call two!
this.s = s; // Another use of "this"
System.out.println("String & int args");
}
Flower() { //<-- chiamato per primo nel main
this("hi", 47);
System.out.println("default constructor (no args)");
}
void print() {
//! this(11); // Not inside non-constructor!
System.out.println(
"petalCount = " + petalCount + " s = "+ s);
}
public static void main(String[] args) {
Flower x = new Flower();
x.print();

/* OUTPUT
"Constructor w/ int arg only, petalCount= 47",
"String & int args",
"default constructor (no args)",
"petalCount = 47 s = hi"
*/
}
}

Vediamo se interpreto bene
1) Nel main, con la creazione dell'oggetto
Flower x = new Flower();
parte il costruttore senza argomenti in input

2)il costruttore Flower() chiama il costrutttore con due argomenti
("hi", 47)

3)il costruttore con due argomenti chiama quello con un solo
argomento intero this(petals)

4) a questo punto il costrut Flower(int petals) stampa
Constructor w/ int arg only, petalCount= 47

Adesso è come se si ritornasse all'indietro, quindi

5)Si ritorna al costr Flower(String s, int petals), il quale mette s nella
this.s (che è il campo della classe, che era s="NULL") e stampa
String & int args

6) adesso si ritorna a Flower(), il quale stampa
default constructor (no args)

7)infine l'invocazione di x.print fa stampare
petalCount = 47 s = hi

Diciamo che seguendo il codice dovrebbe andare così :)

Se invece il codice era:
public class Flower {
int petalCount = 0;
String s = new String("null");

Flower(int petals) {
petalCount = petals;
System.out.println(
"Constructor w/ int arg only, petalCount= "
+ petalCount);
}
Flower(String ss) {
System.out.println(
"Constructor w/ String arg only, s=" + ss);
s = ss;
}
Flower(String s, int petals) {
this(petals);
//! this(s); // Can't call two!
this.s = s; // Another use of "this"
System.out.println("String & int args");
}
Flower() { //<-- chiamato per primo nel main
this("hi", 47);
System.out.println("default constructor (no args)");
}
void print() {
//! this(11); // Not inside non-constructor!
System.out.println(
"petalCount = " + petalCount + " s = "+ s);
}
public static void main(String[] args) {
Flower x = new Flower("azz",99);
x.print();
/* OUTPUT
"Constructor w/ int arg only, petalCount= 99",
"String & int args",
"petalCount = 99 s = azz"
*/
}
}
Poichè viene chiamato
Flower x = new Flower("azz",99);

questa volta inizia il costruttore Flower(String s, int petals) e quindi l'ordine in cui vengono chiamati i rimanenti costruttori cambia, tanto è vero che il costrutt Flower() non viene mai chiamato, per cui
default constructor (no args) non viene mai stampato.

Ok, adesso ho capito
:sofico: