问题

在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关键字上的前缀显式指定要使用的外部类的哪个实例。


原文链接