Recently one of my colleague came to me with interesting question about builder pattern he saw in Effectiva Java I borrowed him. He implemented the pattern in the same way like Joshua Bloch in his book but no matter what, Eclipse displayed weird warning message about synthetic accessor. I have explained him theory behind synthetic classes but I was not quite sure why Eclipse allows to raise a compiler warning in such cases. In this post I would like to touch that topic and find reasons why Eclipse does that.
The source code of mentioned builder looks like following:
public class NutritionFacts { private final int servingSize; private final int servings; private final int calories; private final int fat; private final int sodium; private final int carbohydrate; public static class Builder { private int servingSize; private int servings; private int calories; private int fat; private int sodium; private int carbohydrate; public Builder servingSize(int servingSize) { this.servingSize = servingSize; return this; } public Builder servings(int servings) { this.servings = servings; return this; } public Builder calories(int calories) { this.calories = calories; return this; } public Builder fat(int fat) { this.fat = fat; return this; } public Builder sodium(int sodium) { this.sodium = sodium; return this; } public Builder carbohydrate(int carbohydrate) { this.carbohydrate = carbohydrate; return this; } public NutritionFacts build() { return new NutritionFacts(this); } } private NutritionFacts(Builder builder) { this.servingSize = builder.servingSize; this.servings = builder.servings; this.calories = builder.calories; this.fat = builder.fat; this.sodium = builder.sodium; this.carbohydrate = builder.carbohydrate; } }
This is most typical builder implementation. Immutable object NutritionFacts
is created by invoking its private constructor from builder method and passing builder instance as parameter. So its kind of copy constructor which copies data from builder to the created instance, that can’t be modified further.
Problem
If you change compiler severity level from ignore (default) to warning for:
Access to a non-accessible member of an enclosing type
in Eclipse IDE (Preferences->Java->Compiler->Errors/Warnings->Code Style
), you will see following warnings for builder above:
Access to enclosing constructor is emulated by a synthetic accessor method
(line 26)
Read access to enclosing field is emulated by a synthetic accessor method
(lines 31 – 36)
Syntethic constructs
Lets remind why java compiler must generate synthetic accessor methods. As you certainly know, private fields/methods are not visibile outside class. The same principle applies to nested classes as well, because on JVM level, there are no nested classes. Concept of nested classes is known only to Java language at source code level. Bytecode has no support for them. After compilation, they end up as standalone classes in their own files. Therefore on JVM, private fields/methods of enclosing class are not accessible for nested classes and enclosing class can’t access private stuff of nested classes (no matter if they are static or inner). However, Java supports access private fields/methods of nested class according JLS. So there are two opposite sides, Java language which syntax supports that and JVM which refuses to execute. For that reason java compiler must generate package-private synthetic methods, using which private stuff of enclosing class can be accessed from nested classes and vice versa, without any need to change the source code. Lets look how the generated synthetic constructor looks like:
/*synthetic*/ NutritionFacts(NutritionFacts.Builder x0, NutritionFacts$1 x1) { this(x0); }
So generated synthetic package-private constructor calls the original private constructor. But how the new synthethic constructor gets called? That’s again work of the java compiler, which modifies content of build() method inside builder to following form:
public NutritionFacts build() { return new NutritionFacts(this, null); }
As second constructor parameter is useless, compiler can safely putt null value in it. Similar generation is done also for private builder fields, that are read within private NutritionFacts constructor. Synthetic accessor methods look following:
/*synthetic*/ static int access$600(NutritionFacts$Builder x0) { return x0.carbohydrate; } /*synthetic*/ static int access$500(NutritionFacts$Builder x0) { return x0.sodium; } /*synthetic*/ static int access$400(NutritionFacts$Builder x0) { return x0.fat; } /*synthetic*/ static int access$300(NutritionFacts$Builder x0) { return x0.calories; } /*synthetic*/ static int access$200(NutritionFacts$Builder x0) { return x0.servings; } /*synthetic*/ static int access$100(NutritionFacts$Builder x0) { return x0.servingSize; }
Of course, java compiler needs to also modify private constructor that must use generated synthetic methods to gain access to the private fields of builder:
private NutritionFacts(NutritionFacts$Builder builder) { super(); this.servingSize = NutritionFacts.Builder.access$100(builder); this.servings = NutritionFacts.Builder.access$200(builder); this.calories = NutritionFacts.Builder.access$300(builder); this.fat = NutritionFacts.Builder.access$400(builder); this.sodium = NutritionFacts.Builder.access$500(builder); this.carbohydrate = NutritionFacts.Builder.access$600(builder); }
If you are intersted how I get to these generated stuff above, please look at Sundararajan’s post that explain some hidden options of java compiler. Simply you need to invoke compiler with -XD-printflat
option:
javac -XD-printflat NutritionFacts.java -d generated-src
With this command, java compiler generates source code which already passed some internal transformation (synthetic methods, assertion etc.) into generated-src directory. Note that you must create this directory before you run compiler.
Possible warning reasons
So now, when you mastered basics about synthetic constructs, lets back to the eclipse warnings. What are the possible unwanted implications of using synthetic constructs and why eclipse probably provides such alerts to be shown?
- Performance penalty – almost negligible on modern JVM if you look at syntetic code that compiler generated
- Security breach – generated package-private synthetic constructor can be accessed via reflection and thus create an instance of class, which was not intended to be created from the outside on source code level. You have to take this into account when you model your class a you rely on synthetic accessors
Conclusion
On the builder example I tried to demonstrate how java compiler generates synthetic constructs. Fortunately, Eclipse doesn’t enforce such warnings and it ignores them by default, until you state otherwise. Personally, I would continue to take advantage of synthetic methods for nested classes as they lower number of code that needs to be written otherwise. Second reason is that you maintain private stuff private and non-accessible without using reflection.