44

The below code produces the output middle. Can anyone explain in detail how this is happening?

Is it because the declaration of "inner" version of class A comes after the instance of class A is created in the go() method?

class A {
    void m() {
        System.out.println("outer");
    }
}

public class MethodLocalVSInner {
    public static void main(String[] args) {
        new MethodLocalVSInner().go();
    }

    void go() {
        new A().m();
        class A {
            void m() {
                System.out.println("inner");
            }
        }
    }

    class A {
        void m() {
            System.out.println("middle");
        }
    }
}
Ram Patra
  • 16,266
  • 13
  • 66
  • 81
  • 1
    Try to rename the *inner* class `A` to `B` and you'll get `outer` output. This should give you a hint. – Maroun Apr 14 '15 at 06:45
  • 1
    @TimBiegeleisen I am going through a hard time choosing one particular answer as many of your answers are correct and to the point. – Ram Patra Apr 14 '15 at 08:59
  • 1
    You will get a warning in eclipse IDE `The type A is never used locally`, That says local class `A` defined inside method `go` is never used. – Not a bug Apr 14 '15 at 09:13
  • I am curious to know for which requirement you are implementing this ? or its just a tricky question...!! – Not a bug Apr 14 '15 at 09:15
  • new A() will return instance of type that nearest (in same block, same method, same class...). Of course, if they are in same block/method, that class must be declared before new instance statement. In your case, if you have a package name. You can use "new packagename.A().m()" for "outer" – Mr Phuc 87 Apr 14 '15 at 10:15
  • I chose the answer of @RohitJain because of the exact references he gave in addition to his explanation. – Ram Patra Apr 14 '15 at 10:59
  • 1
    @KisHanSarsecHaGajjar Was solving some exercises from a book and I landed into this problem. – Ram Patra Apr 14 '15 at 12:01
  • @KisHanSarsecHaGajjar But you can get similar situations when designing some GUI applications in java/swing or while making android apps. Attaching event listeners use anonymous inner class significantly. – Ram Patra Apr 14 '15 at 12:12
  • Ideally, you will never face this situation if you use meaningful class names and proper design structure !! If you arrive at this situation at any point of time during implementation...stop coding...get one pen and paper...REDESIGN !!! – Not a bug Apr 14 '15 at 12:47
  • @KisHanSarsecHaGajjar This exact situation you may not but I was referring to anonymous inner class :) – Ram Patra Apr 14 '15 at 12:57

6 Answers6

38

I guess you expected the local class method to be invoked. That didn't happen, because you're using new A() outside the scope of local class. So, it accesses the next closer candidate in scope, that would be the inner class. From JLS §6.3:

The scope of a local class declaration immediately enclosed by a block (§14.2) is the rest of the immediately enclosing block, including its own class declaration.

Thus, new A() in the first line of method, will not access the local class appearing after it. If you move the class declaration before that, you'll get the expected output.

Also see JLS §14.3, which contains similar example.

Rohit Jain
  • 209,639
  • 45
  • 409
  • 525
  • @Radiodef Well, what is missing? – Rohit Jain Apr 14 '15 at 07:09
  • `If you move the class declaration before that, you'll get the expected output`...by hiding class `MethodLocalVSInner.A` !! PS : Not a downvoter :D – Not a bug Apr 14 '15 at 09:20
  • @KisHanSarsecHaGajjar You mean *shadowing*. ; ) *Shadowing* and *hiding* are different things. – Radiodef Apr 14 '15 at 09:49
  • @KisHanSarsecHaGajjar Isn't it implicit that local class will shadow the inner class? – Rohit Jain Apr 14 '15 at 10:06
  • @RohitJain I thought that will be a good explicit mention for readers who are less aware about inner class concepts ( or hiding/shadowing concepts ) !!! – Not a bug Apr 14 '15 at 11:35
  • @Radiodef Yup...its [shadowing](https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html#shadowing) !! My mistake !! – Not a bug Apr 14 '15 at 11:39
  • 1
    @RohitJain Can you tell me why declaring the class before `new A().m()` brings it to the scope and it prints `inner`. – Ram Patra Apr 14 '15 at 12:15
  • 4
    @KisHanSarsecHaGajjar Why do you write so many exclamation marks ? – Jean-François Savard Apr 14 '15 at 13:19
  • 2
    @Ramswaroop The answer to your question may simply be that the Java architects chose local classes to behave this way. – Tim Biegeleisen Apr 14 '15 at 14:25
  • @TimBiegeleisen Actually I wanted this to be included in the answer. Because this was what I wanted to know. I thought I may be missing something here but it seems local classes have been designed this way. – Ram Patra Apr 14 '15 at 19:01
  • 1
    @Ramswaroop I guess the JLS section which I quoted, about scope explains that pretty well. – Rohit Jain Apr 15 '15 at 05:34
16

You are getting the output "middle" because of the order in which you have your code. Since the method-scoped class A occurs after your call to new A(), you are getting the output "middle". If you switch around the order as follows, you will get the output "inner":

void go() {
    class A {
        void m() {
            System.out.println("inner");
        }
    }
    new A().m();
}

Output:

inner

The order of precedence for instantiating class A, from high to low, is:

  1. block
  2. method
  3. class
  4. package

Please have a look at the official Java Language Specification discussing inner classes for more information.

Premraj
  • 72,055
  • 26
  • 237
  • 180
Tim Biegeleisen
  • 502,043
  • 27
  • 286
  • 360
  • 3
    That's what the OP also guessed. But why is that? Please back up your answer. – Erwin Bolwidt Apr 14 '15 at 06:52
  • 1
    Then find the JLS reference that proves it - currently it's just speculation. – Boris the Spider Apr 14 '15 at 08:02
  • 1
    @Tim Biegeleisen Can you tell me why declaring the class before new A().m() brings it to the scope and it prints inner? – Ram Patra Apr 14 '15 at 13:05
  • 1
    As @Radiodef pointed out in his answer, as soon as you define the local class `A` in a block, the scope for that class is the remainder of the block after, but not before, that definition. As to why the Java architects decided to do it this way, that is another question. – Tim Biegeleisen Apr 14 '15 at 15:13
  • @TimBiegeleisen Actually I wanted this to be included in the answer. Because this was what I wanted to know. – Ram Patra Apr 14 '15 at 18:58
7

The reason inner doesn't get printed is (6.3):

The scope of a local class declaration immediately enclosed by a block is the rest of the immediately enclosing block, including its own class declaration.

(A class declared inside a method is called a local class.)

So A can't refer to the local class, because the expression new A() happens before its declaration. In other words, local classes have similar scope to local variables.

The reason middle gets printed instead of outer is that the inner class A shadows the top-level class A (6.4.1):

A declaration d of a type named n shadows the declarations of any other types named n that are in scope […] of d.

This means that anywhere in the body of MethodLocalVSInner, the unqualified A must refer to the inner class.

If you are familiar with shadowing of member variables e.g.:

class Example {
    int x;
    void setX(int x) {
        //       ┌ 'x' refers to the local method parameter
        this.x = x;
    }
}

Essentially the same thing is going on with class declarations.

Radiodef
  • 37,180
  • 14
  • 90
  • 125
4

Case 1:

void go() {
        new A().m();
        class A {
            void m() {
                System.out.println("inner");
            }
        }
    }

In this case if you running your method outside the scope of local class. That why it will print middle

Case 2:

void go() {
        class A {
            void m() {
                System.out.println("inner");
            }
        }
        new A().m();
    }

In this case it will print inner becase class is now in the scope.

Sumit Singh
  • 15,743
  • 6
  • 59
  • 89
2

in method :

 void go() {
    new A().m();
    class A {
        void m() {
            System.out.println("inner");
        }
    }
}

when method is started executing, first line will be executed new A().m();

and because inner class is already in scope so object for that class will be created and m method will be called for inner class not for local method class because its still not in scope. that is why you are getting middle as output.

but if you change your method as :

 void go() {

    class A {
        void m() {
            System.out.println("inner");
        }
    }
   new A().m();
}

Your local method class will now be in scope and will have higher preference so you will get output now inner.

Prashant
  • 2,556
  • 2
  • 20
  • 26
  • 1
    This is not related to class loading. When the method is executed a second time, it will still generate the same output, even though the local class A is already loaded. – Erwin Bolwidt Apr 14 '15 at 06:53
  • it because of scope preference also. which goes like method then class. @ErwinBolwidt i have change it now. – Prashant Apr 14 '15 at 06:54
1

You are calling go method using an instance of MethodLocalVSInner

Inside the go method you are creating an instance of A() here since you are not explicitly import the outer A class and immediate inner class is after the method calling statement, JVM is picking the inner class A which is in the class level of MethodLocalVSInner and execute the go method inside that

Failed Scientist
  • 1,977
  • 3
  • 29
  • 48