web-dev-qa-db-fra.com

Initialiser le champ avant l'exécution du super constructeur?

En Java, existe-t-il un moyen d'initialiser un champ avant l'exécution du super constructeur?

Même les hacks les plus laids que je puisse trouver sont rejetés par le compilateur:

class Base
{
    Base(String someParameter)
    {
        System.out.println(this);
    }
}

class Derived extends Base
{
    private final int a;

    Derived(String someParameter)
    {
        super(hack(someParameter, a = getValueFromDataBase()));
    }

    private static String hack(String returnValue, int ignored)
    {
        return returnValue;
    }

    public String toString()
    {
        return "a has value " + a;
    }
}

Remarque: Le problème a disparu lorsque je suis passé de l'héritage à la délégation, mais je voudrais quand même savoir.

35
fredoverflow

Non, il n'y a aucun moyen de le faire.

Selon les spécifications de langue , les variables d'instance ne sont même pas initialisées jusqu'à ce qu'un appel super() ait été effectué.

Voici les étapes effectuées lors de l'étape constructeur de la création d'instance de classe, à partir du lien:

  1. Affectez les arguments du constructeur aux variables de paramètre nouvellement créées pour cet appel de constructeur.
  2. Si ce constructeur commence par une invocation explicite de constructeur (§8.8.7.1) d'un autre constructeur de la même classe (en utilisant ceci), alors évaluez les arguments et traitez cette invocation de constructeur récursivement en utilisant ces mêmes cinq étapes. Si cette invocation de constructeur se termine brusquement, cette procédure se termine brusquement pour la même raison; sinon, passez à l'étape 5.
  3. Ce constructeur ne commence pas par une invocation de constructeur explicite d'un autre constructeur de la même classe (en utilisant ceci). Si ce constructeur est pour une classe autre que Object, alors ce constructeur commencera par une invocation explicite ou implicite d'un constructeur de superclasse (en utilisant super). Évaluez les arguments et traitez récursivement l'appel de constructeur de superclasse en utilisant ces cinq mêmes étapes. Si cette invocation de constructeur se termine brusquement, cette procédure se termine brusquement pour la même raison. Sinon, passez à l'étape 4.
  4. Exécutez les initialiseurs d'instance et les initialiseurs de variable d'instance pour cette classe, en affectant les valeurs des initialiseurs de variable d'instance aux variables d'instance correspondantes, dans l'ordre de gauche à droite dans lequel ils apparaissent textuellement dans le code source de la classe. Si l'exécution de l'un de ces initialiseurs entraîne une exception, aucun autre initialiseur n'est traité et cette procédure se termine brusquement avec cette même exception. Sinon, passez à l'étape 5.
  5. Exécutez le reste du corps de ce constructeur. Si cette exécution se termine brusquement, cette procédure se termine brusquement pour la même raison. Sinon, cette procédure se termine normalement.
26
Keppil

Le super constructeur fonctionnera dans tous les cas, mais comme nous parlons des "hacks les plus laids", nous pouvons en profiter

public class Base {
    public Base() {
        init();
    }

    public Base(String s) {
    }

    public void init() {
    //this is the ugly part that will be overriden
    }
}

class Derived extends Base{

    @Override
    public void init(){
        a = getValueFromDataBase();
    }
} 

Je ne suggère jamais d'utiliser ce genre de hacks.

11
Serkan Arıkuşu

J'ai un moyen de le faire.

class Derived extends Base
{
    private final int a;

    // make this method private
    private Derived(String someParameter,
                    int tmpVar /*add an addtional parameter*/) {
        // use it as a temprorary variable
        super(hack(someParameter, tmpVar = getValueFromDataBase()));
        // assign it to field a
        a = tmpVar;
    }

    // show user a clean constructor
    Derived(String someParameter)
    {   
        this(someParameter, 0)
    }

    ...
}
8
luobo25

Comme d'autres l'ont dit, vous ne pouvez pas initialiser le champ d'instance avant d'appeler le constructeur de la superclasse.

Mais il existe des solutions de contournement. L'une consiste à créer une classe d'usine qui obtient la valeur et la transmet au constructeur de la classe dérivée.

class DerivedFactory {
    Derived makeDerived( String someParameter ) {
        int a = getValueFromDataBase();
        return new Derived( someParameter, a );
    }
}


class Derived extends Base
{
    private final int a;

    Derived(String someParameter, int a0 ) {
        super(hack(someParameter, a0));
        a = a0;
    }
    ...
}
4
Andy Thomas

C'est interdit par la spécification du langage Java (section 8.8.7) :

La première instruction d'un corps constructeur peut être une invocation explicite d'un autre constructeur de la même classe ou de la superclasse directe.

Le corps du constructeur doit ressembler à ceci:

ConstructorBody:

{ ExplicitConstructorInvocationopt BlockStatementsopt }
1
dcernahoschi