1. Initialisation des variables

Comme vous le savez déjà, vous pouvez déclarer plusieurs variables dans votre classe, et non seulement les déclarer, mais aussi les initialiser immédiatement avec leurs valeurs initiales.

Et ces mêmes variables peuvent également être initialisées dans un constructeur. Cela signifie qu'en théorie, ces variables pourraient être évaluées deux fois. Exemple

Code Note
class Cat
{
   public String name;
   public int age = -1;

   public Cat(String name, int age)
   {
     this.name = name;
     this.age = age;
   }

   public Cat()
   {
     this.name = "Nameless";
   }
}



La agevariable reçoit une valeur initiale




La valeur initiale est écrasée


La variable age stocke sa valeur initiale.
 Cat cat = new Cat("Whiskers", 2);
Ceci est autorisé : le premier constructeur sera appelé
 Cat cat = new Cat();
Ceci est autorisé : le deuxième constructeur sera appelé

C'est ce qui se passe quand Cat cat = new Cat("Whiskers", 2);est exécuté :

  • Un Catobjet est créé
  • Toutes les variables d'instance sont initialisées avec leurs valeurs initiales
  • Le constructeur est appelé et son code est exécuté.

En d'autres termes, les variables reçoivent d'abord leurs valeurs initiales, puis le code du constructeur est exécuté.


2. Ordre d'initialisation des variables dans une classe

Les variables ne sont pas simplement initialisées avant l'exécution du constructeur — elles sont initialisées dans un ordre bien défini : l'ordre dans lequel elles sont déclarées dans la classe.

Regardons un code intéressant :

Code Note
public class Solution
{
   public int a = b + c + 1;
   public int b = a + c + 2;
   public int c = a + b + 3;
}

Ce code ne compilera pas, car au moment où la avariable est créée, il n'y a pas  encore de variables b et . cMais vous pouvez écrire votre code comme suit — ce code se compilera et fonctionnera parfaitement.

Code Note
public class Solution
{
   public int a;
   public int b = a + 2;
   public int c = a + b + 3;
}


0
0+2
0+2+3

Mais n'oubliez pas que votre code doit être transparent pour les autres développeurs. Il est préférable de ne pas utiliser de telles techniques, car cela nuit à la lisibilité du code.

Ici, nous devons nous rappeler qu'avant d'attribuer une valeur aux variables, elles ont une valeur par défaut . Pour le inttype, c'est zéro.

Lorsque la JVM initialise la avariable, elle assignera simplement la valeur par défaut pour le type int : 0.

Lorsqu'elle atteint b, la variable a sera déjà connue et aura une valeur, donc la JVM lui attribuera la valeur 2.

Et lorsqu'elle atteint la cvariable, les variables aet bseront déjà initialisées, donc la JVM calculera facilement la valeur initiale pour c: 0+2+3.

Si vous créez une variable à l'intérieur d'une méthode, vous ne pouvez pas l'utiliser à moins que vous ne lui ayez préalablement attribué une valeur. Mais ce n'est pas vrai pour les variables d'une classe ! Si une valeur initiale n'est pas affectée à une variable d'une classe, alors une valeur par défaut lui est affectée.


3. Constantes

Pendant que nous analysons comment les objets sont créés, il est intéressant de toucher à l'initialisation des constantes, c'est-à-dire des variables avec le finalmodificateur.

Si une variable a le finalmodificateur, alors une valeur initiale doit lui être assignée. Vous le savez déjà, et il n'y a rien d'étonnant à cela.

Mais ce que vous ne savez pas, c'est que vous n'êtes pas obligé d'assigner la valeur initiale tout de suite si vous l'assignez dans le constructeur. Cela fonctionnera très bien pour une variable finale. La seule exigence est que si vous avez plusieurs constructeurs, une valeur finale doit être affectée à une variable finale dans chaque constructeur.

Exemple:

public class Cat
{
   public final int maxAge = 25;
   public final int maxWeight;

   public Cat (int weight)
   {
     this.maxWeight = weight; // Assign an initial value to the constant
   }
}


4. Code dans un constructeur

Et quelques notes plus importantes sur les constructeurs. Plus tard, au fur et à mesure que vous continuerez à apprendre Java, vous rencontrerez des choses comme l'héritage, la sérialisation, les exceptions, etc. Ils influencent tous le travail des constructeurs à des degrés divers. Cela n'a aucun sens d'approfondir ces sujets maintenant, mais nous sommes obligés de les aborder au moins.

Par exemple, voici une remarque importante sur les constructeurs. En théorie, vous pouvez écrire du code de n'importe quelle complexité dans un constructeur. Mais ne fais pas ça. Exemple:

class FilePrinter
{
   public String content;

   public FilePrinter(String filename) throws Exception
   {
      FileInputStream input = new FileInputStream(filename);
      byte[] buffer = input.readAllBytes();
      this.content = new String(buffer);
   }

   public void printFile()
   {
      System.out.println(content);
   }
}






Ouvrir un flux de lecture de fichier
Lire le fichier dans un tableau d'octets
Enregistrer le tableau d'octets sous forme de chaîne




Afficher le contenu du fichier à l'écran

Dans le constructeur de la classe FilePrinter, nous avons immédiatement ouvert un flux d'octets sur un fichier et lu son contenu. Ce comportement est complexe et peut entraîner des erreurs.

Et s'il n'y avait pas un tel fichier? Et s'il y avait des problèmes avec la lecture du fichier ? Et si c'était trop gros ?

La logique complexe implique une forte probabilité d'erreurs et cela signifie que le code doit gérer correctement les exceptions.

Exemple 1 — Sérialisation

Dans un programme Java standard, il existe de nombreuses situations où vous n'êtes pas celui qui crée les objets de votre classe. Par exemple, supposons que vous décidiez d'envoyer un objet sur le réseau : dans ce cas, la machine Java elle-même convertira votre objet en un ensemble d'octets, l'enverra et recréera l'objet à partir de l'ensemble d'octets.

Mais supposons que votre fichier n'existe pas sur l'autre ordinateur. Il y aura une erreur dans le constructeur et personne ne la gérera. Et cela est tout à fait capable de provoquer l'arrêt du programme.

Exemple 2 — Initialisation des champs d'une classe

Si votre constructeur de classe peut lancer des exceptions vérifiées, c'est-à-dire qu'il est marqué avec le mot-clé throws, vous devez intercepter les exceptions indiquées dans la méthode qui crée votre objet.

Mais que se passe-t-il s'il n'y a pas une telle méthode? Exemple:

Code  Note
class Solution
{
   public FilePrinter reader = new FilePrinter("c:\\readme.txt");
}
Ce code ne compilera pas.

Le FilePrinterconstructeur de classe peut lancer une exception vérifiée , ce qui signifie que vous ne pouvez pas créer un FilePrinterobjet sans l'envelopper dans un bloc try-catch. Et un bloc try-catch ne peut être écrit que dans une méthode



5. Constructeur de classe de base

Dans les leçons précédentes, nous avons un peu parlé de l'héritage. Malheureusement, notre discussion complète sur l'héritage et la POO est réservée au niveau dédié à la POO, et l'héritage des constructeurs est déjà pertinent pour nous.

Si votre classe hérite d'une autre classe, un objet de la classe parent sera intégré dans un objet de votre classe. De plus, la classe parent a ses propres variables et ses propres constructeurs.

Cela signifie qu'il est très important pour vous de connaître et de comprendre comment les variables sont initialisées et les constructeurs sont appelés lorsque votre classe a une classe parente et que vous héritez de ses variables et méthodes.

Des classes

Comment connaître l'ordre dans lequel les variables sont initialisées et les constructeurs appelés ? Commençons par écrire le code pour deux classes. L'un héritera de l'autre :

Code Note
class ParentClass
{
   public String a;
   public String b;

   public ParentClass()
   {
   }
}

class ChildClass extends ParentClass
{
   public String c;
   public String d;

   public ChildClass()
   {
   }
}










La ChildClass classe hérite de la ParentClassclasse.

Nous devons déterminer l'ordre dans lequel les variables sont initialisées et les constructeurs sont appelés. La journalisation nous aidera à le faire.

Enregistrement

La journalisation est le processus d'enregistrement des actions effectuées par un programme pendant son exécution, en les écrivant sur la console ou dans un fichier.

Il est assez simple de déterminer que le constructeur a été appelé : dans le corps du constructeur, écrivez un message à la console. Mais comment savoir si une variable a été initialisée ?

En fait, ce n'est pas non plus très difficile : écrivez une méthode spéciale qui renverra la valeur utilisée pour initialiser la variable et enregistrera l'initialisation. Voici à quoi pourrait ressembler le code :

Code final

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

   public static String print(String text)
   {
      System.out.println(text);
      return text;
   }
}

class ParentClass
{
   public String a = Main.print("ParentClass.a");
   public String b = Main.print("ParentClass.b");

   public ParentClass()
   {
      Main.print("ParentClass.constructor");
   }
}

class ChildClass extends ParentClass
{
   public String c = Main.print("ChildClass.c");
   public String d = Main.print("ChildClass.d");

   public ChildClass()
   {
      Main.print("ChildClass.constructor");
   }
}




Créer un ChildClassobjet


Cette méthode écrit le texte transmis à la console et le renvoie également.





Déclarez la ParentClassclasse

Display text et initialisez également les variables avec.




Écrivez un message indiquant que le constructeur a été appelé. Ignorez la valeur de retour.


Déclarez la ChildClassclasse

Display text et initialisez également les variables avec.




Écrivez un message indiquant que le constructeur a été appelé. Ignorez la valeur de retour.

Si vous exécutez ce code, le texte s'affichera à l'écran comme suit :

Sortie console de la méthodeMain.print()
ParentClass.a
ParentClass.b
ParentClass.constructor
ChildClass.c
ChildClass.d
ChildClass.constructor

Ainsi, vous pouvez toujours vous assurer personnellement que les variables d'une classe sont initialisées avant l'appel du constructeur. Une classe de base est entièrement initialisée avant l'initialisation de la classe héritée.