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.

Remarque: à partir du JDK 11, les méthodes synthétiques et les constructeurs ne sont plus générés, car ils sont remplacés par le contrôle d'accès basé sur l'imbrication.

2. Synthétique en Java

La meilleure définition de synthétique 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'un autre côté, bien que les classes imbriquées puissent être modifiées par le compilateur (c'est-à-dire les classes anonymes), elles ne sont pas considérées comme synthétiques .

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ée, toute classe interne contiendra un champ synthétiquequi fait référence à la classe de niveau supérieur. Par coïncidence, c'est ce qui permet d'accéder aux membres de classe englobants à partir d'une classe imbriquée.

Pour nous assurer que c'est bien ce qui se passe, nous implémenterons un test qui récupère les champs de classe imbriqués par réflexion et les vérifie à l'aide de la méthode isSynthetic () :

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 commande javap. Dans les deux cas, la sortie affiche un champ synthétique nommé $ 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, la compilation générera des accesseurs à la variable. Sans ces méthodes, il serait impossible d'accéder à un champ privé à partir de l'instance englobante.

Encore une fois, nous pouvons vérifier cela avec la même technique qui montre deux méthodes synthétiques appelées access $ 0 et access $ 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 que pour générer le code, le champ doit en fait être lu ou écrit , sinon les méthodes seront optimisées . C'est la raison pour laquelle nous avons également ajouté un getter et un setter.

Comme mentionné ci-dessus, ces méthodes de synthèse ne sont plus générées à partir de JDK 11.

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 comparateur :

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

Bien que comparer () prend deux entiers arguments dans la source, une fois compilé il faudra deux objets arguments au lieu, en fonction du type d' effacement.

Pour gérer cela, le compilateur crée un pont synthétique qui se charge de lancer les 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 également isBridge () depuis la classe Method :

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 y a 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 la même façon que les champs synthétiques, ce constructeur généré est essentiel pour instancier une classe imbriquée avec un constructeur privé à partir de son instance englobante.

Comme mentionné ci-dessus, le constructeur synthétique n'est plus généré à partir de JDK 11.

6. Conclusion

Dans cet article, nous avons discuté des constructions synthétiques générées par le compilateur Java. Pour les tester, nous avons utilisé la réflexion, sur laquelle vous pouvez en savoir plus ici.

Comme toujours, tout le code est disponible sur sur GitHub.