`someObject.new` 在 Java 中做了啥?

Posted

技术标签:

【中文标题】`someObject.new` 在 Java 中做了啥?【英文标题】: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 的内部类对我来说有点奇怪。 *** 上没有愚蠢的问题! @IsaacRabinovitch - 没有愚蠢的问题。然而,有很多愚蠢的。 (偶尔也会有一个愚蠢的答案。) @HotLicks 你对愚蠢问题的定义是什么?我想你太聪明了,不需要问。太好了,你有这么多的自尊心。 【参考方案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

【讨论】:

以上是关于`someObject.new` 在 Java 中做了啥?的主要内容,如果未能解决你的问题,请参考以下文章

ctmp在java中的用法?

在java中,啥是队列?

java文件在eclipse中可以运行,在cmd中javac命令运行正确,java命令报错

在java中怎样在有名包中引用无名包中的类

在java中,啥是接口,接口的特点是啥

在java中变量和属性有啥不同