Constructions synthétiques en Java

Constructions synthétiques en Java

1. Vue d'ensemble

Dans ce didacticiel, nous examinerons les constructions synthétiques de Java, le code introduit par le compilateur pour gérer de manière transparente l'accès aux membres qui seraient autrement inaccessibles en raison d'une visibilité insuffisante ou de références manquantes.

2. Synthétique en Java

La meilleure définition desynthetic que nous pourrions trouver provient directement de la spécification du langage Java (JLS 13.1.7):

Toutes les constructions introduites par un compilateur Java qui n'ont pas de construction correspondante dans le code source doivent être marquées comme synthétiques, à l'exception des constructeurs par défaut, de la méthode d'initialisation de classe et des valeurs et méthodes valueOf de la classe Enum.

Il existe différents types de constructions de compilation, à savoir les champs, les constructeurs et les méthodes. D'autre part,although nested classes can be altered by the compiler (i.e. anonymous classes), they aren’t considered synthetic.

Sans plus tarder, approfondissons chacun de ces éléments.

3. Champs synthétiques

Commençons par une classe imbriquée simple:

public class SyntheticFieldDemo {
    class NestedClass {}
}

Une fois compilé,any inner class will contain a synthetic field which references the top level class. Coincidentally, this is what makes possible to access the enclosing class members from a nested class.

Pour nous assurer que c'est bien ce qui se passe, nous allons mettre en œuvre un test qui récupère les champs de classe imbriqués par réflexion et les vérifie à l'aide de la méthodeisSynthetic():

public void givenSyntheticField_whenIsSynthetic_thenTrue() {
    Field[] fields = SyntheticFieldDemo.NestedClass.class
      .getDeclaredFields();
    assertEquals("This class should contain only one field",
      1, fields.length);

    for (Field f : fields) {
        System.out.println("Field: " + f.getName() + ", isSynthetic: " +
          f.isSynthetic());
        assertTrue("All the fields of this class should be synthetic",
          f.isSynthetic());
    }
}

Une autre façon de vérifier cela serait d'exécuter le désassembleur via la commandejavap.  Dans les deux cas, la sortie affiche un champ synthétique nomméthis$0.

4. Méthodes synthétiques

Ensuite, nous ajouterons un champ privé à notre classe imbriquée:

public class SyntheticMethodDemo {
    class NestedClass {
        private String nestedField;
    }

    public String getNestedField() {
        return new NestedClass().nestedField;
    }

    public void setNestedField(String nestedField) {
        new NestedClass().nestedField = nestedField;
    }
}

Dans ce cas,the compilation will generate accessors to the variable. Without these methods, it’d be impossible to access a private field from the enclosing instance.

Encore une fois, nous pouvons le vérifier avec la même technique qui montre deux méthodes synthétiques appeléesaccess$0 etaccess$1:

public void givenSyntheticMethod_whenIsSynthetic_thenTrue() {
    Method[] methods = SyntheticMethodDemo.NestedClass.class
      .getDeclaredMethods();
    assertEquals("This class should contain only two methods",
      2, methods.length);

    for (Method m : methods) {
        System.out.println("Method: " + m.getName() + ", isSynthetic: " +
          m.isSynthetic());
        assertTrue("All the methods of this class should be synthetic",
          m.isSynthetic());
    }
}

Notez quein order to generate the code, the field must actually be read from or written to,otherwise, the methods will be optimized away. C'est la raison pour laquelle nous avons également ajouté un getter et un setter.

4.1. Méthodes de pont

Un cas particulier des méthodes synthétiques est les méthodes de pont, qui gèrent l'effacement de type des génériques.

Par exemple, considérons un simple Comparator:

public class BridgeMethodDemo implements Comparator {
    @Override
    public int compare(Integer o1, Integer o2) {
        return 0;
    }
}

Bien quecompare() accepte deux argumentsInteger dans la source, une fois compilé, il prendra deux argumentsObject à la place, en raison de l'effacement du type.

Pour gérer cela,the compiler creates a synthetic bridge which takes care of casting the arguments:

public int compare(Object o1, Object o2) {
    return compare((Integer) o1, (Integer) o2);
}

En plus de nos tests précédents, cette fois, nous appellerons égalementisBridge() de la classeMethod:

public void givenBridgeMethod_whenIsBridge_thenTrue() {
    int syntheticMethods = 0;
    Method[] methods = BridgeMethodDemo.class.getDeclaredMethods();
    for (Method m : methods) {
        System.out.println("Method: " + m.getName() + ", isSynthetic: " +
          m.isSynthetic() + ", isBridge: " + m.isBridge());
        if (m.isSynthetic()) {
            syntheticMethods++;
            assertTrue("The synthetic method in this class should also be a bridge method",
              m.isBridge());
        }
    }
    assertEquals("There should be exactly 1 synthetic bridge method in this class",
      1, syntheticMethods);
}

5. Constructeurs synthétiques

Enfin, nous ajouterons un constructeur privé:

public class SyntheticConstructorDemo {
    private NestedClass nestedClass = new NestedClass();

    class NestedClass {
        private NestedClass() {}
    }
}

Cette fois, une fois que nous avons exécuté le test ou le désassembleur, nous verrons qu'il existe en fait deux constructeurs, dont l'un est synthétique:

public void givenSyntheticConstructor_whenIsSynthetic_thenTrue() {
    int syntheticConstructors = 0;
    Constructor[] constructors = SyntheticConstructorDemo.NestedClass
      .class.getDeclaredConstructors();
    assertEquals("This class should contain only two constructors",
      2, constructors.length);

    for (Constructor c : constructors) {
        System.out.println("Constructor: " + c.getName() +
          ", isSynthetic: " + c.isSynthetic());

        if (c.isSynthetic()) {
            syntheticConstructors++;
        }
    }

    assertEquals(1, syntheticConstructors);
}

De même que les champs synthétiques,this generated constructor is essential to instantiate a nested class with a private constructor from its enclosing instance.

6. Conclusion

Dans cet article, nous avons abordé les constructions synthétiques générées par le compilateur Java. Pour les tester, nous avons utilisé la réflexion, dont vous pouvez en savoir plus surhere.

Comme toujours, tout le code est disponibleover on GitHub.