Abstract: Inner classes have magic handles to the outer object and provide synthetic methods to acces private data. This can result in some hard-to-understand bugs.
Welcome to the 62nd edition of The Java(tm) Specialists' Newsletter sent to 5450 Java Specialists in 91 countries.
Here I am, sitting in my shorts on a beautiful starry night on our balcony, listening to the Guinea Fowls in our trees, trying to churn out yet another newsletter to appeal to your cerebral impulses. Guinea Fowls make a very strange sound, which is extremely annoying to some, and music to others. Fortunately for me (and the Guinea Fowls), I like their squawking. From the sunburnt-southern-hemispherer to all eggnog-drinking-northern-hemispherers: Relax, the days are getting longer again :-)
Don't you just love those Out-Of-Office messages? Boy, am I going to get a lot of those with this newsletter...
javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.
Consider the following classes:
public abstract class Insect { public Insect() { System.out.println("Inside Insect() Constructor"); printDetails(); } public void printDetails() { System.out.println("Just an insect"); } } 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"); } } } public class BeetleTest { public static void main(String[] args) { Beetle sad_bug = new Beetle(5); // lost one leg in an // argument with his wife Beetle happy_bug = new Beetle(6); // the wife bug ;-) } }
Stop for a moment and think of what the effect would be of running BeetleTest. Don't read further until you have decided what would happen.
I hope you didn't peep :-) Here is the output:
Inside Insect() Constructor The beetle has 0 legs Ouch Inside Beetle() Constructor Inside Insect() Constructor The beetle has 0 legs Ouch Inside Beetle() Constructor
Yes, even though legs
was final
, we were able to
access it before it was initialised. What is more,
we are able to call the subclass' methods from the
constructor of the superclass, before the subclass
had been initialised! This should come as no surprise
to you, since by being subscribed to The Java(tm) Specialists' Newsletter you would
be classed as a Java Specialist. Yes? No ... ?
But, the plot thickens. Look at the following 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[] arguments) { new NestedBug(); } }
When we run this code, we get a NullPointerException:
Inside Insect() Constructor java.lang.NullPointerException at NestedBug.access$0(NestedBug.java:2) at NestedBug$ComplexBug.printDetails(NestedBug.java:8) at Insect.<init>(Insect.java:4) at NestedBug$ComplexBug.<init>(NestedBug.java:6) at NestedBug.<init>(NestedBug.java:4) at NestedBug.main(NestedBug.java:12) Exception in thread "main"
A friend of mine once had this problem, so my first thought was
that somehow, because wings
was null, we ended up
with a NullPointerException when printing it. That explanation
did not make sense, because calling toString()
on
a null pointer is supposed to just return null
. My
friend changed his code to the following:
public class NestedBug2 { private Integer wings = new Integer(2); public NestedBug2() { new ComplexBug(); } private class ComplexBug extends Insect { public void printDetails() { if (wings != null) { // line 8 System.out.println(wings); } } } public static void main(String[] arguments) { new NestedBug2(); } }
Sadly, this does not make the NullPointerException go away:
Inside Insect() Constructor java.lang.NullPointerException at NestedBug2.access$0(NestedBug2.java:2) at NestedBug2$ComplexBug.printDetails(NestedBug2.java:8) at Insect.<init>(Insect.java:4) at NestedBug2$ComplexBug.<init>(NestedBug2.java:6) at NestedBug2.<init>(NestedBug2.java:4) at NestedBug2.main(NestedBug2.java:14) Exception in thread "main"
But wait! The line with the NullPointerException is the
line that simply checks whether wings is null!?
Can the mere act of checking whether wings
is
null
itself cause a
NullPointerException? In this case it appears that it can!
Or is the actual mistake on line 2? What is this
access$0
method?
To understand this strange behaviour, we have to understand what the constructor of the inner class consists of. The easiest way of doing this is to use JAD and decompile the class file with the -noinner option:
jad -noinner NestedBug2$ComplexBug.class class NestedBug2$ComplexBug extends Insect { NestedBug2$ComplexBug(NestedBug2 nestedbug2) { this$0 = nestedbug2; } public void printDetails() { if (NestedBug2.access$0(this$0) != null) System.out.println(NestedBug2.access$0(this$0)); } private final NestedBug2 this$0; /* synthetic field */ }
We also need to look at NestedBug2.class:
jad -noinner NestedBug2.class public class NestedBug2 { public NestedBug2() { wings = new Integer(2); new NestedBug2$ComplexBug(this); } public static void main(String arguments[]) { new NestedBug2(); } static Integer access$0(NestedBug2 nestedbug2) { return nestedbug2.wings; } private Integer wings; }
Again, I must urge you to spend a few minutes thinking about
this. Step through what would happen in your head or scribble
on a piece of paper, but try to understand. When
printDetails()
is called by Insect>
,
the handle to outer class (this$0
) has not yet
been set in the ComplexBug
class, so that class
passes null
to the access$0
method, which of course causes a NullPointerException when it
tries to access nestedbug2.wings
as we would
expect.
I bet that many developers have run into this problem, but being under typical time pressure would have just coded around it until they got it working, without taking the time to think about what is happening.
This experience leads us to some questions:
this$0
a new type of keyword you did not
know about?I will leave you with these questions to answer yourself over the festive season. For those of you who are actually working, I hope this newsletter has cheered up your workday and that you will be motivated to seek new nuggets of information inside the fascinating Java language.
All the best for the new year. May 2003 bring you new opportunities to learn and grow, not just in the material world :-)
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.