En PHP on ne doit pas considérer les problématiques liées à des références. On initialise une variable et on ne doit pas gérer son placement dans la mémoire. En Java la gestion des références est plus complexe car elle prend en compte de différents types de références.
Qu'est-ce que c'est qu'une référence en Java ?
Pour bien comprendre la spécificité du Java, il faut commencer par définir une référence. Avec la construction de chaque objet, il est placé dans un endroit dans la mémoire. Cependant, le développeur ne peut pas y accéder directement. Par contre, il peut le faire via une sorte d'indicateurs qui sont les références placées dans des variables. Elles permettent donc de récupérer les objets directement depuis la mémoire.Les références ne sont pas représentées sous leurs formes réelles (il n'y aura pas une chaîne de caractères pour un String, ni un entier pour un Integer). A la place on retrouve des identifiants qui pointent vers un endroit précis de la mémoire. Ces identifiants sont uniques.
Si une variable appelée ne possède pas de référence correspondante (elle est null), une exception NullPointerException est captée.
Comment une référence est placée dans la mémoire ?
Chaque fois, avant d'allouer de l'espace dans la mémoire à un objet, la machine virtuelle du Java vérifie combien d'espace libre elle dispose. Si ce nombre est insuffisant, elle fait appel au Garbage Collector de Java pour faire un peu de nettoyage.
Regardons donc ce processus sur deux exemples : un type primitif (int) et un objet :
import java.util.HashMap; import java.util.Map; public class References { private Mapmap; private int i; private StringBuffer text; public static void main(String[] args) { References references = new References(); references.setNewInt(references.getInt()); System.out.println(references.getInt()); references.mapOperation(references.getMap(), "2", "B"); System.out.println("Map after operation : " + references.getMap()); references.stringOperation(references.getString(), "B"); System.out.println("String after operation : " + references.getString()); } public References() { map = new HashMap (); map.put("1", "A"); text = new StringBuffer("A"); i = 0; } public void setNewInt(int j) { j++; } public int getInt() { return i; } public void mapOperation(Map m, String key, String value) { m.put(key, value); System.out.println("Map reference is : " + m); } public Map getMap() { return map; } public void stringOperation(StringBuffer newText, String toAppend) { newText.append(toAppend); } public StringBuffer getString() { return text; } }
Voici le résultat :
0
Map reference is : {2=B, 1=A}
Map after operation : {2=B, 1=A}
String after operation : AB
On voit clairement que même en passant un paramètre Map
Par contre, ce n'est pas le cas pour int i où l'on a l'impression de récupérer uniquement une valeur et de modifier toute une autre variable j. Alors une copie de la valeur i et réalisée avant l'appel de la méthode qui la manipule.
Cependant, dans les deux cas il s'agit du mode de transmission appelé pass-by-value. Il repose sur le principe qu'on manipule des valeurs. Regardons pourquoi cela s'applique également au passage des références en paramètres. Pour le constater, on rajoute ceci dans la méthode mapOperation() :
m = new HashMap(); m.put("new-"+key, "new-"+value); System.out.println("Value of new map reference is : " + m);
Voici le résultat :
0
Map reference is : {2=B, 1=A}
Value of new map reference is : {new-2=new-B}
Map after operation : {2=B, 1=A}
String after operation : AB
On constate que les Maps sont deux objets différents. Autrement dit, ils possèdent deux adresses en mémoire distinctes. La déclaration m = new HashMap
Types de références
Java distingue 4 types de références :
1. strong (forte) : signifie les références qui ne sont collectées par le Garbage Collector que lorsque leurs objets deviennent null (cela veut dire que les objets ne sont plus utilisés par l'application et qu'ils peuvent être supprimés). Un bon exemple des références fortes sont des éléments des collections. Tant qu'une collection est référencée (différente de null), elle et ses composants sont considérées comme des références fortes.
2. soft (douce) : illustre les références qui peuvent être réclamées par le Garbage Collector. Cependant ce dernier ne va pas essayer de le faire. Par contre, si la machine virtuelle de Java décide de récupérer plus d'espace, elle va automatiquement nettoyer la mémoire des références douces. La libération pourra être lieu pour des objets des références douces qui n'ont pas des références fortes.
3. weak (faible) : il s'agit des références faibles qui n'essaient même pas de se protéger contre la collecte du Garbage Collector. La collecte pourra avoir lieu si aucune référence forte et douce n'existe pour l'objet en question.
4. phantom (phantôme) : les méthodes get() de cette référence renvoient toujours null. Elle est rajoutée dans la queue de références correspondante dès lors que l'objet est supprimé de la mémoire. Elle devient libérable quand l'objet y attaché n'a aucune référence forte, douc et faible. Une référence phantôme permet une action de notification avant la libération.
Cette référence peut être utile dans les opérations qui utilisent beaucoup de ressource. On peut imaginer la situation où l'on traite un grand fichier texte dans la mémoire. Un autre est en attente de traitement. Pour éviter des exceptions du type OutOfMemory, on peut commencer à manipuler l'autre fichier après la suppression du premier de la mémoire.
Regardons les exemples de ces références :
import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.util.Map; import java.util.WeakHashMap; import java.lang.ref.PhantomReference; import java.lang.ref.Reference; public class ReferencesTypes { public static void main(String[] args) { ReferenceQueue queue = new ReferenceQueue(); StringBuffer strong = new StringBuffer("Strong reference"); String soft = new String("Soft reference"); SoftReferencesoftRef = new SoftReference (soft, queue); String weak = new String("Weak reference"); String phantom = new String("Phantom reference"); PhantomReference phantomRef = new PhantomReference (phantom, queue); System.out.println("Weak reference"); Map weakMap = new WeakHashMap (); weakMap.put(weak, "weak value"); System.out.println("WeakHashMap with not null object " + weakMap.get(weak)); weak = null; System.out.println("WeakHashMap with null object " + weakMap.get(weak)); System.out.println("\n\n"); System.out.println("Phantom reference"); // put String reference to null phantom = null; System.gc(); try { while(true) { Reference phantRef = queue.remove(1000); System.out.println("Found reference " + phantRef + ". Looking for " + phantomRef); if(phantRef == phantomRef) { System.out.println(phantRef + " matches " + phantomRef); break; } } } catch(InterruptedException e) { System.out.println("An InterruptedException occured " + e.getMessage()); } } }
Voici le résultat :
Weak reference
WeakHashMap with not null object weak value
WeakHashMap with null object null
Phantom reference
Found reference java.lang.ref.PhantomReference@9304b1. Looking for java.lang.ref.PhantomReference@9304b1
java.lang.ref.PhantomReference@9304b1 matches java.lang.ref.PhantomReference@9304b1
En occurence on n'a pas pu voir la libération de la référence douce car elle est stocké jusqu'à la saturation de la mémoire.