Méthodes synthétiques de Java

Dans cet article de blog, j'examine le concept des méthodes synthétiques Java. Cet article résume ce qu'est une méthode synthétique Java, comment elle peut être créée et identifiée, et les implications des méthodes synthétiques Java sur le développement Java.

La spécification du langage Java (section 13.1) stipule que "Toutes les constructions introduites par le compilateur 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 et de la méthode d'initialisation de classe." D'autres indices sur la signification de synthétique en Java peuvent être trouvés dans la documentation Javadoc pour Member.isSynthetic (). La documentation de cette méthode indique qu'elle renvoie «true si et seulement si ce membre a été introduit par le compilateur». J'aime cette définition très courte de «synthétique»: une construction Java introduite par le compilateur.

Le compilateur Java doit créer des méthodes synthétiques sur des classes imbriquées lorsque leurs attributs spécifiés avec le modificateur privé sont accédés par la classe englobante. L'exemple de code suivant indique cette situation.

DemonstrateSyntheticMethods.java (la classe englobante appelle un attribut privé de classe imbriquée)

package dustin.examples; import java.util.Calendar; import static java.lang.System.out; public final class DemonstrateSyntheticMethods { public static void main(final String[] arguments) { DemonstrateSyntheticMethods.NestedClass nested = new DemonstrateSyntheticMethods.NestedClass(); out.println("String: " + nested.highlyConfidential); } private static final class NestedClass { private String highlyConfidential = "Don't tell anyone about me"; private int highlyConfidentialInt = 42; private Calendar highlyConfidentialCalendar = Calendar.getInstance(); private boolean highlyConfidentialBoolean = true; } } 

Le code ci-dessus se compile sans incident. Lorsque javap est exécuté sur le .classfichier compilé , la sortie est comme indiqué dans la capture d'écran suivante.

Comme l'indique l'instantané d'écran ci-dessus, une méthode synthétique avec le nom access$100a été créée sur la classe imbriquée NestedClasspour fournir sa chaîne privée à la classe englobante. Notez que la méthode synthétique n'est ajoutée que pour l'attribut privé unique de NestedClass auquel la classe englobante accède. Si je change la classe englobante pour accéder à tous les attributs privés de NestedClass, des méthodes synthétiques supplémentaires seront générées. L'exemple de code suivant montre exactement cela et la capture d'écran qui suit prouve que quatre méthodes synthétiques sont générées dans ce cas.

DemonstrateSyntheticMethods.java (la classe englobante appelle quatre attributs privés de classe imbriquée)

package dustin.examples; import java.util.Calendar; import static java.lang.System.out; public final class DemonstrateSyntheticMethods { public static void main(final String[] arguments) { DemonstrateSyntheticMethods.NestedClass nested = new DemonstrateSyntheticMethods.NestedClass(); out.println("String: " + nested.highlyConfidential); out.println("Int: " + nested.highlyConfidentialInt); out.println("Calendar: " + nested.highlyConfidentialCalendar); out.println("Boolean: " + nested.highlyConfidentialBoolean); } private static final class NestedClass { private String highlyConfidential = "Don't tell anyone about me"; private int highlyConfidentialInt = 42; private Calendar highlyConfidentialCalendar = Calendar.getInstance(); private boolean highlyConfidentialBoolean = true; } } 

Comme le montrent les deux extraits de code précédents ci-dessus et les images associées, le compilateur Java introduit des méthodes synthétiques selon les besoins. Lorsqu'un seul des attributs privés de la classe imbriquée était accédé par la classe englobante, une seule méthode synthétique ( access$100) était créée par le compilateur. Cependant, lorsque les quatre attributs privés de la classe imbriquée sont accessibles par la classe externe, quatre procédés de synthèse correspondants ont été générés par le compilateur ( access$100, access$200, access$300, et access$400).

Dans tous les cas où une classe englobante accède aux données privées de sa classe imbriquée, une méthode synthétique a été créée pour permettre cet accès. Que se passe-t-il lorsque la classe imbriquée fournit un accesseur pour ses données privées que la classe englobante peut utiliser? Cela est démontré dans la liste de code suivante et dans sa sortie, comme indiqué dans la capture d'écran suivante.

DemonstrateSyntheticMethods.java avec un accesseur public de classe imbriquée pour les données privées

package dustin.examples; import java.util.Calendar; import java.util.Date; import static java.lang.System.out; public final class DemonstrateSyntheticMethods { public static void main(final String[] arguments) { DemonstrateSyntheticMethods.NestedClass nested = new DemonstrateSyntheticMethods.NestedClass(); out.println("String: " + nested.highlyConfidential); out.println("Int: " + nested.highlyConfidentialInt); out.println("Calendar: " + nested.highlyConfidentialCalendar); out.println("Boolean: " + nested.highlyConfidentialBoolean); out.println("Date: " + nested.getDate()); } private static final class NestedClass { private String highlyConfidential = "Don't tell anyone about me"; private int highlyConfidentialInt = 42; private Calendar highlyConfidentialCalendar = Calendar.getInstance(); private boolean highlyConfidentialBoolean = true; private Date date = new Date(); public Date getDate() { return this.date; } } } 

L'instantané d'écran ci-dessus montre que le compilateur n'avait pas besoin de générer une méthode synthétique pour accéder à l'attribut Date privée dans la classe imbriquée car la classe englobante accédait à cet attribut via la getDate()méthode fournie . Même avec getDate()fourni, le compilateur aurait généré une méthode synthétique pour accéder à si le datecode englobant a été écrit pour accéder datedirectement à l' attribut (en tant que propriété) plutôt que via la méthode d'accesseur.

Le dernier instantané d'écran fait apparaître une autre observation. Comme le getDate()montre la méthode nouvellement ajoutée dans cet instantané d'écran, des modificateurs tels que publicsont inclus dans la sortie javap. Étant donné qu'aucun modificateur n'est affiché pour les méthodes synthétiques créées par le compilateur, nous savons qu'elles sont au niveau du package (ou privées du package). En bref, le compilateur a créé des méthodes privées pour accéder aux attributs privés.

Les API de réflexion Java fournissent une autre approche pour déterminer les méthodes synthétiques. La liste de code suivante concerne un script Groovy qui utilisera les API de réflexion Java pour fournir de manière pratique des détails concernant les méthodes de la classe imbriquée illustrée ci-dessus.

ReflectOnMethods.groovy

#!/usr/bin/env groovy import java.lang.reflect.Method import java.lang.reflect.Modifier if (args == null || args.size() < 2) { println "Outer and nested class names must be provided." println "\nUsage #1: reflectOnMethods qualifiedOuterClassName nestedClassName\n" println "\nUsage #2: groovy -cp classpath reflectOnMethods.groovy qualifiedOuterClassName nestedClassName\n" println "\t1. Include outer and nested classes on classpath if necessary" println "\t2. Do NOT include \$ on front of nested class name.\n" System.exit(-1) } def enclosingClassName = args[0] def nestedClassName = args[1] def fullNestedClassName = enclosingClassName + '$' + nestedClassName def enclosingClass = Class.forName(enclosingClassName) Class nestedClass = null enclosingClass.declaredClasses.each { if (!nestedClass && fullNestedClassName.equals(it.name)) { nestedClass = it } } if (nestedClass == null) { println "Unable to find nested class ${fullNestedClassName}" System.exit(-2) } // Use declaredMethods because don't care about inherited methods nestedClass.declaredMethods.each { print "\nMethod '${it.name}' " print "is ${getScopeModifier(it)} scope, " print "${it.synthetic ? 'is synthetic' : 'is NOT synthetic'}, and " println "${it.bridge ? 'is bridge' : 'is NOT bridge'}." } def String getScopeModifier(Method method) { def modifiers = method.modifiers def isPrivate = Modifier.isPrivate(modifiers) def isPublic = Modifier.isPublic(modifiers) def isProtected = Modifier.isProtected(modifiers) String scopeString = "package-private" // default if (isPublic) { scopeString = "public" } else if (isProtected) { scopeString = "protected" } else if (isPrivate) { scopeString = "private" } return scopeString } 

Lorsque le script Groovy ci-dessus est exécuté sur la classe et la classe imbriquée illustrées ci-dessus, la sortie est celle affichée dans l'instantané d'écran suivant.

Les résultats du script Groovy montrés dans l'image précédente vérifient ce que javap nous avait déjà dit: il y a quatre méthodes synthétiques et une méthode non synthétique définies sur la classe imbriquée NestedClass. Le script nous indique également que les méthodes synthétiques générées par le compilateur sont de portée privée du package.

L'ajout de méthodes synthétiques à la classe imbriquée au niveau de la portée privée du package n'est pas la seule chose que le compilateur a faite dans l'exemple ci-dessus. Il a également changé la portée de la classe imbriquée elle-même du paramètre privé dans le code à package-private dans le .classfichier. En effet, alors que les méthodes synthétiques n'ont été ajoutées que dans le cas où la classe englobante accédait à l'attribut private, le compilateur rend toujours la classe imbriquée package-private même si elle est spécifiée comme privée dans le code. La bonne nouvelle est qu'il s'agit d'un artefact résultant du processus de compilation, ce qui signifie que le code ne peut pas être compilé tel quel par rapport au niveau de portée modifié de la classe imbriquée ou de ses méthodes synthétiques. Le runtime est l'endroit où les choses peuvent devenir risquées.

La classe Rogue tente d'accéder à certaines des méthodes synthétiques NestedClass. Son code source est affiché ensuite, suivi de l'erreur du compilateur lors de la tentative de compilation de ce code source Rogue.

Rogue.java essayant d'accéder aux méthodes synthétiques au moment de la compilation

package dustin.examples; import static java.lang.System.out; public class Rogue { public static void main(final String[] arguments) { out.println(DemonstrateSyntheticMethods.NestedClass.getDate()); } } 

Le code ci-dessus ne sera pas compilé, même pour la méthode non synthétique getDate(), et signale cette erreur:

Buildfile: C:\java\examples\synthetic\build.xml -init: compile: [javac] Compiling 1 source file to C:\java\examples\synthetic\classes [javac] C:\java\examples\synthetic\src\dustin\examples\Rogue.java:9: dustin.examples.DemonstrateSyntheticMethods.NestedClass has private access in dustin.examples.DemonstrateSyntheticMethods [javac] out.println(DemonstrateSyntheticMethods.NestedClass.getDate()); [javac] ^ [javac] 1 error BUILD FAILED C:\java\examples\synthetic\build.xml:29: Compile failed; see the compiler error output for details. Total time: 1 second 

Comme l'indique le message d'erreur de compilation ci-dessus, même la méthode non synthétique sur la classe imbriquée est inaccessible au moment de la compilation car la classe imbriquée a une portée privée. Dans son article Java Insecurities: Accounting for Subtleties That Can Compromise Code, Charlie Lai discute des situations potentielles dans lesquelles ces changements introduits par le compilateur sont des failles de sécurité. Faisal Feroz va plus loin et déclare, dans l'article Comment écrire du code Java sécurisé, "Ne pas utiliser les classes internes" (voir Classes imbriquées, internes, membres et de niveau supérieur pour plus de détails sur les classes internes en tant que sous-ensemble de classes imbriquées) .

Beaucoup d'entre nous peuvent travailler longtemps dans le développement Java sans avoir besoin d'une compréhension approfondie des méthodes synthétiques. Cependant, il y a des situations où la conscience de ces derniers est importante. Outre les problèmes de sécurité liés à ceux-ci, il faut également être conscient de ce qu'ils sont lors de la lecture des traces de pile. Noms méthode telle que access$100, access$200, access$300, access$400, access$500, access$600, et access$1000dans la trace de pile reflètent des méthodes de synthèse générées par le compilateur.

Article original disponible sur //marxsoftware.blogspot.com/

.

Cette histoire, "Java's Synthetic Methods" a été initialement publiée par JavaWorld.