Programmation Java/Instanciation et cycle de vie

Un livre de Wikilivres.
Sauter à la navigation Sauter à la recherche


L'instruction new[modifier | modifier le wikicode]

L'instruction new permet d'instancier une classe en utilisant l'un des constructeurs de la classe.

Par exemple pour créer un objet de type MaClasse, on écrit :

MaClasse cl = new MaClasse("hello");

Cette instruction sert également à allouer les tableaux en spécifiant le nombre d'éléments entre crochets après leur type.

int[] entiers = new int[15]; // Alloue un tableau de 15 entiers initialisés à 0.
MaClasse[] mes_objets = new MaClasse[20]; // Alloue un tableau de 20 références de type MaClasse initialisées à null.

Les constructeurs[modifier | modifier le wikicode]

Un constructeur est une méthode particulière de la classe appelée lors de la création d'une instance de la classe. Son rôle est d'initialiser les membres de l'objet juste créé. Un constructeur a le même nom que sa classe et n'a pas de valeur de retour.

Dans l'exemple suivant la classe MaClasse dispose de deux constructeurs, l'un ne prenant aucun paramètre et l'autre prenant un paramètre de type String :

public class MaClasse
{
	// Attributs
	private String name;

	// Constructeurs
	public MaClasse()
	{
		name = "defaut";
	}

	public MaClasse(String s)
	{
		name = s;
	}
}

Toute classe possède au moins un constructeur. Cependant, il n'est pas obligatoire de déclarer un constructeur pour une classe. En effet, si aucun constructeur n'est déclaré dans une classe, un constructeur sans paramètre est ajouté de manière implicite. Celui-ci ne fait rien, en apparence.

Un constructeur d'objet en appelle toujours un autre :

  • Soit explicitement un autre constructeur de la même classe en utilisant this,
  • Soit explicitement un constructeur de la classe mère en utilisant super,
  • Soit implicitement le constructeur sans arguments de la classe mère.

Ces deux derniers points ne sont pas applicables à la classe java.lang.Object racine de toutes les autres classes.

Le code ci-dessous est strictement équivalent à l'exemple précédent, avec les appels implicites mis explicitement

public class MaClasse extends Object /* Implicite */
{
	// Attributs
	private String name;

	// Constructeurs
	public MaClasse()
	{
		super(); /* Implicite */
		name = "defaut";
	}

	public MaClasse(String s)
	{
		super(); /* Implicite */
		name = s;
	}
}

Quand une classe a plusieurs constructeurs qui définissent des valeurs par défaut, il vaut mieux que chaque constructeur appelle un autre constructeur de la classe. Cela permet de faciliter la maintenance en centralisant le comportement de construction et la définition des valeurs par défaut en un seul endroit du code.

public class MaClasse
{
	// Attributs
	private String name;
	private String description;

	// Constructeurs
	public MaClasse()
	{
		this("Pas de nom");
	}

	public MaClasse(String name)
	{
		this(name, "Pas de description");
	}

	public MaClasse(String name, String description)
	{
		// super(); /* Implicite */
		this.name = name;
		this.description = description;
	}
}

Nettoyage[modifier | modifier le wikicode]

Ramasse-miettes (Garbage Collector)[modifier | modifier le wikicode]

Le ramasse-miettes garde un compteur du nombre de références pour chaque objet. Dès qu'un objet n'est plus référencé, celui-ci est marqué. Lorsque le ramasse-miettes s'exécute (en général quand l'application ne fait rien), les objets marqués sont libérés.

Son exécution se produit toujours à un moment qui ne peut être déterminé à l'avance. Il s'exécute lors des évènements suivants :

  • périodiquement, si le processeur n'est pas occupé,
  • quand la quantité de mémoire restante est insuffisante pour allouer un nouveau bloc de mémoire.

Il est donc recommandé de libérer toute référence (affecter null) à des objets encombrants (tableaux de grande taille, collection d'objets, ...) dès que possible, ou au plus tard juste avant l'allocation d'une grande quantité de mémoire.

Exemple : Pour le code suivant, il faut 49 152 octets disponibles, le ramasse-miettes ne pouvant rien libérer durant l'allocation du deuxième tableau.

byte[] buffer = new byte[16384];
// -> 1. allocation de 16384 octets
//    2. affecter la référence à la variable buffer
// ...
buffer = new byte[32768];
// -> 1. allocation de 32768 octets
//    2. affecter la référence à la variable buffer

Une fois le code corrigé, il ne faut plus que 32 768 octets disponibles, le ramasse-miettes pouvant libérer le premier tableau avant d'allouer le deuxième.

byte[] buffer = new byte[16384];
// -> 1. allocation de 16384 octets
//    2. affecter la référence à la variable buffer
// ...
buffer = null; // Le ramasse-miettes peut libérer les 16384 octets du tableau si besoin.
buffer = new byte[32768];
// -> 1. allocation de 32768 octets
//    2. affecter la référence à la variable buffer

Mettre à null les références devenues inutiles au plus tôt permet donc de réduire la quantité de mémoire libre nécessaire à l'allocation de tableaux ou d'objets.

Finalisation[modifier | modifier le wikicode]

Lors de la libération des objets par le ramasse-miettes, celui-ci appelle la méthode finalize() afin que l'objet libère les ressources qu'il utilise.

Cette méthode peut être redéfinie afin de libérer des ressources système non Java. Dans ce cas, la méthode doit se terminer en appelant la méthode de la classe parente :

super.finalize();

Comme l'exécution du ramasse-miettes se produit toujours à un moment qui ne peut être déterminé à l'avance, l'appel à cette méthode par le ramasse-miettes n'est pas garanti. C'est pourquoi cette méthode est rarement implémentée afin d'éviter des effets de bords.

Représentation des objets dans la mémoire[modifier | modifier le wikicode]

L'instruction new alloue la mémoire sur le tas pour les objets et les tableaux.

La création d'un objet de classe C qui hérite de la classe B, elle-même héritant de la classe A, se déroule à peu près de la manière suivante :

  • Pour chacune des classes de la hiérarchie en partant de la classe racine (ordre A, B, C pour l'exemple), si cela n'a pas déjà été fait auparavant :
    • Charger la classe (Class, code des méthodes, ...), en lui allouant également l'espace nécessaire pour ses variables membres statiques.
    • Initialiser les variables membres statiques de la classe en appelant la méthode spéciale <clinit> qui contient la concaténation des assignations des membres statiques et des blocs de code statiques.
  • Allocation de l'espace mémoire pour les membres d'instance de la classe C incluant les membres déclarés ainsi que ceux hérités des classes B et A.
  • Appel au constructeur (méthode spéciale <init>) de la classe C correspondant aux arguments fournis en lui passant la référence à la zone mémoire allouée dans this :
    • Sa première instruction est un appel explicite ou implicite à un constructeur de la classe B (lui-même appelant un constructeur de la classe A).
    • Les instructions suivantes sont la concaténation des assignations des membres d'instance et des blocs de code non statiques.
    • Enfin, les instructions définies explicitement dans le constructeur sont appelées.

La création d'un tableau avec l'instruction new se déroule de la même façon (sachant que tout type de tableau hérite de la classe java.lang.Object), en allouant la place nécessaire pour le type des éléments multipliée par le nombre d'éléments voulu.

Le tas et la pile[modifier | modifier le wikicode]

Le tas est la zone mémoire où sont alloués les éléments de tableaux et les membres des objets. C'est une zone mémoire très grande, partagée par tous les processus légers (threads) de l'application Java. C'est pourquoi l'accès à un même objet ou tableau par plusieurs threads nécessite des moyens de synchronisation.

La pile est une zone mémoire locale limitée, alloué pour chaque processus léger (thread). Cette zone sert de stockage temporaire aux appels de méthodes pour y stocker la valeur des arguments, des variables locales, et de manière interne la valeur de retour.

public class Test
{
	int a;
	int b = 50;
	String c;
	String d = "test";

	// Code inclus dans tous les constructeurs :
	{
		c = d.toUpperCase();
		System.out.println("C = "+c);
	}

	public static Object creerTest()
	{
		Object o; // Une référence nulle, allouée sur la pile
		o = new Test(); // Objet alloué sur le tas
		return o; // La méthode retourne la référence à l'objet alloué dans le tas
	}
}