方法局部内部类 vs 内部类

44
下面的代码产生输出“middle”。有人能详细解释一下这是如何发生的吗?
这是因为在go() 方法中创建了A类的实例之后,"inner"版本的A类声明出现了。
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");
        }
    }
}

1
尝试将内部A重命名为B,您将获得outer输出。这应该会给你一些提示。 - Maroun
1
@TimBiegeleisen,我正在经历一个难以选择特定答案的时期,因为你的许多答案都是正确且切中要点的。 - Ram Patra
1
在Eclipse IDE中,你会收到一个警告 The type A is never used locally,这意味着在方法 go 中定义的局部类 A 从未被使用。 - Not a bug
new A() 将返回与其最接近的类型实例(在同一代码块、同一方法、同一类中...)。当然,如果它们在同一代码块/方法中,则该类必须在新实例语句之前声明。对于您的情况,如果您有一个包名。您可以使用 "new packagename.A().m()" 来表示 "outer"。 - Mr Phuc 87
1
@KisHanSarsecHaGajjar 正在解决一些来自书本的练习,结果遇到了这个问题。 - Ram Patra
显示剩余5条评论
6个回答

38

我猜您希望调用本地类方法。这没有发生,因为您在本地类作用域之外使用new A()。所以,它访问了范围内下一个更接近的候选项,也就是内部类。JLS §6.3中写道:

紧挨着块(§14.2)的局部类声明的范围是其余的直接封闭块,包括自己的类声明。

因此,在方法的第一行中使用new A()将不会访问出现在它后面的本地类。如果您将类声明移到那之前,则会获得预期的输出。

另请参见JLS §14.3,其中包含类似的示例。


@Radiodef 嗯,缺少什么? - Rohit Jain
1
@RohitJain 你能告诉我为什么在 new A().m() 前声明类会将其带入作用域并打印 inner 吗? - Ram Patra
4
你为什么写那么多感叹号? - Jean-François Savard
2
@Ramswaroop,你的问题的答案可能只是Java架构师选择让本地类表现出这种方式。 - Tim Biegeleisen
1
@Ramswaroop 我想我引用的JLS部分,关于范围的说明已经解释得很清楚了。 - Rohit Jain
显示剩余6条评论

16

由于代码顺序的原因,您得到了输出"middle"。由于方法作用域中的class A出现在您调用new A(),因此输出为"middle"。如果您按以下顺序交换顺序,您将获得输出"inner":

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

输出:

inner

实例化 A类 的优先顺序,由高到低依次为:

  1. 代码块
  2. 方法

请查看官方Java语言规范中关于内部类的讨论以获取更多信息。


3
这也是OP猜测的。但为什么呢?请支持你的答案。 - Erwin Bolwidt
1
然后找到证明它的JLS参考 - 目前只是猜测。 - Boris the Spider
1
@Tim Biegeleisen,你能告诉我为什么在new A().m()之前声明类会将其带入作用域并打印inner吗? - Ram Patra
1
正如@Radiodef在他的答案中指出的那样,一旦你在一个块中定义了局部类A,该类的作用域就是该定义之后的块的剩余部分,而不是之前的部分。至于为什么Java架构师决定这样做,那是另一个问题。 - Tim Biegeleisen
@TimBiegeleisen其实我希望这个问题也能包含在答案里,因为这正是我想知道的内容。 - Ram Patra

7

内部类inner没有被打印出来的原因在于(6.3):

一个块语句内局部类的作用域为该块语句,包括它自身的类声明。

(在方法中声明的类称为局部类。)

所以A不能引用局部类,因为表达式new A()在其声明之前发生。换句话说,局部类的作用域与局部变量类似。

middle打印出来而不是outer的原因在于,内部类A shadowed(屏蔽) 了顶级类A (6.4.1):

声明了类型n的声明d会屏蔽掉任何其他同名类型n的声明,这些声明在d作用域内

这意味着在MethodLocalVSInner的任何地方,未经限定的A必须指代内部类。

如果您熟悉成员变量的遮蔽,例如:

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

基本上,类声明也是同样的情况。

4

案例一:

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

在这种情况下,如果您在本地类的范围之外运行方法,则会打印middle
第二种情况:
void go() {
        class A {
            void m() {
                System.out.println("inner");
            }
        }
        new A().m();
    }

在这种情况下,它将打印inner,因为类现在处于作用域中。

2

在方法中:

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

当方法开始执行时,首先会执行第一行:new A().m();

因为内部类已经在作用域内,所以会创建该类的对象,并且调用m方法,而不是调用局部方法类中的m方法,因为它仍然不在作用域内。这就是为什么你会得到middle作为输出结果。

但是如果你将方法改为:

 void go() {

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

现在您的本地方法类将在作用域内,并且具有更高的优先级,因此您现在将获得输出inner


1
这与类加载无关。当方法第二次执行时,即使本地类A已经被加载,它仍将生成相同的输出。 - Erwin Bolwidt
这是因为作用域的偏好。通常是方法先于类。@ErwinBolwidt,我已经更改了。 - Prashant

1
你正在使用一个MethodLocalVSInner实例调用go方法。
go方法内部,你创建了一个A()实例。因为你没有明确导入外部A类,而且直接内部类在方法调用语句之后,JVM会选择执行MethodLocalVSInner类级别下的内部类A来执行go方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接