Types référence

A voir avant : les différences entre types primitifs et types référence

Copie

En java, les références sont complètement opaques, on ne peut pas comme en C les manipuler (en C, opérateurs &, * et ->)
Cela a une importance lorsqu'on duplique des variables :
int a = 42;
int b = a;
Pour les types primitifs, la JVM stocke 2 fois la valeur 42 dans 2 endroits différents de sa mémoire.
int a = 42;
int b = a;
a = 43;
System.out.println(a);      // affiche 43
System.out.println(b);      // affiche 42
Mais si on fait la même chose avec une classe :
class Point {
    public int x, y;
    public Point(int x, int y) { this.x = x; this.y = y; }
    public String toString(){ return "(" + this.x + "," + this.y + ")"; } // noter au passage la redéfinition d'une méthode de Object
}
Point a = new Point(1, 1);
Point b = a;
a.x = 2;
System.out.println(a); // affiche (2,1)
System.out.println(b); // affiche (2,1)

Comme a et b pointent vers la même chose, toute modification de l'un sera répercutée sur l'autre.

Clone

La classe Object fournit une méthode clone() dont toutes les classes héritent, mais elle ne fait que copier les références (shallow copy).
L'API java fournit des méthodes de recopie pour certaines classes.
Par exemple, pour dupliquer 2 tableaux, on dispose de System.arraycopy().
Mais si on a besoin de recopier des objets de nos propres classes, on doit l'implémenter.

Comparaison

Lorsqu'on utilise l'opérateur de comparaison == pour les types primitifs, java compare leurs valeurs (est évalué à true s'ils ont exactement les mêmes bits), donc on a le résultat attendu.
Mais si on compare 2 types référence, java compare leurs références, pas leurs valeurs.
String s = "hello";
String letter = "o";

String t = "hell" + letter;

System.out.println("avec == \t" + (s == t ? "equal" : "not equal"));

System.out.println("avec equals \t" + (s.equals(t) ? "equal" : "not equal"));
à l'exécution :
avec ==         not equal
avec equals     equal
Comme on pouvait s'y attendre, le test avec == indique que s et t ne sont pas égales.
Pour faire le test correctement, on a utilisé la méthode equals(). Cette méthode fait partie de la classe java.lang.Object, donc toutes les classes en héritent, mais dans le cas général, elle n'est pas plus utile, car l'implémentation par défaut utilise simplement == pour faire la comparaison.
Le test a marché uniquement parceque la classe String réimplémente equals().

Pour comparer les valeurs des tableaux, il faut par exemple utiliser java.util.Arrays.equals() ou java.util.Arrays.deepEquals() pour les tableaux à plusieurs dimensions.

Passage en paramètre

Le même phénomène se produit lorsqu'on passe une variable en paramètre :
void changePrimitive(int x){
    x--;
}
int a = 42;
System.out.println(a);      // affiche 42
changePrimitive(a);
System.out.println(a);      // affiche 42
Mais avec un type référence :
void changeReference(Point p) {
    p.x--;
}
Point a = new Point(1, 1);
System.out.println(a); // affiche (1,1)
changeReference(a);
System.out.println(a); // affiche (0,1)

Autoboxing

Il peut être utile de traduire des types primitifs en objet.
Pour ça, java fournit des wrapper classes pour chaque type primitif : Boolean, Byte, Short, Character, Integer, Long, Float, et Double.
Passer du type primitif à sa représentation objet s'appelle le boxing, l'inverse unboxing.
On peut le faire "à la main" :
myInt = new Integer(-1);            // deprecated, faire à la place : Integer.valueOf(-1)
int i = myInt.intValue();
Mais pas très pratique, java le fait automatiquement (autoboxing) :
Integer i = 0;      // int literal 0 boxed to an Integer object
Number n = 0.0f;    // float literal boxed to Float and widened to Number
Integer i = 1;      // this is a boxing conversion
int j = i;          // i is unboxed here
i++;                // i is unboxed, incremented, and then boxed up again
Integer k = i+2;    // i is unboxed and the sum is boxed up again
i = null;
j = i;