在Java中,我刚刚发现以下代码是合法的:
KnockKnockServer newServer = new KnockKnockServer();
KnockKnockServer.receiver receive = newServer.new receiver(clientSocket);
Run Code Online (Sandbox Code Playgroud)
仅供参考,接收器只是一个带有以下签名的助手类:
public class receiver extends Thread { /* code_inside */ }
Run Code Online (Sandbox Code Playgroud)
我以前从未见过这种XYZ.new符号.这是如何运作的?有没有什么方法可以更传统地编码?
Ian*_*rts 120
这是从包含类主体外部实例化非静态内部类的方法,如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()
}
}
Run Code Online (Sandbox Code Playgroud)
但是,如果要在Foo外部创建Bar实例,或者将新实例与包含其他实例的实例关联,this则必须使用前缀表示法.
Foo f = new Foo(5);
Foo.Bar b = f.new Bar();
b.printVal(); // prints 5
Run Code Online (Sandbox Code Playgroud)
Mik*_*ail 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();
}
}
Run Code Online (Sandbox Code Playgroud)
使用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
}
Run Code Online (Sandbox Code Playgroud)
内部类构造函数:
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
}
Run Code Online (Sandbox Code Playgroud)
一切都很简单 - 在调用TestInner构造函数时,java将Test实例作为第一个参数main传递:12.不看那个TestInner应该有一个无参数的构造函数.TestInner反过来只保存对父对象Test $ TestInner:2的引用.从实例方法调用内部类构造函数时,将自动传递对父对象的引用,因此您不必指定它.实际上它每次传递,但是当从外部调用它时应该明确传递.
t.new TestInner(); - 只是一种为TestInner构造函数指定第一个隐藏参数的方法,而不是类型
method()等于:
public TestInner method(){
return this.new TestInner();
}
Run Code Online (Sandbox Code Playgroud)
TestInner等于:
class TestInner{
private Test this$0;
TestInner(Test parent){
this.this$0 = parent;
}
}
Run Code Online (Sandbox Code Playgroud)
当内部类在该语言的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();
}
}
Run Code Online (Sandbox Code Playgroud)
当转换为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);
}
}
Run Code Online (Sandbox Code Playgroud)
内部类名称以外部类名称为前缀,以使其唯一.this$0添加了隐藏的私有成员,其中包含外部的副本this.并创建一个隐藏的构造函数来初始化该成员.
如果你看一下这个createBar方法,它会变成这样的东西:
public Foo$Bar createBar() {
return new Foo$Bar(this);
}
Run Code Online (Sandbox Code Playgroud)
因此,让我们看看执行以下代码时会发生什么.
Foo f = new Foo(5);
Foo.Bar b = f.createBar();
b.printVal();
Run Code Online (Sandbox Code Playgroud)
首先,我们Foo将val成员的实例和初始化实例化为5(即f.val = 5).
接下来我们调用f.createBar(),它实例化一个实例Foo$Bar并将this$0成员初始化为this从createBar(ie b.this$0 = f)传入的值.
最后我们调用b.printVal()它试图打印b.this$0.val这f.val是5.
现在这是一个内部类的常规实例化.让我们看一下Bar从外部实例化时会发生什么Foo.
Foo f = new Foo(5);
Foo.Bar b = f.new Bar();
b.printVal();
Run Code Online (Sandbox Code Playgroud)
再次应用我们的1.0转换,第二行将变成这样的:
Foo$Bar b = new Foo$Bar(f);
Run Code Online (Sandbox Code Playgroud)
这几乎与f.createBar()通话完全相同.我们再次实例化一个实例Foo$Bar并将this$0成员初始化为f.再说一遍b.this$0 = f.
另一次是当你打电话b.printVal(),你是印刷b.thi$0.val这f.val是5.
要记住的关键是内部类有一个隐藏的成员,它持有this外部类的副本.当您从外部类中实例化一个内部类时,它会使用当前值隐式初始化this.当您从外部类外部实例化内部类时,您可以通过new关键字上的前缀显式指定要使用的外部类的哪个实例.
| 归档时间: |
|
| 查看次数: |
3671 次 |
| 最近记录: |