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

062The Link to the Outer Class

Author: Dr. Heinz M. KabutzDate: 2002-12-27Java Version: 1.4Category: Language
 

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.

The Link to the Outer Class

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:

  • Is this$0 a new type of keyword you did not know about?
  • What happens when your inner class already contains a variable this$0?
  • Is calling a method from a constructor a good idea in the first place?
  • When, if ever, are inner classes a good idea?

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

 

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...