Assegnamento in Java e aliasing

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.


 

Aliasing durante le chiamate dei metodi

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].