I am one of those curious souls who scrolls through all the functions of the Javadoc, and above section in the Javadoc caught my eye.
I am sure almost all Java Developers have used Java’s Reflection API then most of you would have seen this Member.isSynthetic() (if you haven’t, don’t worry you will learn something interesting).
So as the doc says simply “member introduced by the compiler”, So does it mean compiler changes our code? After searching around, if we go by JLS(Java Language Specification)-13.1 synthetic is :
A construct emitted by a Java compiler must be marked as synthetic if it does not correspond to a construct declared explicitly or implicitly in source code, unless the emitted construct is a class initialization method
These methods are created by the Java compiler as a result of type erasure or when the nested private class’s private methods or variables are accessed.
So in next few steps, I will show you some of the cases where Synthetic Methods, Classes or Member variables are created (below may not be the comprehensive list do let me know if others are present, Java Code has been uploaded on Github here):
Let’s go through below example. Our SyntheticAnonymousTest class generates
- SyntheticAnonymousTest
import lombok.Data;
@Data
public class SyntheticAnonymousTest
{
interface Callback<T>
{
String call(T value);
}public static void main(String[] args)
{
System.out.println(new Callback<String>()
{
@Override
public String call(String value)
{
return ("Synthetic Anonymous " + value);
}
}.call("Test"));
}
}
If you compile the above java code, it generates below synthetic SyntheticAnonymousTest$1.class, by disassembling you see the following code :
SyntheticAnonymousTest$1.class
SyntheticAnonymousTest$Callback.class
SyntheticAnonymousTest.class$ javap SyntheticAnonymousTest$1.class
class com.mytest.SyntheticAnonymousTest$1 implements com.mytest.SyntheticAnonymousTest$Callback<java.lang.String> {
com.mytest.SyntheticAnonymousTest$1();
public java.lang.String call(java.lang.String);
public java.lang.String call(java.lang.Object);
}$ javap SyntheticAnonymousTest$Callback.class
interface com.mytest.SyntheticAnonymousTest$Callback<T> {
public abstract java.lang.String call(T);
}$ javap SyntheticAnonymousTest.class
public class com.mytest.SyntheticAnonymousTest {
public static void main(java.lang.String[]);
public boolean equals(java.lang.Object);
protected boolean canEqual(java.lang.Object);
public int hashCode();
public java.lang.String toString();
public com.mytest.SyntheticAnonymousTest();
}
So the above experiment generates one class of synthetic code. Now imagine if your code has hundreds of these types of classes, So the next question which arises is
Do they have performance Implications ?
The answer is Yes/No.
As such it does have very minuscule amount of penalty i.e additional methods and its related invocation costs, which is mostly irrelevant and trivial to most of the use cases in Enterprise Grade Applications(As we have luxury of having enough memory on servers i.e above 4GB at least) but that’s not the case with Java code on Android-based phones.
On an Android Phone where you are tasked with optimal use of memory, these extra methods might add up to thousands of unwanted methods as we have these nested/anonymous classes everywhere in our code(including the libraries).
If you check the Android Developer Blog you will infer the following :
- Android app has 64K methods limit (without multidex). So it is not wise to waste a few thousand methods as unwanted synthetic methods.
- The performance of Accessors is slower than direct field access.
Since the above method limit applicable to Android it has greater impact on mobile based Java Applications but not on Enterprise grade Java based applications as there is no limit of memory and cost of upgrade is low.
Furthermore, I have tried to compile a comprehensive and detailed explanation in my below examples that generate Synthetic members/classes, etc, have a quick look :
2. SyntheticFunctionTest
import java.util.function.Function;
import lombok.Data;
@Data
public class SyntheticFunctionTest
{
public static final Function<String, String> returnString = new Function<String, String>()
{
public String apply(String value)
{
return "Synthetic Function " + value;
}
}; public static void main(String[] args)
{
System.out.println(returnString.apply("Test"));
}
}
If you compile the above java code, it generates below synthetic SyntheticFunctionTest$1.class, by disassembling you see the following code :
SyntheticFunctionTest$1.class
SyntheticFunctionTest.class$ javap SyntheticFunctionTest$1.class
class com.mytest.SyntheticFunctionTest$1 implements java.util.function.Function<java.lang.String, java.lang.String> {
com.mytest.SyntheticFunctionTest$1();
public java.lang.String apply(java.lang.String);
public java.lang.Object apply(java.lang.Object);
}$ javap SyntheticFunctionTest.class
public class com.mytest.SyntheticFunctionTest {
public static final java.util.function.Function<java.lang.String, java.lang.String> returnString;
static {};
public static void main(java.lang.String[]);
public boolean equals(java.lang.Object);
protected boolean canEqual(java.lang.Object);
public int hashCode();
public java.lang.String toString();
public com.mytest.SyntheticFunctionTest();
}
3. SyntheticGenericTest
import java.util.Date;
import lombok.Data;
@Data
public class SyntheticGenericTest<A extends CharSequence, B extends Date>
{
public static void main(String[] args)
{
NestedClass<String, Date> nestedClass = new NestedClass<>();
System.out.println(nestedClass.getData());
} private static final class NestedClass<A, B>
{
private A testA;
private B testB; private Object getData()
{
return getTestString() + "-" + getTestDate();
} public A getTestString()
{
return testA;
} public B getTestDate()
{
return testB;
}
}
}
If you compile the above java code, it generates below synthetic access$1 method generated, by disassembling you see the following code :
SyntheticGenericTest$NestedClass.class
SyntheticGenericTest.class$ javap SyntheticGenericTest$NestedClass.class
final class com.mytest.SyntheticGenericTest$NestedClass<A, B> {
public A getTestString();
public B getTestDate();
com.mytest.SyntheticGenericTest$NestedClass(com.mytest.SyntheticGenericTest$NestedClass);
static java.lang.Object access$1(com.mytest.SyntheticGenericTest$NestedClass);
}$ javap SyntheticGenericTest.class
public class com.mytest.SyntheticGenericTest<A extends java.lang.CharSequence, B extends java.util.Date> {
public static void main(java.lang.String[]);
public boolean equals(java.lang.Object);
protected boolean canEqual(java.lang.Object);
public int hashCode();
public java.lang.String toString();
public com.mytest.SyntheticGenericTest();
}
4. SyntheticInnerTest
import lombok.Data;
@Data
public class SyntheticInnerTest
{
public static void main(String[] args)
{
SyntheticInnerTest syntheticTest = new SyntheticInnerTest();
NestedClass nestedClass = syntheticTest.new NestedClass();
System.out.println(nestedClass.getResult());
} private static String displayText(String test)
{
return test;
} private final class NestedClass
{
private final String testString = "Synthetic Nested String : "; private String getResult()
{
return SyntheticInnerTest.displayText(testString);
}
}
}
If you compile the above java code, it generates below synthetic this.$0 access$0 and access$1 methods generated but in both parent and child classes, by disassembling you see the following code :
SyntheticInnerTest.class
SyntheticInnerTest$NestedClass.class$ javap SyntheticInnerTest$NestedClass.class
final class com.mytest.SyntheticInnerTest$NestedClass {
final com.mytest.SyntheticInnerTest this$0;
com.mytest.SyntheticInnerTest$NestedClass(com.mytest.SyntheticInnerTest, com.mytest.SyntheticInnerTest$NestedClass);
static java.lang.String access$1(com.mytest.SyntheticInnerTest$NestedClass);
}$ javap SyntheticInnerTest.class
public class com.mytest.SyntheticInnerTest {
public static void main(java.lang.String[]);
public boolean equals(java.lang.Object);
protected boolean canEqual(java.lang.Object);
public int hashCode();
public java.lang.String toString();
public com.mytest.SyntheticInnerTest();
static java.lang.String access$0(java.lang.String);
}
5. SyntheticLambdaTest
import lombok.Data;
@Data
public class SyntheticLambdaTest
{
interface Callback<T>
{
String call(T value);
} public static void main(String[] args)
{
System.out.println(((Callback<String>) value -> "Synthetic Lambda " + value).call("Test"));
}
}
If you compile the above java code, you will notice no synthetic methods are generated after disassembling :
javap SyntheticLambdaTest.class
javap SyntheticLambdaTest$Callback.class$ javap SyntheticLambdaTest.class
public class com.mytest.SyntheticLambdaTest {
public static void main(java.lang.String[]);
public boolean equals(java.lang.Object);
protected boolean canEqual(java.lang.Object);
public int hashCode();
public java.lang.String toString();
public com.mytest.SyntheticLambdaTest();
}$ javap SyntheticLambdaTest$Callback.class
interface com.mytest.SyntheticLambdaTest$Callback<T> {
public abstract java.lang.String call(T);
}
6. SyntheticMethodReferenceTest
import java.util.function.Supplier;
import lombok.Data;
@Data
public class SyntheticMethodReferenceTest
{
private static class Greeter
{
public static String sayHi()
{
return "Synthetic Hi!";
}
} public static void main(String[] args)
{
Supplier<String> supplierHi = Greeter::sayHi;
System.out.println(supplierHi.get());
}
}
If you compile the above java code, you will notice no synthetic methods are generated after disassembling :
SyntheticMethodReferenceTest$Greeter.class
SyntheticMethodReferenceTest.class$ javap SyntheticMethodReferenceTest$Greeter.class
class com.mytest.SyntheticMethodReferenceTest$Greeter {
public static java.lang.String sayHi();
}$ javap SyntheticMethodReferenceTest.class
public class com.mytest.SyntheticMethodReferenceTest {
public static void main(java.lang.String[]);
public boolean equals(java.lang.Object);
protected boolean canEqual(java.lang.Object);
public int hashCode();
public java.lang.String toString();
public com.mytest.SyntheticMethodReferenceTest();
}
7. SyntheticTest
import java.util.Date;
import lombok.Data;
@Data
public class SyntheticTest
{
public static void main(String[] args)
{
SyntheticTest syntheticTest = new SyntheticTest();
NestedClass nestedClass = syntheticTest.new NestedClass();
System.out.println(nestedClass.testString);
System.out.println(nestedClass.getTestString());
System.out.println(nestedClass.getData());
System.out.println(nestedClass.getData1());
} private final class NestedClass
{
private String testString = "Synthetic Nested Class : ";
private Date testDate = new Date(); private String getData()
{
return (testString + testDate);
} private String getData1()
{
return (getTestString() + getTestDate());
} public String getTestString()
{
return testString;
} public Date getTestDate()
{
return testDate;
}
}
}
If you compile the above java code, it does not generate any synthetic class but generates this.$0 , access$1 , access$2 and access$3 methods, by disassembling you see the following code classes generated:
SyntheticTest$NestedClass.class
SyntheticTest.class$ javap SyntheticTest$NestedClass.classfinal class com.mytest.SyntheticTest$NestedClass {
final com.mytest.SyntheticTest this$0;
public java.lang.String getTestString();
public java.util.Date getTestDate();
com.mytest.SyntheticTest$NestedClass(com.mytest.SyntheticTest, com.mytest.SyntheticTest$NestedClass);
static java.lang.String access$1(com.mytest.SyntheticTest$NestedClass);
static java.lang.String access$2(com.mytest.SyntheticTest$NestedClass);
static java.lang.String access$3(com.mytest.SyntheticTest$NestedClass);
}$ javap SyntheticTest.class
public class com.mytest.SyntheticTest {
public static void main(java.lang.String[]);
public boolean equals(java.lang.Object);
protected boolean canEqual(java.lang.Object);
public int hashCode();
public java.lang.String toString();
public com.mytest.SyntheticTest();
}
8. SyntheticAnnotationTest
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import lombok.Data;@Data
public class SyntheticAnnotationTest
{
@Retention(RetentionPolicy.SOURCE)
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.TYPE })
public @interface Private
{} @Private
static String test = "SyntheticAnnotationTest"; public static void main(String[] args)
{
System.out.println(test);
}
}
If you compile the above java code, it does not generate any synthetic code, by disassembling you see the following code classes generated:
SyntheticAnnotationTest.class
SyntheticAnnotationTest$Private.class$ javap SyntheticAnnotationTest.classpublic class com.mytest.SyntheticAnnotationTest {
static java.lang.String test;
static {};
public static void main(java.lang.String[]);
public boolean equals(java.lang.Object);
protected boolean canEqual(java.lang.Object);
public int hashCode();
public java.lang.String toString();
public com.mytest.SyntheticAnnotationTest();
}$ javap SyntheticAnnotationTest$Private.class
public interface com.mytest.SyntheticAnnotationTest$Private extends java.lang.annotation.Annotation {
}
For Reference: