问题
在Java中,我刚刚发现以下代码是合法的:
KnockKnockServer newServer = new KnockKnockServer();
KnockKnockServer.receiver receive = newServer.new receiver(clientSocket);
仅供参考,接收器只是一个带有以下签名的助手类:
public class receiver extends Thread { /* code_inside */ }
我以前从未见过XYZ.new
。这是如何运作的?有没有什么方法可以更传统地编码?
#1 热门回答(120 赞)
这是从包含类体外部实例化非静态内部类的方法,如Oracle docs中所述。
每个内部类实例都与其包含类的实例相关联。当你从含有类的内部类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外部创建Bar实例,或者将新实例与除了this
之外的包含实例相关联,则必须使用前缀表示法。
Foo f = new Foo(5);
Foo.Bar b = f.new Bar();
b.printVal(); // prints 5
#2 热门回答(18 赞)
看看这个例子:
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构造函数指定第一个隐藏参数的方法,而不是类型
method()等于:
public TestInner method(){
return this.new TestInner();
}
TestInneris等于:
class TestInner{
private Test this$0;
TestInner(Test parent){
this.this$0 = parent;
}
}
#3 热门回答(7 赞)
当内部类在该语言的1.1版本中添加到Java时,它们最初被定义为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兼容代码时,内部classBar
将变成这样的:
class Foo$Bar {
private Foo this$0;
Foo$Bar(Foo outerThis) {
this.this$0 = outerThis;
}
public void printVal() {
System.out.println(this$0.val);
}
}
内部类名称以外部类名称为前缀,以使其唯一。添加了隐藏的privatethis$0
成员,其中包含outerthis
的副本。并创建一个隐藏的构造函数来初始化该成员。
如果你看看createBar
方法,它会变成这样的东西:
public Foo$Bar createBar() {
return new Foo$Bar(this);
}
因此,让我们看看执行以下代码时会发生什么。
Foo f = new Foo(5);
Foo.Bar b = f.createBar();
b.printVal();
首先,我们实例化Foo
的实例,并将theval
成员初始化为5(即f.val = 5
)。
接下来我们调用f.createBar()
,它实例化一个实例Foo$Bar
并将this$0
成员初始化为从createBar
(即b.this$0 = f
)开始的值this
。
最后我们打电话给b.printVal()
,试图打印410188833,其中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);
这几乎与316665606呼叫完全相同。我们再次实例化一个实例Foo$Bar
并将this$0
成员初始化为f。所以,b.this$0 = f
。
再次当你致电b.printVal()
时,你正在打印2137373555,即f.val
,即5。
要记住的关键是内部类有一个隐藏的成员,它从外部类中持有一个this
的副本。当你从外部类中实例化一个内部类时,它会使用当前值this
进行隐式初始化。当你从外部类外部实例化内部类时,你可以通过new
关键字上的前缀显式指定要使用的外部类的哪个实例。