JavaRush /Blog Java /Random-FR /Affectation et initialisation en Java
Viacheslav
Niveau 3

Affectation et initialisation en Java

Publié dans le groupe Random-FR

Introduction

La finalité principale des programmes informatiques est le traitement des données. Pour traiter des données, vous devez les stocker d'une manière ou d'une autre. Je propose de comprendre comment les données sont stockées.
Assignation et initialisation en Java - 1

Variables

Les variables sont des conteneurs qui stockent toutes les données. Regardons le didacticiel officiel d'Oracle : Déclaration des variables membres . Selon ce tutoriel, il existe plusieurs types de variables :
  • Champs : variables déclarées dans la classe ;
  • Variables locales : variables dans une méthode ou un bloc de code ;
  • Paramètres : variables dans la déclaration de la méthode (dans la signature).
Toutes les variables doivent avoir un type de variable et un nom de variable.
  • Le type d'une variable indique quelles données la variable représente (c'est-à-dire quelles données elle peut stocker). Comme nous le savons, le type d'une variable peut être primitif (primitives ) ou object , et non primitif (Non-primitive). Avec les variables objets, leur type est décrit par une classe spécifique.
  • Le nom de la variable doit être en minuscule, en casse chameau. Vous pouvez en savoir plus sur la dénomination dans « Variables : Dénomination ».
De plus, si une variable de niveau classe, c'est-à-dire est un champ de classe, un modificateur d'accès peut être spécifié pour celui-ci. Voir Contrôle de l'accès aux membres d'une classe pour plus de détails .

Déclaration de variables

Nous rappelons donc ce qu'est une variable. Pour commencer à travailler avec une variable, vous devez la déclarer. Tout d'abord, regardons une variable locale. Au lieu d'un IDE, pour plus de commodité, nous utiliserons la solution en ligne de tutorielspoint : IDE en ligne . Exécutons ce programme simple dans leur IDE en ligne :
public class HelloWorld{
    public static void main(String []args){
        int number;
        System.out.println(number);
    }
}
Ainsi, comme vous pouvez le voir, nous avons déclaré une variable locale avec name numberet type int. Nous appuyons sur le bouton « Exécuter » et obtenons l'erreur :
HelloWorld.java:5: error: variable number might not have been initialized
        System.out.println(number);
Ce qui s'est passé? Nous avons déclaré une variable, mais n'avons pas initialisé sa valeur. Il convient de noter que cette erreur ne s'est pas produite au moment de l'exécution (c'est-à-dire pas au moment de l'exécution), mais au moment de la compilation. Le compilateur intelligent a vérifié si la variable locale serait initialisée avant d'y accéder ou non. Par conséquent, les déclarations suivantes en découlent :
  • Les variables locales ne doivent être accessibles qu'après avoir été initialisées ;
  • Les variables locales n'ont pas de valeurs par défaut ;
  • Les valeurs des variables locales sont vérifiées au moment de la compilation.
On nous dit donc que la variable doit être initialisée. Initialiser une variable, c'est attribuer une valeur à une variable. Voyons ensuite de quoi il s'agit et pourquoi.

Initialiser une variable locale

L'initialisation des variables est l'un des sujets les plus délicats en Java, car... est très étroitement lié au travail avec la mémoire, à l'implémentation JVM, à la spécification JVM et à d'autres choses tout aussi effrayantes et délicates. Mais vous pouvez essayer de le comprendre au moins dans une certaine mesure. Passons du simple au complexe. Pour initialiser la variable, nous allons utiliser l'opérateur d'affectation et modifier la ligne dans notre code précédent :
int number = 2;
Dans cette option, il n'y aura aucune erreur et la valeur sera affichée à l'écran. Que se passe-t-il dans ce cas? Essayons de raisonner. Si nous voulons attribuer une valeur à une variable, alors nous voulons que cette variable stocke une valeur. Il s’avère que la valeur doit être stockée quelque part, mais où ? Sur disque ? Mais cela est très lent et peut nous imposer des restrictions. Il s’avère que le seul endroit où nous pouvons stocker des données rapidement et efficacement « ici et maintenant » est la mémoire. Cela signifie que nous devons allouer de l'espace en mémoire. C'est vrai. Lorsqu'une variable est initialisée, un espace lui sera alloué dans la mémoire allouée au processus Java au sein duquel notre programme sera exécuté. La mémoire allouée à un processus Java est divisée en plusieurs zones ou zones. Lequel d'entre eux allouera de l'espace dépend du type de variable déclaré. La mémoire est divisée en sections suivantes : Heap, Stack et Non-Heap . Commençons par la mémoire de pile. Stack est traduit par pile (par exemple, une pile de livres). Il s'agit d'une structure de données LIFO (Last In, First Out). C'est-à-dire comme une pile de livres. Lorsque nous y ajoutons des livres, nous les mettons au-dessus, et lorsque nous les retirons, nous prenons celui du haut (c'est-à-dire celui qui a été ajouté le plus récemment). Nous lançons donc notre programme. Comme nous le savons, un programme Java est exécuté par une JVM, c'est-à-dire une machine virtuelle Java. La JVM doit savoir où l'exécution du programme doit commencer. Pour ce faire, nous déclarons une méthode principale, appelée « point d’entrée ». Pour l'exécution dans la JVM, un thread principal (Thread) est créé. Lorsqu'un thread est créé, sa propre pile en mémoire lui est allouée. Cette pile est constituée de frames. Lorsque chaque nouvelle méthode est exécutée dans un thread, un nouveau cadre lui sera alloué et ajouté en haut de la pile (comme un nouveau livre dans une pile de livres). Ce cadre contiendra des références à des objets et des types primitifs. Oui, oui, notre int sera stocké sur la pile, car... int est un type primitif. Avant d'attribuer une trame, la JVM doit comprendre ce qu'elle doit y enregistrer. C'est pour cette raison que nous recevrons l'erreur « la variable n'a peut-être pas été initialisée », car si elle n'est pas initialisée, alors la JVM ne pourra pas préparer la pile pour nous. Par conséquent, lors de la compilation d’un programme, un compilateur intelligent nous aidera à éviter de faire des erreurs et de tout casser. (!) Pour plus de clarté, je recommande un article super-duper : « Java Stack and Heap : Java Memory Allocation Tutorial ». Il renvoie à une vidéo tout aussi cool :
Une fois l'exécution d'une méthode terminée, les trames allouées à ces méthodes seront supprimées de la pile du thread et, avec elles, la mémoire allouée à cette trame avec toutes les données sera effacée.

Initialisation des variables d'objet locales

Modifions à nouveau notre code pour un code un peu plus délicat :
public class HelloWorld{

    private int number = 2;

    public static void main(String []args){
        HelloWorld object = new HelloWorld();
        System.out.println(object.number);
    }

}
Que va-t-il se passer ici ? Parlons-en encore. La JVM sait d'où elle doit exécuter le programme, c'est-à-dire elle voit la méthode principale. Il crée un thread et lui alloue de la mémoire (après tout, un thread doit stocker les données nécessaires à l'exécution quelque part). Dans ce fil de discussion, un cadre est alloué à la méthode principale. Ensuite, nous créons un objet HelloWorld. Cet objet n'est plus créé sur la pile, mais sur le tas. Parce que l'objet n'est pas un type primitif, mais un type d'objet. Et la pile ne stockera qu'une référence à l'objet dans le tas (il faut d'une manière ou d'une autre accéder à cet objet). Ensuite, dans la pile de la méthode principale, des frames seront allouées pour exécuter la méthode println. Après avoir exécuté la méthode principale, toutes les images seront détruites. Si la trame est détruite, toutes les données seront détruites. L'objet objet ne sera pas détruit immédiatement. Premièrement, la référence à celui-ci sera détruite et ainsi plus personne ne fera référence à l'objet objet et l'accès à cet objet en mémoire ne sera plus possible. Une JVM intelligente dispose de son propre mécanisme pour cela - un garbage collector (garbage collector ou GC en abrégé). Il supprime ensuite de la mémoire les objets auxquels personne d’autre ne fait référence. Ce processus a de nouveau été décrit dans le lien ci-dessus. Il y a même une vidéo avec une explication.

Initialisation des champs

L'initialisation des champs spécifiés dans une classe s'effectue de manière particulière selon que le champ est statique ou non. Si un champ contient le mot-clé static, alors ce champ fait référence à la classe elle-même, et si le mot static n'est pas spécifié, alors ce champ fait référence à une instance de la classe. Regardons cela avec un exemple :
public class HelloWorld{
    private int number;
    private static int count;

    public static void main(String []args){
        HelloWorld object = new HelloWorld();
        System.out.println(object.number);
    }
}
Dans cet exemple, les champs sont initialisés à des moments différents. Le champ numérique sera initialisé après la création de l'objet de classe HelloWorld. Mais le champ count sera initialisé lorsque la classe sera chargée par la machine virtuelle Java. Le chargement des classes est un sujet distinct, nous ne le mélangerons donc pas ici. Il vaut simplement la peine de savoir que les variables statiques sont initialisées lorsque la classe est connue au moment de l'exécution. Quelque chose d’autre est plus important ici, et vous l’avez déjà remarqué. Nous n'avons spécifié la valeur nulle part, mais cela fonctionne. Et en effet. Les variables qui sont des champs, si elles n'ont pas de valeur spécifiée, elles sont initialisées avec une valeur par défaut. Pour les valeurs numériques, il s'agit de 0 ou 0,0 pour les nombres à virgule flottante. Pour le booléen, c'est faux. Et pour toutes les variables de type objet, la valeur sera nulle (nous en reparlerons plus tard). Il semblerait, pourquoi en est-il ainsi ? Mais parce que les objets sont créés en Heap (dans le tas). Le travail avec cette zone est effectué dans le Runtime. Et on peut initialiser ces variables au moment de l'exécution, contrairement à la pile, dont la mémoire doit être préparée avant l'exécution. C'est ainsi que fonctionne la mémoire en Java. Mais il y a encore une fonctionnalité ici. Ce petit morceau touche à différents coins de la mémoire. On s'en souvient, une frame est allouée dans la mémoire Stack pour la méthode main. Ce cadre stocke une référence à un objet dans la mémoire Heap. Mais où est alors stocké le décompte ? On le rappelle, cette variable est initialisée immédiatement, avant la création de l'objet dans le tas. C'est une question vraiment délicate. Avant Java 8, il existait une zone mémoire appelée PERMGEN. Depuis Java 8, cette zone a changé et s'appelle METASPACE. Essentiellement, les variables statiques font partie de la définition de classe, c'est-à-dire ses métadonnées. Il est donc logique qu’elles soient stockées dans le référentiel de métadonnées METASPACE. MetaSpace appartient à la même zone de mémoire non tas et en fait partie. Il est également important de prendre en compte que l'ordre dans lequel les variables sont déclarées est pris en compte. Par exemple, il y a une erreur dans ce code :
public class HelloWorld{

    private static int b = a;
    private static int a = 1;

    public static void main(String []args){
        System.out.println(b);
    }

}

Qu'est-ce qui est nul

Comme indiqué ci-dessus, les variables de types d'objet, s'il s'agit de champs d'une classe, sont initialisées aux valeurs par défaut et cette valeur par défaut est nulle. Mais qu’est-ce qui est nul en Java ? La première chose à retenir est que les types primitifs ne peuvent pas être nuls. Et tout cela parce que null est une référence spéciale qui ne fait référence à aucun objet. Par conséquent, seule une variable objet peut être nulle. La deuxième chose qu’il est important de comprendre est que null est une référence. Je fais référence à leur poids également. Sur ce sujet, vous pouvez lire la question sur stackoverflow : " La variable nulle nécessite-t-elle de l'espace en mémoire ".

Blocs d'initialisation

Lorsqu’on considère l’initialisation des variables, ce serait un péché de ne pas considérer les blocs d’initialisation. Cela ressemble à ceci :
public class HelloWorld{

    static {
        System.out.println("static block");
    }

    {
        System.out.println("block");
    }

    public HelloWorld () {
        System.out.println("Constructor");
    }

    public static void main(String []args){
        HelloWorld obj = new HelloWorld();
    }

}
L'ordre de sortie sera : bloc statique, bloc, constructeur. Comme on peut le voir, les blocs d'initialisation sont exécutés avant le constructeur. Et parfois, cela peut être un moyen d’initialisation pratique.

Conclusion

J'espère que ce bref aperçu a pu donner un aperçu de comment cela fonctionne et pourquoi. #Viacheslav
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION