什么`someObject.new`在Java中做什么?

Cof*_*fee 100 java

在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)

  • 而且,正如你所知,这可能令人难以置信的混乱.理想情况下,内部类应该是外部类的实现细节,而不是暴露给外部世界. (18认同)
  • @EricJablow确实,它是必须存在的一些语法,以保持规范一致,但99.9999%的时间你实际上不需要使用它.如果局外人真的需要创建Bar实例,那么我会在Foo上提供一个工厂方法,而不是让他们使用`f.new`. (10认同)

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)


Jam*_*ess 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();
  }
}
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)

首先,我们Fooval成员的实例和初始化实例化为5(即f.val = 5).

接下来我们调用f.createBar(),它实例化一个实例Foo$Bar并将this$0成员初始化为thiscreateBar(ie b.this$0 = f)传入的值.

最后我们调用b.printVal()它试图打印b.this$0.valf.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.valf.val是5.

要记住的关键是内部类有一个隐藏的成员,它持有this外部类的副本.当您从外部类中实例化一个内部类时,它会使用当前值隐式初始化this.当您从外部类外部实例化内部类时,您可以通过new关键字上的前缀显式指定要使用的外部类的哪个实例.