L’assegnamento è eseguito attraverso
l’operatore =. Esso significa “prendi il
valore sul lato destro (spesso chiamato rvalue) e copialo nel lato sinistro
(spesso chiamato lvalue). Un rvalue è una
costante, una variabile o un’espressione che può produrre un valore, ma un
lvalue deve essere una variabile in altre parole, deve esistere uno spazio
fisico dove memorizzare il valore. Non è possibile assegnare qualcosa ad un
valore costante (non può essere un lvalue).
L’assegnamento di primitive è una cosa lineare
e semplice. Siccome le primitive contengono il valore attuale e non il riferimento
ad un oggetto, quando si assegnano delle primitive viene copiato il contenuto
da una parte all’altra. Ad esempio, con A = B il contenuto di B
è copiato in A. Se poi si va a
modificare A, B naturalmente
non subirà modifiche. Come programmatore, questo è quello che ci si
aspetterebbe in situazioni simili.
Quando si assegnano oggetti, tuttavia, le
cose cambiano. Quando si manipola un oggetto, ciò che si manipola è un
riferimento, così quando si vuole assegnare un oggetto ad un altro si stà
copiando un riferimento da un luogo ad un altro. Ciò significa che se C e D sono oggetti, l’assegnamento C
= D implica che sia C che D puntano all’oggetto che
originariamente solo D puntava.
Il seguente esempio dimostrerà ciò.
// L’assegnamento di oggetti è un pò ingannevole
class Number
{ int i;
}
public class Assignment
{ public static void main(String[] args)
{ Number n1 = new Number();
Number n2 = new Number();
n1.i = 9;
n2.i = 47;
System.out.println("1: n1.i: " + n1.i + ", n2.i: " + n2.i);
n1 = n2;
System.out.println("2: n1.i: " + n1.i + ", n2.i: " + n2.i);
n1.i = 27;
System.out.println("3: n1.i: " + n1.i + ", n2.i: " + n2.i);
}
}
La classe Number è semplice, e vengono create
due istanze di essa (n1 e n2) sono create nel main( ).
Al valore i in ogni Number
è dato un differente valore, e poi n2 è assegnato a n1, e n1
è cambiato. In molti linguaggi di programmazione ci si potrebbe aspettare che n1
e n2 siano indipendenti,
ma siccome abbiamo assegnato un riferimento, questo è l’output che vedremo:
1: n1.i: 9, n2.i: 47
2: n1.i: 47, n2.i: 47
3: n1.i: 27, n2.i: 27
Sembra che cambiando l’oggetto n1 si cambi
anche l’oggetto n2. Ciò perché sia n1 che n2 contengono lo
stesso riferimento che punta allo stesso oggetto. Il riferimento originale che
era in n1 che puntava all’oggetto che conteneva il valore 9 è stato
sovrascritto durante l’assegnamento ed è effettivamente perso; il suo oggetto
sarà ripulito dal garbage collector.
Questo fenomeno è spesso detto aliasing ed
è un modo fondamentale con cui Java lavora con gli oggetti. E se non si volesse
che l’aliasing avvenga in questo caso?
Basta usare quest’assegnamento invece di quello
precedente :
n1.i = n2.i;
In tal modo si preservano i due oggetti, ma si va
contro i buoni principi della programmazione object-oriented perché si
manipolano direttamente i campi degli oggetti.
L’aliasing può avvenire anche quando si passa un
oggetto in un metodo:
// Gli oggetti passati ai metodi potrebbero non essere quelli usati.
class Letter
{ char c;
}
public class PassObject
{ static void f(Letter y)
{ y.c = 'z';
}
public static void main(String[] args)
{ Letter x = new Letter();
x.c = 'a';
System.out.println("1: x.c: " + x.c);
f(x);
System.out.println("2: x.c: " + x.c);
}
}
In molti linguaggi di programmazione, il metodo f( )
potrebbe sembrare che faccia la copia del suo argomento Letter y
all’interno dello scope del metodo. Ma ancora una volta è passato un
riferimento. E quindi la linea
y.c = 'z';
cambia l’oggetto aldifuori di f( ).
L’output mostrato sarà :
1: x.c: a
2: x.c: z
L’aliasing e la sua soluzione è un argomento complesso
che può portare a molte trappole, per approfondimenti vedere l’appendice A di [34].