9

在下面显示的代码片段中,内部类继承了外部类本身。

package test;

class TestInnerClass {

    private String value;

    public TestInnerClass(String value) {
        this.value = value;
    }

    private String getValue() {
        return value;
    }

    public void callShowValue() {
        new InnerClass("Another value").showValue();
    }

    private final class InnerClass extends TestInnerClass {

        public InnerClass(String value) {
            super(value);
        }

        public void showValue() {
            System.out.println(getValue());
            System.out.println(value);
        }
    }
}

public final class Test {

    public static void main(String[] args) {
        new TestInnerClass("Initial value").callShowValue();
    }
}

方法内的唯一语句main()(最后一个片段)将值分配给类Initial value的私有字段valueTestInnerClass然后调用该callShowValue()方法。

callShowValue()方法导致另一个字符串 -在调用扩展方法之前设置为类Another value的私有字段。valueTestInnerClassshowValue()InnerClassTestInnerClass

showValue()因此,方法内部的以下两个语句,

System.out.println(getValue());
System.out.println(value);

应该显示,

另一个价值
另一个价值

但他们显示,

初始值
初始值

为什么会这样?

4

4 回答 4

8

这里的关键是了解内部类如何访问外部类的成员。private以及在和成员的情况下如何访问这些non-private成员。(注意:我将在这里讨论非static内部类,因为问题仅与此有关)。

内部类存储对封闭实例的引用:

内部类将对封闭实例的引用存储为字段。该字段被命名为this$0。封闭实例总是绑定到内部类对象。当您从封闭类内部创建内部类的对象时,this$0所有对象的引用值保持相同,但this引用会有所不同。

您可以使用内部类中的语法访问this$0字段。Outer.this例如,考虑以下代码:

class Outer {
    public Outer() { }

    public void createInnerInstance() {
        Inner obj1 = new Inner();
        Inner obj2 = new Inner();
    }

    private class Inner {
        public Inner() {
            System.out.println(Outer.this);
            System.out.println(this);
        }
    }
}

public static void main(String[] args) {
    new Outer().createInnerInstance();
}

执行此代码时,您将获得如下输出:

Outer@135fbaa4
Outer$Inner@45ee12a7
Outer@135fbaa4
Outer$Inner@330bedb4

请注意第 1和第 3引用是相同的,而第 2和第 4是不同的。


外部类成员可以使用this$0引用在内部类中访问:

当您从内部类访问字段或外部类的任何其他成员时,访问表达式会自动限定this$0。您明确地将成员访问限定为this$0使用OuterClass.this引用。因此,请考虑value外部类中的字段 was public,然后在showValue()内部类中的方法中:

public void showValue() {
    System.out.println(TestInnerClass.this.value);
    System.out.println(value);
}

前 2 个打印语句是等效的。它们将被编译为相同的字节码:

 public void showValue();
   Code:
      0: getstatic     #3                  // Field java/lang/System.out:Ljava/
o/PrintStream;
      3: aload_0
      4: getfield      #1                  // Field this$0:LTestInnerClass;
      7: getfield      #4                  // Field TestInnerClass.value:Ljava/lang/Stri
g;
     10: invokevirtual #5                  // Method java/io/PrintStream.printl
:(Ljava/lang/String;)V
     13: getstatic     #3                  // Field java/lang/System.out:Ljava/
o/PrintStream;
     16: aload_0
     17: getfield      #1                  // Field this$0:LTestInnerClass;
     20: getfield      #4                  // Field TestInnerClass.value:Ljava/lang/Stri
g;
     23: invokevirtual #5                  // Method java/io/PrintStream.printl
:(Ljava/lang/String;)V
     26: return

您不能使用以下方式显式访问外部类成员this

如果您在内部类中显式尝试限定字段或方法访问表达式this,您将收到编译器错误:

public void showValue() {
    System.out.println(this.value);  // this won't compile
}

上面的 print 语句不会编译,因为value它不是内部类本身的字段。这是一个外部类字段。this指的是内部类实例,而不是外部类。


当内部类扩展外部类时,故事会发生变化:

当你的内部类扩展外部类时,事情就开始变得奇怪了。因为在这种情况下,限定字段或方法访问this对非私有成员有效。对于private成员,这仍然无效,因为private成员不是继承的。

在继承内部类的情况下,直接访问外部类成员用this. 这意味着,它们将作为内部类成员访问。在明确限定访问时,Outer.this将引用封闭实例的字段 - this$0

考虑value字段声明为public

public void showValue() { 
    System.out.println(value);            // inner class instance field
    System.out.println(this.value);       // inner class instance field
    System.out.println(Outer.this.value); // enclosing instance field
}

前两个 print 语句将打印value内部类实例的字段,而第三个 print 语句将打印value封闭实例的字段。使困惑?

记得我说过,当你从外部类内部创建多个内部类实例时,它们将具有相同的this$0引用。

考虑创建一个外部类实例:

new Outer("rohit").callShowValue();

然后在callShowValue()方法中,创建内部类的实例:

new Inner("rj").showValue();

现在,该showValue()方法的输出将是:

rj
rj
rohit

您会注意到,this.value与 不同Outer.this.value

如果您制作value字段怎么办private

现在,当您制作外部类字段private时,当然您无法使用this.value;. 因此,第二个打印语句将无法编译。

在这种情况下,直接访问字段将被限定this$0。现在更改字段value私有,并将showValue()方法修改为:

public void showValue() { 
    System.out.println(value);            // enclosing instance field
    System.out.println(this.value);       // compiler error
    System.out.println(Outer.this.value); // enclosing instance field
}

这就是问题所在。第一个 print 语句value使用thisthis$0基于字段是public或来限定private


谈到你的具体问题:

现在在您的代码中,由于value字段和getValue()方法都是private,因此showValue()方法:

public void showValue() {
    System.out.println(getValue());
    System.out.println(value);
}

等同于:

public void showValue() {
    System.out.println(TestInnerClass.this.getValue());
    System.out.println(TestInnerClass.this.value);
}

这是访问封闭实例的字段和方法。并且该字段仍然是Initial Value。这就是为什么输出是:

初始值
初始值

于 2013-10-13T20:48:45.677 回答
8

方法getValue()和领域value都是private。因此,任何其他类(包括子类)都无法访问它们。他们不是继承的。

InnerClass#showValue()

public void showValue() {
    System.out.println(getValue());
    System.out.println(value);
}

因为它们是私有的,getValue()并且value指的是外部类的成员,因为您在同一个类中,所以可以访问它们,即。内部类可以访问外部类私有成员。上面的调用等价于

public void showValue() {
    System.out.println(TestInnerClass.this.getValue());
    System.out.println(TestInnerClass.this.value);
}

既然你已经设置value

new TestInnerClass("Initial value")

你看到"Initial value"被打印了两次。无法访问private子类中的那些成员。


关键是:不要让子类成为内部类。

于 2013-10-13T19:41:36.780 回答
2

在上述情况下, TestInnerClass 和 InnerClass之间有两种不同的关系。

但是故事有一点转折……有两个对象!问题是我们将“另一个值”放入不同的对象中!并打印旧值的值..

第一个对象:

public static void main(String[] args) {
    new TestInnerClass("Initial value").callShowValue();
}

测试类中的上述方法创建了一个具有“初始值”的 TestInnerClass 实例

第二个对象:

public void callShowValue() {
    new InnerClass("Another value").showValue();
}

由于InnerClass正在扩展TestInnerClass另一个新的 TestInnerClass 实例是用"Another Value"创建的。但是我们打印的是旧对象而不是第二个对象的值。

于 2013-10-13T20:42:19.137 回答
1

其他答案很好地解释了为什么你得到你看到的结果(你有两个实例TestInnerClass并且正在访问第一个),但是实际上有一种方法可以访问TestInnerClass其子类中的私有成员 - 关键字super

如果你showValue用这个替换你的方法:

public void showValue() {
    System.out.println(super.getValue());
    System.out.println(super.value);
}

您将获得预期的输出。

我还建议,如果您决定这样做,请创建InnerClass一个静态内部类,因为它不再需要对其外部类的实例的引用。

于 2014-07-25T21:32:39.047 回答