Impossible de référencer «X» avant que le constructeur de supertype n'ait été appelé

1. Vue d'ensemble

Dans ce court tutoriel, nous allons montrer comment nous pouvons obtenir l'erreur Impossible de référencer «X» avant que le constructeur de supertype n'ait été appelé, et comment l'éviter.

2. Chaîne de constructeurs

Un constructeur peut appeler exactement un autre constructeur. Cet appel doit être dans la première ligne de son corps.

On peut appeler un constructeur de la même classe avec le mot - clé this , ou on peut appeler un constructeur de la superclasse avec le mot - clé super .

Lorsqu'un constructeur n'appelle pas un autre constructeur, le compilateur ajoute un appel au constructeur sans argument de la superclasse.

3. Notre erreur de compilation

Cette erreur se résume à essayer d'accéder aux membres au niveau de l'instance avant d'appeler la chaîne de constructeur.

Voyons quelques façons dont nous pourrions rencontrer cela.

3.1. Référence à une méthode d'instance

Dans l'exemple suivant, nous verrons l'erreur de compilation Impossible de référencer «X» avant que le constructeur de supertype n'ait été appelé à la ligne 5. Notez que le constructeur tente d'utiliser la méthode d'instance getErrorCode () trop tôt:

public class MyException extends RuntimeException { private int errorCode = 0; public MyException(String message) { super(message + getErrorCode()); // compilation error } public int getErrorCode() { return errorCode; } } 

Cette erreur parce que, u usqu'à super () a terminé , il n'y a pas une instance de la classe MyException . Par conséquent, nous ne pouvons pas encore faire notre appel à la méthode d'instance getErrorCode () .

3.2. Faire référence à un champ d'instance

Dans l'exemple suivant, nous voyons notre exception avec un champ d'instance au lieu d'une méthode d'instance. Voyons comment le premier constructeur essaie d'utiliser un membre d'instance avant que l'instance elle-même ne soit prête:

public class MyClass { private int myField1 = 10; private int myField2; public MyClass() { this(myField1); // compilation error } public MyClass(int i) { myField2 = i; } }

Une référence à un champ d'instance ne peut être faite qu'après l'initialisation de sa classe, c'est-à-dire après tout appel à this () ou super () .

Alors, pourquoi n'y a-t-il pas d'erreur du compilateur dans le deuxième constructeur, qui utilise également un champ d'instance?

N'oubliez pas que toutes les classes sont implicitement dérivées de la classe Object , et qu'il y a donc un appel implicite super () ajouté par le compilateur:

public MyClass(int i) { super(); // added by compiler myField2 = i; } 

Ici, le constructeur d' Object est appelé avant d'accéder à myField2 , ce qui signifie que tout va bien.

4. Solutions

La première solution possible à ce problème est triviale: nous n'appelons pas le deuxième constructeur. Nous faisons explicitement dans le premier constructeur ce que nous voulions faire dans le second constructeur.

Dans ce cas, nous copierions la valeur de myField1 dans myField2 :

public class MyClass { private int myField1 = 10; private int myField2; public MyClass() { myField2 = myField1; } public MyClass(int i) { myField2 = i; } } 

En général, cependant, nous devons probablement repenser la structure de ce que nous construisons.

Mais, si nous appelons le deuxième constructeur pour une bonne raison, par exemple pour éviter de répéter du code, nous pouvons déplacer le code dans une méthode:

public class MyClass { private int myField1 = 10; private int myField2; public MyClass() { setupMyFields(myField1); } public MyClass(int i) { setupMyFields(i); } private void setupMyFields(int i) { myField2 = i; } } 

Encore une fois, cela fonctionne car le compilateur a implicitement appelé la chaîne de constructeur avant d'appeler la méthode.

Une troisième solution pourrait être que nous utilisons des champs ou des méthodes statiques . Si nous changeons myField1 en une constante statique, alors le compilateur est également heureux:

public class MyClass { private static final int SOME_CONSTANT = 10; private int myField2; public MyClass() { this(SOME_CONSTANT); } public MyClass(int i) { myField2 = i; } } 

Nous devons noter que rendre un champ statique signifie qu'il devient partagé avec toutes les instances de cet objet, donc ce n'est pas un changement à faire trop à la légère.

Pour que la statique soit la bonne réponse, nous avons besoin d'une bonne raison. Par exemple, peut-être que la valeur n'est pas vraiment un champ, mais plutôt une constante, il est donc logique de la rendre statique et définitive . Peut-être que la méthode de construction que nous voulions appeler n'a pas besoin d'accéder aux membres de l'instance de la classe, ce qui signifie qu'elle devrait être statique .

5. Conclusion

Nous avons vu dans cet article comment faire une référence aux membres de l'instance avant l' appel super () ou this () donne une erreur de compilation. Nous avons vu cela se produire avec une classe de base explicitement déclarée et aussi avec la classe de base Object implicite .

Nous avons également démontré qu'il s'agissait d'un problème avec la conception du constructeur et montré comment cela peut être résolu en répétant le code dans le constructeur, en déléguant à une méthode de configuration post-construction ou en utilisant des valeurs constantes ou des méthodes statiques pour aider à la construction .

Comme toujours, le code source de cet exemple peut être trouvé sur GitHub.