Синтетичните методи на Java

В тази публикация в блога разглеждам концепцията за синтетичните методи на Java. Постът обобщава какво е синтетичен метод на Java, как може да бъде създаден и идентифициран и последиците от синтетичните методи на Java върху развитието на Java.

Спецификацията на езика Java (раздел 13.1) гласи "Всички конструкции, въведени от компилатора, които нямат съответна конструкция в изходния код, трябва да бъдат маркирани като синтетични, с изключение на конструкторите по подразбиране и метода за инициализация на класа." Допълнителни улики относно значението на синтетичния в Java могат да бъдат намерени в документацията на Javadoc за Member.isSynthetic (). Документацията на този метод гласи, че той връща „вярно, ако и само ако този член е въведен от компилатора“. Харесва ми тази много кратка дефиниция на "синтетичен": Java конструкция, въведена от компилатора.

Компилаторът на Java трябва да създава синтетични методи за вложени класове, когато техните атрибути, посочени с частния модификатор, са достъпни от затварящия клас. Следващият примерен код показва тази ситуация.

DemonstrateSyntheticMethods.java (Ограждащият клас предизвиква частен атрибут на един вложен клас)

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; } } 

Горният код се компилира без инциденти. Когато javap се изпълнява срещу компилирания .classфайл, изходът е както е показано на следващата екранна снимка.

Както показва горната снимка на екрана, access$100на вложения клас е създаден синтетичен метод с името, за NestedClassда предостави своя частен низ на затварящия клас. Обърнете внимание, че синтетичният метод се добавя само за единичния частен атрибут на NestedClass, до който има достъп ограждащият клас. Ако променя заграждащия клас за достъп до всички частни атрибути на NestedClass, ще бъдат генерирани допълнителни синтетични методи. Следващият пример с код демонстрира, че правим точно това, а моментната снимка на екрана след него доказва, че в този случай се генерират четири синтетични метода.

DemonstrateSyntheticMethods.java (Ограждащият клас извиква четири частни атрибута на вложен клас)

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; } } 

Както показват предишните два кодови фрагмента по-горе и свързаните изображения, компилаторът на Java въвежда синтетични методи при необходимост. Когато само един от частните атрибути на вложения клас е бил достъпен от заграждащия клас, само един синтетичен метод ( access$100) е създаден от компилатора. Въпреки това, когато всичките четири самостоятелни атрибутите на вместения клас са достъпни от класа на обхващащата, четири съответните синтетични методи са генерирани от компилатора ( access$100, access$200, access$300, и access$400).

Във всички случаи на затварящ клас достъп до личните данни на неговия вложен клас е създаден синтетичен метод, който да позволи този достъп да се случи. Какво се случва, когато вложеният клас предоставя достъп за своите лични данни, който затварящият клас може да използва? Това се демонстрира в следващия списък с кодове и в изхода му, както е показано на следващата екранна снимка.

DemonstrateSyntheticMethods.java с вложен клас публичен достъп за частни данни

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; } } } 

Горната снимка на екрана показва, че компилаторът не е трябвало да генерира синтетичен метод за достъп до атрибута private date в вложения клас, тъй като затварящият клас е имал достъп до този атрибут чрез предоставения getDate()метод. Дори и getDate()при условие, компилаторът би генерирал синтетичен метод за достъп до dateограждащия код, написан за директен достъп до dateатрибута (като свойство), а не чрез метода за достъп.

Последната снимка на екрана извежда още едно наблюдение. Както новодобавеният getDate()метод показва в тази моментна снимка на екрана, модификатори като например publicса включени в javap изхода. Тъй като не е показан модификатор за синтетичните методи, създадени от компилатора, знаем, че те са на ниво пакет (или пакетно-частен). Накратко, компилаторът е създал частни методи за достъп до частни атрибути.

API за отразяване на Java предоставят друг подход за определяне на синтетични методи. Следващият списък с кодове е за скрипт Groovy, който ще използва API за отражение на Java, за да предостави удобно подробности относно методите на вложения клас, показани по-горе.

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 } 

Когато горният скрипт Groovy се изпълни срещу класа и вложения клас, показан по-горе, изходът е този, показан в следващата екранна снимка.

Резултатите от скрипта Groovy, показани в предишното изображение, потвърждават това, което javap вече ни е казал: има четири синтетични метода и един несинтетичен метод, дефиниран в вложения клас NestedClass. Скриптът също така ни казва, че генерираните от компилатора синтетични методи са с частен обхват.

Добавянето на синтетични методи към вложения клас на ниво частен обхват на пакета не е единственото нещо, което компилаторът направи в горния пример. Той също така промени обхвата на самия вложен клас от частната настройка в кода на package-private във .classфайла. Всъщност, докато синтетичните методи са добавени само в случая, когато затварящият клас е осъществил достъп до атрибута private, компилаторът винаги прави вложения клас private-private, дори ако той е посочен като private в кода. Добрата новина е, че това е произтичащ артефакт от процеса на компилация, което означава, че кодът не може да се компилира както е спрямо промененото ниво на обхват на вложения клас или неговите синтетични методи. Времето на изпълнение е мястото, където нещата могат да се заблудят.

Класът, Rogue, се опитва да получи достъп до някои от синтезираните методи на NestedClass. Неговият изходен код е показан по-нататък, последван от грешката на компилатора, видяна при опит за компилиране на този измамник измамник.

Rogue.java се опитва да получи достъп до синтетични методи по време на компилация

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

Горният код няма да се компилира дори за несинтетичния метод getDate()и отчита тази грешка:

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 

Както показва горното съобщение за грешка при компилацията, дори несинтетичният метод на вложения клас е недостъпен по време на компилация, тъй като вложеният клас има частен обхват. В статията си Java Insecurities: Отчитане на тънкостите, които могат да компрометират кода, Чарли Лай обсъжда потенциални ситуации, в които тези въведени от компилатора промени представляват уязвимости в сигурността. Faisal Feroz отива по-далеч и заявява, в публикацията „Как да напиша защитен Java код“ „Не използвайте вътрешни класове“ (вж. Вложени, вътрешни, членски и най-високи класове за подробности относно вътрешните класове като подмножество на вложени класове) .

Много от нас могат да продължат дълго време в разработката на Java, без да се нуждаят от значително разбиране на синтетичните методи. Има обаче ситуации, когато осъзнаването на това е важно. Освен проблемите със сигурността, свързани с тях, трябва да знаете и какви са те, когато четете следи от стека. Метод имена като access$100, access$200, access$300, access$400, access$500, access$600, и access$1000в следата на стека отразяват синтетични методи, генерирани от компилатора.

Оригинална публикация на разположение на //marxsoftware.blogspot.com/

.

Тази история „Синтетичните методи на Java“ първоначално е публикувана от JavaWorld.