Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Java guarantee the persistence of the reference to the outer class? How is the following trace possible?

Question

When an inner class (N.B.: non static) is instantiated, it will get a reference to the outer class (upon its construction). Which guarantees apply in this case (if any) regarding the persistence of this reference?

Long explanation (trying to give a sense to the question)

Consider this simple code:

public class OuterInnerExample {

    private int mInt;

    public void startJob() {
        InnerRunnable r = new InnerRunnable();
        Thread t = new Thread(r);
        t.start();
    }

    private class InnerRunnable implements Runnable {
        public void run() {
            int localInt = mInt;
            System.out.println(localInt);
        }
    }
}

After compilation, if we decompile the class files (I used jad), we can observe that InnerRunnable has a this$0 member which is the reference to OuterInnerExample object.

This reference is set in the compiler-synthesized constructor of InnerRunnable, before calling super() (so this avoids the situation in which this$0 may be null when the object is used before its construction is completed, i.e., through the base class).

When InnerRunnable wants to access mInt, it will use a static getter synthesized by the compiler, that takes a reference to OuterInnerExample as a parameter (this is going to be this$0). This getter is the access$100 method.

For reference, the decompiled Java can be found here: http://pastebin.com/gr8GB03t.

Now, the whole problem is that I have observed (and cannot explain), a stack trace similar to the following:

FATAL EXCEPTION: main
java.lang.NullPointerException
    at <package.name>.OuterInnerExample.access$100(<line where "class OuterInnerExample" is>
    at <package.name>.OuterInnerExample$InnerRunnable.run(<line where "public void run()" is>)

which makes me think that this$0 must have been null at that point.

like image 526
Vincenzo Pii Avatar asked Nov 11 '22 18:11

Vincenzo Pii


1 Answers

To answer your question: as the JLS 8.1.3 states:

When an inner class (whose declaration does not occur in a static context) refers to an instance variable that is a member of a lexically enclosing class, the variable of the corresponding lexically enclosing instance is used.

Read JLS 17.4.5 about the happens-before relations:

The default initialization of any object happens-before any other actions (other than default-writes) of a program.

Default initialization here means the assignment of default values to fields.

Since the field private int mInt is primitive it can't be null so you must look elsewhere.

My guess is that you are using some custom compiler since this part:

private OuterInnerExample$InnerRunnable(OuterInnerExample outerinnerexample)
{
    this$0 = outerinnerexample;
    super();
}

leads to a compile-time error. You have to call super() in the first line of a constructor.

I actually copy-pasted your original code and used java 1.7 to execute it and it worked without problems so we are missing context here.

like image 129
Adam Arold Avatar answered Nov 15 '22 02:11

Adam Arold