Java中的`someObject.new`是做什么的?
在Java中,我刚刚发现下面的代码是合法的:
KnockKnockServer newServer = new KnockKnockServer(); KnockKnockServer.receiver receive = newServer.new receiver(clientSocket);
仅供参考,接收器只是一个助手类,具有以下签名:
public class receiver extends Thread { /* code_inside */ }
我以前从来没有见过XYZ.new
符号。 这是如何运作的? 有什么方法可以更传统地编码?
这是从包含的类体外实例化一个非静态内部类的方法,如Oracle文档中所述 。
每个内部类实例都与其包含的类的实例关联。 当你在其包含的类中new
一个内部类时,默认情况下它将使用容器的this
实例:
public class Foo { int val; public Foo(int v) { val = v; } class Bar { public void printVal() { // this is the val belonging to our containing instance System.out.println(val); } } public Bar createBar() { return new Bar(); // equivalent of this.new Bar() } }
但是如果你想在Foo之外创build一个Bar的实例,或者将一个新的实例与一个包含this
实例关联起来,那么你必须使用前缀符号。
Foo f = new Foo(5); Foo.Bar b = f.new Bar(); b.printVal(); // prints 5
看看这个例子:
public class Test { class TestInner{ } public TestInner method(){ return new TestInner(); } public static void main(String[] args) throws Exception{ Test t = new Test(); Test.TestInner ti = t.new TestInner(); } }
使用javap我们可以查看为这个代码生成的指令
主要方法:
public static void main(java.lang.String[]) throws java.lang.Exception; Code: 0: new #2; //class Test 3: dup 4: invokespecial #3; //Method "<init>":()V 7: astore_1 8: new #4; //class Test$TestInner 11: dup 12: aload_1 13: dup 14: invokevirtual #5; //Method java/lang/Object.getClass:()Ljava/lang/Class; 17: pop 18: invokespecial #6; //Method Test$TestInner."<init>":(LTest;)V 21: astore_2 22: return }
内部类构造函数:
Test$TestInner(Test); Code: 0: aload_0 1: aload_1 2: putfield #1; //Field this$0:LTest; 5: aload_0 6: invokespecial #2; //Method java/lang/Object."<init>":()V 9: return }
一切都很简单 – 调用TestInner构造函数时,java将Test实例作为第一个参数main:12 。 不要看这个TestInner应该有一个没有参数的构造函数。 TestInner依次保存对父对象Test,TestInner:2的引用。 从实例方法调用内部类构造函数时,对父对象的引用是自动传递的,因此不必指定它。 其实它每次都通过,但是从外部调用的时候应该明确的通过。
t.new TestInner();
– 只是一种方法来指定TestInner构造函数的第一个隐藏的参数,而不是一个types
方法()等于:
public TestInner method(){ return this.new TestInner(); }
TestInner等于:
class TestInner{ private Test this$0; TestInner(Test parent){ this.this$0 = parent; } }
当内部类被添加到Java语言的1.1版本中时,它们最初被定义为向1.0兼容代码的转换。 如果你看一个这个转变的例子,我认为这会让内部类实际工作更加清晰。
考虑Ian Roberts的答案:
public class Foo { int val; public Foo(int v) { val = v; } class Bar { public void printVal() { System.out.println(val); } } public Bar createBar() { return new Bar(); } }
当转换为1.0兼容代码时,该内部类Bar
将变成如下所示:
class Foo$Bar { private Foo this$0; Foo$Bar(Foo outerThis) { this.this$0 = outerThis; } public void printVal() { System.out.println(this$0.val); } }
内部类名称以外部类名称为前缀,以使其具有唯一性。 一个隐藏的私人this$0
成员被添加,保存了这个外部的副本。 并创build一个隐藏的构造函数来初始化该成员。
如果你看看createBar
方法,它会被转换成这样的东西:
public Foo$Bar createBar() { return new Foo$Bar(this); }
那么让我们看看执行下面的代码时会发生什么。
Foo f = new Foo(5); Foo.Bar b = f.createBar(); b.printVal();
首先我们实例化一个Foo
实例,并将val
成员初始化为5(即f.val = 5
)。
接下来我们调用f.createBar()
,它实例化一个Foo$Bar
实例,并将this$0
成员初始化为从createBar
传入的值(即b.this$0 = f
)。
最后我们调用b.printVal()
试图打印b.this$0.val
,即f.val
,它是5。
现在,这是一个内部类的定期实例化。 让我们来看看从Foo
外部实例化Bar
时会发生什么。
Foo f = new Foo(5); Foo.Bar b = f.new Bar(); b.printVal();
再次应用我们的1.0转换,第二行会变成这样:
Foo$Bar b = new Foo$Bar(f);
这几乎与f.createBar()
调用相同。 我们再次实例化一个Foo$Bar
实例,并将this$0
成员初始化为f。 那么, b.this$0 = f
。 b.this$0 = f
。
再次调用b.printVal()
,您正在打印b.thi$0.val
,即f.val
,即5。
要记住的关键是内部类有一个隐藏的成员从外部类拿着这个副本。 当你在外部类中实例化一个内部类的时候,它会用当前的值隐式地初始化它。 当您从外部类的外部实例化内部类时,可以通过new
关键字的前缀明确指定要使用的外部类的哪个实例。
把new receiver
想象成一个单一的标记。 有点像一个有空格的函数名字。
当然, KnockKnockServer
类实际上并没有一个名为new receiver
的函数,但我猜这个语法是为了表明这一点。 这意味着你要调用一个函数来创build一个KnockKnockServer.receiver
实例,该实例使用KnockKnockServer.receiver
一个特定实例来访问封闭类。
阴影
如果特定范围(如内部类或方法定义)中的types声明(例如成员variables或参数名称)与封闭范围中的另一个声明具有相同的名称,则该声明将声明封闭的范围。 你不能仅仅通过它的名字引用一个阴影声明。 以下示例ShadowTest演示了这一点:
public class ShadowTest { public int x = 0; class FirstLevel { public int x = 1; void methodInFirstLevel(int x) { System.out.println("x = " + x); System.out.println("this.x = " + this.x); System.out.println("ShadowTest.this.x = " + ShadowTest.this.x); } } public static void main(String... args) { ShadowTest st = new ShadowTest(); ShadowTest.FirstLevel fl = st.new FirstLevel(); fl.methodInFirstLevel(23); } }
以下是这个例子的输出:
x = 23 this.x = 1 ShadowTest.this.x = 0
此示例定义了三个名为x的variables:类ShadowTest的成员variables,内部类FirstLevel的成员variables以及methodInFirstLevel方法中的参数。 定义为方法methodInFirstLevel的参数的variablesx会影响内部类FirstLevel的variables。 因此,当您在方法methodInFirstLevel中使用variablesx时,它将引用方法参数。 要引用内部类FirstLevel的成员variables,请使用关键字this来表示封闭范围:
System.out.println("this.x = " + this.x);
通过它们所属的类名引用包含更大范围的成员variables。 例如,以下语句从methodInFirstLevel方法访问类ShadowTest的成员variables:
System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
参考文档