8

Sorry for the title gore, I did not know how to describe the problem in one line. If you have suggestions, I'm open.

Suppose you have the following class:

public class SomeClass {
    // doesn't even need to be final, which is freaky
    Runnable memberRunnable = new Runnable() {
        public void run() {
            SomeOtherClass.someMethod(memberRunnable); // this works
        }
    }
    public void someMethod() {
        final Runnable varRunnable = new Runnable() {
            public void run() {
                SomeOtherClass.someMethod(varRunnable); // compiler error - "varRunnable" might not have been initialized
            }
        }
    }
}

Why is the memberRunnable able to access itself from inside run(), while varRunnable is not? AFAICS it's the exact same construct.

You can obviously use this instead, I know. I'm just wondering why the compiler makes a difference between the two cases, which seem identical. Also why it thinks varRunnable might not have been initialized, when it's obvious that it is at that point.

One could argue that, if Runnable was a class (it's an interface), it's constructor might be trying to call run(), thus actually running into a scenario where the reference is uninitialized. However, this should also be the case for memberRunnable, but that case works.

Funny thing, nothing changes if instead of Runnable you use a class, in which case the above scenario (constructor calling an overridden method) can actually happen. This means that, in that case, you can run into a "field not initialized" at runtime (haven't tried it though), which is rather dumb since the compiler should guard against that.

sepp2k
  • 363,768
  • 54
  • 674
  • 675
Felix
  • 88,392
  • 43
  • 149
  • 167

1 Answers1

9

Also why it thinks varRunnable might not have been initialized, when it's obvious that it is at that point.

No, the variable is not (in the general case) guaranteed to be initialized at that point.

Suppose, just for the sake of argument, that Runnable was an abstract class (rather than an interface) and that the constructor of Runnable called this.run(). Since construction of the Runnable occurs before the assignment, this would result in access of varRunnable before the assignment had occurred.

In other words, it would lead to access of an uninitialized local variable. Note that this is worse than accessing a field that has not yet been explicitly initialized, since local variables are not initialized to default values. It's so much worse in fact, that access of uninitialized local variables is forbidden, while access to fields that have not been explicitly initialized is allowed, as you just discovered. (Making the field final doesn't change this. Final fields also have default values, and they can in fact change (once) in the constructor.)

Source: I'm a javac developer.

aioobe
  • 413,195
  • 112
  • 811
  • 826
  • I pointed out this exact case in my question. It doesn't explain why `memberRunnable` **can** reference itself in the same way. – Felix May 08 '15 at 17:54
  • 1
    I address this in the last sentence. I'll try to clarify. – aioobe May 08 '15 at 17:54
  • @aioobe what if the member is declared `final`? This example still works, but it shouldn't, since it doesn't have a default value. Or does it? – Felix May 08 '15 at 17:56
  • 1
    @Felix, `final` fields do have the same default values as other fields. – aioobe May 08 '15 at 17:57
  • Sweet. If this were reddit I'd give you gold. I was not expecting such a question to be answered in 10 mins. – Felix May 08 '15 at 18:05
  • By the way there's a similar interesting case with lambda. I opened a [separate question](http://stackoverflow.com/q/30130148/4856258) for it. – Tagir Valeev May 08 '15 at 18:06