Running on Java 22-ea+27-2262 (Preview)
Home of The JavaSpecialists' Newsletter

316Code Before super() - JEP 447

Author: Dr Heinz M. KabutzDate: 2024-03-30Java Version: 22-previewCategory: Language
 

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.

Code Before super() - JEP 447

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

 

Comments

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)

When you load these comments, you'll be connected to Disqus. Privacy Statement.

Related Articles

Browse the Newsletter Archive

About the Author

Heinz Kabutz Java Conference Speaker

Java Champion, author of the Javaspecialists Newsletter, conference speaking regular... About Heinz

Superpack '23

Superpack '23 Our entire Java Specialists Training in one huge bundle more...

Free Java Book

Dynamic Proxies in Java Book
Java Training

We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.

Java Consulting

We can help make your Java application run faster and trouble-shoot concurrency and performance bugs...