【问题标题】:What does `someObject.new` do in Java?`someObject.new` 在 Java 中做了什么?
【发布时间】:2013-03-30 23:31:30
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 供您参考,inner class。
  • 另外,我相信new 是许多语言的运算符。 (我想你也可能在 C++ 中重载new?)不过,Java 的内部类对我来说有点奇怪。
  • StackOverflow 上没有愚蠢的问题!
  • @IsaacRabinovitch - 没有愚蠢的问题。然而,有很多愚蠢的。 (偶尔也会有一个愚蠢的答案。)
  • @HotLicks 你对愚蠢问题的定义是什么?我想你太聪明了,不需要问。太好了,你有这么多的自尊心。

标签: java


【解决方案1】:

这是从包含类主体外部实例化非静态内部类的方法,如Oracle docs 中所述。

每个内部类实例都与其包含类的一个实例相关联。当您 new 来自 within 其包含类的内部类时,它默认使用容器的 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

【讨论】:

  • 而且,正如您所知,这可能会令人难以置信的混乱。理想情况下,内部类应该是外部类的实现细节,而不是暴露给外界。
  • @EricJablow 确实,它是必须存在以保持规范一致的语法之一,但 99.9999% 的时间你实际上并不需要使用它。如果外人真的需要创建 Bar 实例,那么我会在 Foo 上提供一个工厂方法,而不是让他们使用 f.new。
  • 如果我错了,请纠正我,但如果KnockKnockServer.receiver 上的public 访问级别设置为private,那么就不可能以这种方式实例化,对吧?要扩展 @EricJablow 的评论,内部类通常应始终默认为 private 访问级别。
  • @AndrewBissell 是的,但也无法从外部引用 receiver 类。如果我正在设计它,我可能会让 class 是公共的,但它的 constructor 是受保护的或包私有的,并且在 KnockKnockServer 上有一个方法来创建接收器实例。
  • @emory 我不是说,我知道有完全正当的理由想要公开一个内部类,并从方法返回内部类的实例外部,但我倾向于设计我的代码,这样“外部”不需要直接使用x.new构造内部类的实例。
【解决方案2】:

看看这个例子:

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 实例作为第一个参数 ma​​in: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;
    }
}

【讨论】:

    【解决方案3】:

    当内部类被添加到 Java 1.1 版本的语言中时,它们最初被定义为对 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 成员初始化为从createBar 传入的this 的值(即b.this$0 = f)。

    最后我们调用b.printVal(),它尝试打印b.this$0.val,即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);
    

    这与f.createBar() 调用几乎相同。我们再次实例化Foo$Bar 的实例并将this$0 成员初始化为f。再说一遍,b.this$0 = f。

    当您再次调用b.printVal() 时,您正在打印b.thi$0.val,即f.val,即5。

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

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

      • 谢谢,是的 - 它现在帮助我将new receiver 视为一个标记!非常感谢!
      【解决方案5】:

      阴影

      如果特定作用域(例如内部类或方法定义)中的类型声明(例如成员变量或参数名称)与封闭作用域中的另一个声明具有相同的名称,则该声明隐藏封闭范围的声明。您不能仅通过名称来引用影子声明。以下示例 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);
      

      Refer to the docs

      【讨论】:

        猜你喜欢
        • 2012-08-07
        • 1970-01-01
        • 2011-04-11
        • 1970-01-01
        • 1970-01-01
        • 2010-10-27
        • 1970-01-01
        • 1970-01-01
        • 2021-09-05
        相关资源
        最近更新 更多