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 = fb.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); 

参考文档