Comment créer et détruire des objets dans Java
Ce cours est conçu pour vous aider à tirer le meilleur parti de Java. Il aborde des sujets avancés, notamment la création d’objets, la sérialisation, la réflexion, etc. Il vous guidera tout au long de votre parcours vers la maîtrise de Java!
Sommaire
1. Introduction
Le langage de programmation Java est créé par Sun Microsystems en 1995, est l’un des langages de programmation les plus utilisés au monde. Java est un langage de programmation généraliste. Il est attrayant pour les développeurs de logiciels, principalement en raison de ses bibliothèques et de son environnement d’exécution puissants, de sa syntaxe simple, de son riche ensemble de plates-formes prises en charge (Write Once, Run Anywhere – WORA) et de sa communauté impressionnante.
Dans ce didacticiel, nous allons couvrir les concepts avancés de Java, en supposant que nos lecteurs possèdent déjà des connaissances de base du langage. Il ne s’agit en aucun cas d’une référence complète, mais d’un guide détaillé pour faire progresser vos compétences en Java.
2. Construction d’instance
Java est un langage orienté objet et, en tant que tel, la création de nouvelles instances de classe (objets) en est probablement le concept le plus important. Les constructeurs jouent un rôle central dans l’initialisation d’une nouvelle instance de classe et Java offre plusieurs avantages pour les définir.
2.1. Constructeur implicite (généré)
Java permet de définir une classe sans constructeur mais cela ne signifie pas que la classe n’en aura pas. Par exemple, considérons cette classe:
package com.tutojava.avancee.construction;
public class NoConstructor {
}
Cette classe n’a pas de constructeur mais le compilateur Java en générera une implicitement et la création de nouvelles instances de classe sera possible à l’aide d’un nouveau mot clé.
final NoConstructor noConstructorInstance = new NoConstructor();
2.2. Constructeurs sans argument
Le constructeur sans arguments (ou le constructeur no-arg) est le moyen le plus simple de faire explicitement le travail du compilateur Java.
package com.tutojava.avancee.construction;
public class NoArgConstructor {
public NoArgConstructor() {
// Corps du constructeur
}
}
Ce constructeur sera appelé une fois la nouvelle instance de la classe créée avec le mot-clé new.
final NoArgConstructor noArgConstructor = new NoArgConstructor();
2.3. Constructeurs avec arguments
Les constructeurs avec arguments constituent le moyen le plus intéressant et le plus utile de paramétrer la création de nouvelles instances de classe. L’exemple suivant définit un constructeur avec deux arguments.
package com.tutojava.avancee.construction;
public class ConstructorWithArguments {
public ConstructorWithArguments(final String arg1,final String arg2) {
// Corps du constructeur
}
}
Dans ce cas, lorsque l’instance de classe est créée à l’aide du mot-clé new, les deux arguments de constructeur doivent être fournis.
final ConstructorWithArguments constructorWithArguments =
new ConstructorWithArguments( "arg1", "arg2" );
Les constructeurs peuvent s’appeler en utilisant le mot-clé spécial this. Il est considéré comme une bonne pratique d’enchaîner les constructeurs de manière à réduire la duplication de code et à créer un point d’entrée d’initialisation unique.
A titre d’exemple, ajoutons un autre constructeur avec un seul argument.
public ConstructorWithArguments(final String arg1) {
this(arg1, null);
}
2.4. Blocs d’initialisation
Java dispose d’un autre moyen de fournir une logique d’initialisation à l’aide de blocs d’initialisation. Cette fonctionnalité est rarement utilisée mais il est préférable de savoir qu’elle existe.
package com.tutojava.avancee.construction;
public class InitializationBlock {
{
// code source d'initialisation
}
}
D’une certaine manière, le bloc d’initialisation peut être traité comme un constructeur anonyme sans argument. La classe particulière peut avoir plusieurs blocs d’initialisation et tous seront appelés dans l’ordre dans lequel ils ont été définis dans le code. Par exemple:
package com.tutojava.avancee.construction;
public class InitializationBlocks {
{
// initialisation code source
}
{
// initialisation code source
}
}
Les blocs d’initialisation ne remplacent pas les constructeurs et peuvent être utilisés avec eux. Mais il est très important de mentionner que les blocs d’initialisation sont toujours appelés avant tout constructeur.
package com.tutojava.avancee.construction;
public class InitializationBlockAndConstructor {
{
// initialization code here
}
public InitializationBlockAndConstructor() {
}
}
2.5 Collecte des ordures (Garbage Collector)
Java (et la machine virtuelle Java en particulier) utilise la récupération de zone mémoire automatique. Pour le dire simplement, chaque fois que de nouveaux objets sont créés, la mémoire leur est automatiquement allouée. Par conséquent, chaque fois que les objets ne sont plus référencés, ils sont détruits et leur mémoire est récupérée.
La récupération de zone mémoire en Java est générationnelle et repose sur l’hypothèse que la plupart des objets ne sont plus référencés peu de temps après leur création et peuvent donc être détruits en toute sécurité.
La plupart des développeurs avaient l’impression que la création d’objets en Java est lente et que l’instanciation des nouveaux objets doit être évitée autant que possible. En fait, cela n’est pas vrai: la création d’objets en Java est relativement peu coûteuse et rapide. Cependant, ce qui est coûteux, c’est la création inutile d’objets qui pourraient éventuellement ralentir l’execution d,un programme.
2.6. Finalizer
Jusqu’à présent, nous avons parlé des constructeurs et de l’initialisation des objets, mais nous n’avons en réalité rien mentionné à propos de leur contrepartie: la destruction des objets. En effet, Java utilise le garbage collection pour gérer le cycle de vie des objets et il incombe au garbage collector de détruire les objets inutiles et de récupérer la mémoire.
Cependant, il existe une fonctionnalité particulière dans Java appelée finaliseurs ou finalizer, qui ressemble un peu aux destructeurs mais sert le but différent du nettoyage des ressources. Les finaliseurs sont considérés comme une caractéristique dangereuse (ce qui entraîne de nombreux effets secondaires et problèmes de performances). En règle générale, ils ne sont pas nécessaires et doivent être évités (sauf de très rares cas liés principalement à des objets natifs). Une alternative bien meilleure aux finaliseurs est la construction introduite par Java 7, appelée try-with-resources qui permet d’écrire du code épuré comme ceci:
try ( final InputStream in = Files.newInputStream( path ) ) {
// code source
}
3. Initialisation statique
Jusqu’ici, nous avons examiné la construction et l’initialisation d’instances de classe. Mais Java prend également en charge les constructions d’initialisation au niveau classe appelées initialiseurs statiques. Les blocs d’initialisation sont très similaires, à l’exception du mot-clé static supplémentaire. Veuillez noter que l’initialisation statique est effectuée une fois par chargement d’une classe. Par exemple:
package com.tutojava.avancee.construction;
public class StaticInitializationBlock {
static {
// static initialisation code source
}
}
De la même manière que les blocs d’initialisation, vous pouvez inclure un nombre quelconque de blocs d’initialisation statiques dans la définition de classe. Ils seront exécutés dans l’ordre dans lequel ils apparaissent dans le code. Par exemple:
package com.tutojava.avancee.construction;
public class StaticInitializationBlocks {
static {
// static initialisation code source
}
static {
// static initialisation code source
}
}
Étant donné que le bloc d’initialisation statique peut être déclenché à partir de plusieurs threads parallèles (lorsque le chargement de la classe a lieu pour la première fois), le runtime Java garantit qu’il ne sera exécuté qu’une fois et de manière sécurisée pour les threads.
4. Modèles de constructeurs
Au fil des ans, quelques modèles de constructeurs bien compris et largement applicables ont émergé au sein de la communauté Java. Nous allons couvrir les plus célèbres d’entre eux: singleton, helpers, factor et injection de dépendance (également appelée inversion de contrôle).
4.1. Singleton
Le Singleton est l’un des schémas les plus anciens et les plus controversés de la communauté des développeurs de logiciels. En gros, l’idée principale est de faire en sorte qu’une seule instance de la classe puisse être créée à un moment donné. Cependant, étant si simple, le singleton a soulevé de nombreuses discussions sur la façon de remédier à la situation et, en particulier, à la sécurité des threads. Voici à quoi peut ressembler une version naïve de classe singleton :
package com.tutojava.avancee.construction.patterns;
public class NaiveSingleton {
private static NaiveSingleton instance;
private NaiveSingleton() {
}
public static NaiveSingleton getInstance() {
if( instance == null ) {
instance = new NaiveSingleton();
}
return instance;
}
}
Au moins un problème avec ce code est qu’il peut créer de nombreuses instances de la classe s’il est appelé simultanément par plusieurs threads. L’un des moyens de concevoir un singleton correctement (mais de manière non paresseuse) consiste à utiliser la propriété statique final de la classe.
final property of the class.
package com.tutojava.avancee.construction.patterns;
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {
}
public static EagerSingleton getInstance() {
return instance;
}
}
Si vous ne voulez pas gaspiller vos ressources et que vous souhaitez que vos singletons soient créés quand ils sont réellement nécessaires, la synchronisation explicite est nécessaire, ce qui peut entraîner une réduction de la simultanéité dans un environnement multithread , Meilleures pratiques en matière de concurrence).
package com.tutojava.avancee.construction.patterns;
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
}
public static synchronized LazySingleton getInstance() {
if( instance == null ) {
instance = new LazySingleton();
}
return instance;
}
}
De nos jours, les singletons ne sont pas considérés comme un bon choix dans la plupart des cas, principalement parce qu’ils créent un code très difficile à tester. La domination du modèle d’injection de dépendance (veuillez vous reporter à la section Injection de dépendance ci-dessous) rend également les singletons inutiles.
4.2. Classe utilitaire / assistant
Les classes utilitaires constituent un modèle très répandu utilisé par de nombreux développeurs Java. Fondamentalement, il représente la classe non instanciable (avec le constructeur déclaré comme privé), éventuellement déclaré comme final et contient des méthodes statiques seulement. Par exemple:
package com.tutojava.avancee.construction.patterns;
public final class HelperClass {
private HelperClass() {
}
public static void helperMethod1() {
// Method code source
}
public static void helperMethod2() {
// Method code source
}
}
Du point de vue des développeurs de logiciels chevronnés, de tels assistants deviennent souvent des conteneurs pour toutes sortes de méthodes non liées qui n’ont pas trouvé d’endroit différent, mais qui devraient être partagées et utilisées par d’autres classes.
De telles décisions de conception doivent être évitées dans la plupart des cas: il est toujours possible de trouver un autre moyen de réutiliser les fonctionnalités requises tout en maintenant le code propre et concis.
4.3. Factor
Le modèle factor s’est avéré être une technique extrêmement utile entre les mains des développeurs de logiciels. En tant que tel, il a plusieurs variantes en Java, allant de la méthode factory à la construction abstraite. L’exemple le plus simple de modèle de factory est une méthode statique qui renvoie une nouvelle instance d’une classe particulière . Par exemple:
package com.tutojava.avancee.construction.patterns;
public class Book {
private Book( final String title) {
}
public static Book newBook( final String title ) {
return new Book( title );
}
}
On peut faire valoir qu’il n’a pas beaucoup de sens d’introduire la méthode newBook, mais l’utilisation d’un tel modèle rend souvent le code plus lisible. Une autre variance du modèle implique des interfaces ou des classes abstraites. Par exemple, définissons une interface :
public interface BookFactory {
Book newBook();
}
Avec quelques implémentations différentes, en fonction du type de bibliothèque:
public class Library implements BookFactory {
@Override
public Book newBook() {
return new PaperBook();
}
}
public class KindleLibrary implements BookFactory {
@Override
public Book newBook() {
return new KindleBook();
}
}
Désormais, la classe particulière du livre est cachée derrière la mise en œuvre de l’interface BookFactory, offrant toujours le moyen générique de créer des livres.