【问题标题】:Method local inner class vs inner class方法本地内部类与内部类
【发布时间】:2015-06-19 16:07:29
【问题描述】:

下面的代码产生输出middle。谁能详细解释这是怎么发生的?

是因为class A的“内部”版本的声明是在class A的实例在go()方法中创建之后出现的吗?

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");
        }
    }
}

【问题讨论】:

  • 尝试将 innerA 重命名为 B,您将得到 outer 输出。这应该会给你一个提示。
  • @TimBiegeleisen 我很难选择一个特定的答案,因为您的许多答案都是正确且中肯的。
  • 您将在 Eclipse IDE The type A is never used locally 中收到警告,表示从未使用方法 go 中定义的本地类 A
  • 我很想知道您正在实施这个要求是什么?或者它只是一个棘手的问题......!!
  • @KisHanSarsecHaGajjar 正在解决书中的一些练习,我遇到了这个问题。

标签: java inner-classes


【解决方案1】:

我猜你希望调用本地类方法。那没有发生,因为您在本地类范围之外使用new A()。因此,它访问范围内下一个更接近的候选对象,即内部类。来自JLS §6.3

由块(第 14.2 节)直接包围的局部类声明的范围是直接封闭块的其余部分,包括它自己的类声明。

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

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

【讨论】:

  • @Radiodef 那么,缺少什么?
  • @RohitJain 你能告诉我为什么在new A().m() 之前声明类会将其带入范围并打印inner
  • @KisHanSarsecHaGajjar 为什么要写这么多感叹号?
  • @Ramswaroop 您的问题的答案可能只是 Java 架构师选择了本地类来表现这种方式。
  • @Ramswaroop 我猜我引用的 JLS 部分关于范围解释得很好。
【解决方案2】:

由于您拥有代码的顺序,您将获得“中间”输出。由于方法范围的class A 发生在您调用new A() 之后,因此您将获得“中间”输出。如果您按如下方式切换顺序,您将获得输出“inner”:

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

输出:

inner

实例化class A的优先级从高到低依次为:

  1. 阻止
  2. 方法

更多信息请关注官方Java Language Specification discussing inner classes

【讨论】:

  • 这也是 OP 也猜到的。但这是为什么呢?请备份您的答案。
  • 然后找到可以证明这一点的 JLS 参考资料——目前只是推测。
  • @Tim Biegeleisen 你能告诉我为什么在 new A().m() 之前声明类会将它带到作用域并打印内部吗?
  • 正如@Radiodef 在他的回答中指出的那样,一旦您在一个块中定义了本地类A,该类的范围就是该块之后但不是之前的剩余部分定义。至于为什么 Java 架构师决定这样做,那就是另一个问题了。
  • @TimBiegeleisen 实际上我希望将其包含在答案中。因为这是我想知道的。
【解决方案3】:

inner 未打印的原因是 (6.3):

被块直接包围的局部类声明的范围是直接包围块的其余部分,包括它自己的类声明。

(在方法中声明的类称为本地类。)

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

打印middle 而不是outer 的原因是内部类A 遮蔽顶级类A (6.4.1):

名为 n 的类型的声明 d 会隐藏在 d 的范围 [...] 内的任何其他名为 n 的类型的声明。

这意味着在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

    案例2:

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

    在这种情况下,它将打印inner,因为类现在在范围内。

    【讨论】:

      【解决方案5】:

      在方法中:

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

      当方法开始执行时,第一行将被执行 new A().m();

      并且因为内部类已经在范围内,所以将创建该类的对象,并且将为 inner class 而不是 local method class 调用 m 方法,因为它仍然不在范围内。这就是为什么你得到middle 作为输出。

      但如果您将方法更改为:

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

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

      【讨论】:

      • 这与类加载无关。当该方法第二次执行时,它仍然会产生相同的输出,即使本地类 A 已经加载。
      • 它也是因为范围偏好。这就像方法然后类。 @ErwinBolwidt 我现在已经改了。
      【解决方案6】:

      您正在使用MethodLocalVSInner 的实例调用go 方法

      go 方法内部 您正在创建A() 的实例 在这里,由于您没有显式导入外部 A class 并且直接内部类在方法调用语句之后,JVM 正在选择 inner class A ,它位于 MethodLocalVSInner 的类级别并在其中执行 go 方法

      【讨论】:

        猜你喜欢
        • 2018-12-07
        • 1970-01-01
        • 2010-10-17
        • 1970-01-01
        • 2011-09-04
        • 1970-01-01
        • 1970-01-01
        • 2010-09-09
        相关资源
        最近更新 更多