Abstract: Java 22 preview allows us to write code before the call to super(). There are restrictions. We are not allowed to refer to "this" in any way. We thus cannot set fields before calling the superclass constructor. But what if the superclass constructor calls our methods? In this newsletter we explore a workaround using ThreadLocals.
Welcome to the 316th edition of The Java(tm) Specialists' Newsletter, written tediously by a real suffering human, typing away on his keyboard into vim. They say that in the near future, 90%+ of content on "the web" will be AI generated. Are you looking forward to that day? I know I'm not. Thus for the forseeable future, you can be assured that The Java(tm) Specialists' Newsletter is handwritten by your favourite Java Champion :-) Obviously I use AI every day for a whole slew of tasks, but this, no this for sure, is coming from my heart and brain. [Both my English teacher and ChatGPT would cringe at that previous sentence. In fact, I asked ChatGPT what it thought about it and tried to help fix it with this - *yawn* - "I rely on AI daily for a variety of tasks, from organizing my schedule to analyzing data. Despite that, the words I share now genuinely reflect my personal thoughts and feelings." I'm not looking forward to this new world without texture and colour.]
By the way, Google has instituted stricter rules for folks sending thousands of emails. We have set up all the necessary authentication so that it should still work, but most of the subscribers on our list have GMail email addresses. Thus you might still not get my newsletter. In that case, I would recommend whitelisting my email address or even signing up with another provider.
javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.
In 2002, I wrote about an interesting phenomenon regarding uninitialized fields in subclasses when we call overridden methods from superclasses. For example, here is our Insect:
public abstract class Insect { public Insect() { System.out.println("Inside Insect() Constructor"); printDetails(); } public void printDetails() { System.out.println("Just an insect"); } }
With our subclass Beetle that prints information about its legs:
public class Beetle extends Insect { private final int legs; public Beetle(int legs) { System.out.println("Inside Beetle() Constructor"); this.legs = legs; } public void printDetails() { System.out.println("The beetle has " + legs + " legs"); if (legs < 6) { System.out.println("Ouch"); } } }
Our test code creates two bugs:
public class BeetleTest { public static void main(String[] args) { Insect sad_bug = new Beetle(5); // lost one leg in an // argument with his wife Insect happy_bug = new Beetle(6); // the wife bug ;-) } }
If we run the code, we see some interesting results:
Inside Insect() Constructor The beetle has 0 legs Ouch Inside Beetle() Constructor Inside Insect() Constructor The beetle has 0 legs Ouch Inside Beetle() Constructor
What happened to those legs? Well, the Insect class is calling the printDetails() method before the Beetle() constructor has been called. This should not be news for experienced Java programmers.
What is interesting though is how the classes were compiled prior to Java 1.4 in regards to inner classes. Take, for example, this NestedBug class.
public class NestedBug { private Integer wings = new Integer(2); public NestedBug() { new ComplexBug(); } private class ComplexBug extends Insect { public void printDetails() { System.out.println(wings); } } public static void main(String[] args) { new NestedBug(); } }
If we take the JDK 1.8 and compile the code with
javac -source 1.3 -target 1.3 *.java
and then
run the NestedBug, we see the following:
Inside Insect() Constructor Exception in thread "main" java.lang.NullPointerException at NestedBug.access$100(NestedBug.java:1) at NestedBug$ComplexBug.printDetails(NestedBug.java:10) at Insect.<init>(Insect.java:4) at NestedBug$ComplexBug.<init>(NestedBug.java:8) at NestedBug$ComplexBug.<init>(NestedBug.java:8) at NestedBug.<init>(NestedBug.java:5) at NestedBug.main(NestedBug.java:15)
If we disassemble the compiled Java 1.3 bytecode, we see that
the first call in the constructor is the call to "super()",
with the bytecode instruction invokespecial
:
private NestedBug$ComplexBug(NestedBug); Code: 0: aload_0 1: invokespecial #2 // Method Insect."<init>":()V 4: aload_0 5: aload_1 6: putfield #3 // Field this$0:LNestedBug; 9: return
Instruction 6 above is where we assign the pointer to the
outer object this$0
. Since this is still
null
when the
printDetails()
is called, we see a
NullPointerException
when the wings
field is printed, because the pointer to the outer object
this$0
is still uninitialized. This compiled
code was changed in Java 1.4, where the pointer is initialized
before the invokespecial
call to
super()
.
private NestedBug$ComplexBug(NestedBug); Code: 0: aload_0 1: aload_1 2: putfield #2 // Field this$0:LNestedBug; 5: aload_0 6: invokespecial #3 // Method Insect."<init>":()V 9: return
We see thus that the generated code was changed in Java 1.4
to allow a field to be set before the call to
super()
. However, this is not something we could
do in our code, and thus the Beetle code still has the same
behaviour even in Java 22.
The Swift Programming Language solves this nicely by allowing us to set the fields before calling the superclass constructor, like this:
class Insect { init() { print("Inside Insect() Constructor") printDetails() } func printDetails() { print("Just an insect") } } class Beetle: Insect { private let legs: Int init(legs: Int) { self.legs = legs // setting our field before super.init() super.init() print("Inside Beetle() Constructor") } override func printDetails() { print("The beetle has \(legs) legs") if legs < 6 { print("Ouch") } } } // lost one leg in an argument with his wife let sadBug = Beetle(legs: 5) // the wife bug ;-) let happyBug = Beetle(legs: 6)
This time, the output is 5 and 6 legs respectively:
Inside Insect() Constructor The beetle has 5 legs Ouch Inside Beetle() Constructor Inside Insect() Constructor The beetle has 6 legs Inside Beetle() Constructor
When I saw JEP 447, I thought - "ah, at long last they have
addressed this issue in Java." However, we cannot refer to
this
or any of the fields before the
super()
call. Thus we can add code before
super()
, but we cannot set fields or refer to
this
in any way:
public class BeetleJuice extends Insect { private final int legs; public BeetleJuice(int legs) { System.out.println("Going to call super() now"); System.out.println("legs = " + legs); // this.legs = legs; // not allowed super(); System.out.println("Inside Beetle() Constructor"); this.legs = legs; } public void printDetails() { System.out.println("The beetle has " + legs + " legs"); if (legs < 6) { System.out.println("Ouch"); } } }
Or can we? Well, this wouldn't be called The Java(tm) Specialists' Newsletter if we didn't
figure out how ... In the past, we had a trick that we could
use if the superclass constructor had at least one parameter.
We would call a static method as part of the
super()
call, like so super(saveTemp(legs))
,
and this would temporarily store the value in a
ThreadLocal
. However, this would only
work when we were able to pass the result of the
saveTemp()
method to the super()
call. If that didn't have any parameters, we were out of
luck.
Here is how we could temporarily store the legs field for
our printDetails()
method:
public class BeetleBypass extends Insect { private static final ThreadLocal<Integer> TEMP_LEGS = new ThreadLocal<>(); private final int legs; public BeetleBypass(int legs) { saveLegsTemp(legs); super(); System.out.println("Inside Beetle() Constructor"); this.legs = legs; TEMP_LEGS.remove(); } private static void saveLegsTemp(int legs) { TEMP_LEGS.set(legs); } public void printDetails() { System.out.println(STR."The beetle has \{legs()} legs"); if (legs() < 6) { System.out.println("Ouch"); } } private int legs() { if (TEMP_LEGS.get() != null) return TEMP_LEGS.get(); return legs; } }
When we run our previous test code, we see:
Inside Insect() Constructor The beetle has 5 legs Ouch Inside Beetle() Constructor Inside Insect() Constructor The beetle has 6 legs Inside Beetle() Constructor
Yes, I am aware that this is not what the intention behind JEP 447 was, and better would be to not call a non-final method from a constructor. However, such weird code exists unfortunately.
Kind regards
Heinz
We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)
We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.