`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 实例作为第一个参数 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;
【讨论】:
【参考方案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 中做了啥?的主要内容,如果未能解决你的问题,请参考以下文章