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


100

在Java中,我刚刚发现以下代码是合法的:

KnockKnockServer newServer = new KnockKnockServer();                    
KnockKnockServer.receiver receive = newServer.new receiver(clientSocket);

仅供参考,接收方只是具有以下签名的帮助程序类:

public class receiver extends Thread {  /* code_inside */  }

我从未见过这种XYZ.new记法。这是如何运作的?有什么方法可以更常规地进行编码吗?


7
供参考,内部类
Alvin Wong

1
另外,我相信这new是多种语言的运算符。(我以为您也可能new在C ++中重载?)Java的内部类对我来说有点奇怪。
Alvin Wong

5
在StackOverflow上没有愚蠢的问题!
Isaac Rabinovitch

2
@IsaacRabinovitch-没有愚蠢的问题。但是,有很多愚蠢的。(还有一个偶然的愚蠢答案。)
热舔

2
@HotLicks您对一个愚蠢问题的定义是什么?我想,你太聪明了,不需要问。好,你有这么多的自尊心。
艾萨克·拉比诺维奇

Answers:


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

18
而且,如您所知,这可能令人难以置信。理想情况下,内部类应该是外部类的实现细节,而不是暴露于外界。
Eric Jablow

10
实际上,@ EricJablow是必须保持语法规范的那些语法之一,但是实际上您不需要使用它的时间为99.9999%。如果局外人确实需要创建Bar实例,那么我将在Foo上提供一种工厂方法,而不是使用它们f.new
伊恩·罗伯茨

纠正我,如果我错了,但如果public在访问级别KnockKnockServer.receiver作了private那么这将是不可能的实例化这种方式,对不对?为了扩展@EricJablow的注释,内部类通常应始终始终默认为private访问级别。
Andrew Bissell 2013年

1
@AndrewBissell是的,但是也完全不可能receiver从外部引用该类。如果我在设计它,我可能会有一个public 类,但它的构造函数受保护或package-private,并且有一个KnockKnockServer创建接收器实例的方法。
伊恩·罗伯茨

1
@emory 并不是说,我知道有充分正当的理由想要公开内部类,并从外部方法返回内部类的实例,但是我倾向于将代码设计为“局外人”不需要直接使用构造内部类的实例x.new
伊恩·罗伯茨

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();
}

TestInner等于:

class TestInner{
    private Test this$0;

    TestInner(Test parent){
        this.this$0 = parent;
    }
}

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兼容代码时,该内部类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添加了一个隐藏的私有成员,该成员拥有外部的副本this。然后创建一个隐藏的构造函数以初始化该成员。

如果您看一下该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成员初始化为thisfrom 的传入值createBar(即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();

再次应用我们的1.0转换,第二行将变成这样:

Foo$Bar b = new Foo$Bar(f);

这几乎与f.createBar()通话相同。再次,我们实例化一个实例Foo$Bar并将其初始化this$0为f。再次如此b.this$0 = f

另一次是当你打电话b.printVal(),你要打印b.thi$0.valf.val是5。

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


4

可以将其new receiver视为单个令牌。有点像带有空格的函数名称。

当然,该类KnockKnockServer实际上没有一个名为的函数new receiver,但是我猜测语法的含义是建议这样做。这意味着您正在调用一个函数,该函数创建一个新实例,KnockKnockServer.receiver该实例使用的特定实例对KnockKnockServer封闭类进行任何访问。


谢谢,是的-现在可以将我new receiver当作一个令牌来帮助我!非常感谢!
2013年

1

重影

如果在特定范围(例如内部类或方法定义)中的类型声明(例如成员变量或参数名称)与封闭范围中的另一个声明具有相同的名称,则该声明将覆盖该声明封闭范围。您不能仅凭其名称引用带阴影的声明。下面的示例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的变量:ShadowTest类的成员变量,内部类FirstLevel的成员变量以及methodInFirstLevel方法中的参数。定义为方法methodInFirstLevel的参数的变量x遮盖了内部类FirstLevel的变量。因此,在方法methodInFirstLevel中使用变量x时,它引用方法参数。要引用内部类FirstLevel的成员变量,请使用关键字this表示封闭范围:

System.out.println("this.x = " + this.x);

请参阅成员变量,这些成员变量使用它们所属的类名将更大的范围括起来。例如,以下语句从methodInFirstLevel方法访问ShadowTest类的成员变量:

System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);

参考文档

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.