100

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

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

仅供参考,receiver 只是一个具有以下签名的辅助类:

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

我以前从未见过这种XYZ.new符号。这是如何运作的?有什么方法可以更传统地编码吗?

4

5 回答 5

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

但是,如果您想在 Foo 之外创建 Bar 的实例,或者将新实例与包含实例相关联,this那么您必须使用前缀表示法。

Foo f = new Foo(5);
Foo.Bar b = f.new Bar();
b.printVal(); // prints 5
于 2013-03-30T23:32:56.773 回答
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 构造函数指定第一个隐藏参数的一种方法,而不是类型

方法()等于:

public TestInner method(){
    return this.new TestInner();
}

TestInner等于:

class TestInner{
    private Test this$0;

    TestInner(Test parent){
        this.this$0 = parent;
    }
}
于 2013-04-02T08:54:12.870 回答
7

在 Java 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并将成员初始化为从(ie )传入this$0的值。thiscreateBarb.this$0 = f

最后我们调用b.printVal()which 尝试打印b.this$0.valwhich is f.valwhich is 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.val哪个f.val是 5。

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

于 2013-07-11T00:33:50.880 回答
4

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

当然,该类KnockKnockServer实际上并没有一个名为 的函数new receiver,但我猜测语法是为了暗示这一点。这意味着您正在调用一个函数,该函数创建一个新实例,该实例KnockKnockServer.receiver使用特定实例KnockKnockServer来访问封闭类。

于 2013-03-30T23:57:32.843 回答
1

阴影

If a declaration of a type (such as a member variable or a parameter name) in a particular scope (such as an inner class or a method definition) has the same name as another declaration in the enclosing scope, then the declaration shadows the declaration of the enclosing scope. You cannot refer to a shadowed declaration by its name alone. The following example, ShadowTest, demonstrates this:

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

The following is the output of this example:

x = 23
this.x = 1
ShadowTest.this.x = 0

This example defines three variables named x: The member variable of the class ShadowTest, the member variable of the inner class FirstLevel, and the parameter in the method methodInFirstLevel. The variable x defined as a parameter of the method methodInFirstLevel shadows the variable of the inner class FirstLevel. Consequently, when you use the variable x in the method methodInFirstLevel, it refers to the method parameter. To refer to the member variable of the inner class FirstLevel, use the keyword this to represent the enclosing scope:

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

Refer to member variables that enclose larger scopes by the class name to which they belong. For example, the following statement accesses the member variable of the class ShadowTest from the method methodInFirstLevel:

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

Refer to the docs

于 2013-07-16T10:53:46.867 回答