Java에서 방금 다음 코드가 합법적이라는 것을 알았습니다.
KnockKnockServer newServer = new KnockKnockServer();
KnockKnockServer.receiver receive = newServer.new receiver(clientSocket);
참고로 수신자는 다음 서명이있는 도우미 클래스 일뿐입니다.
public class receiver extends Thread { /* code_inside */ }
나는 XYZ.new
전에 표기법을 본 적이 없습니다 . 어떻게 작동합니까? 더 일반적으로 코딩하는 방법이 있습니까?
답변
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
답변
이 예를 살펴보십시오.
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;
}
}
답변
내부 클래스가 언어 버전 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
멤버를 this
전달 된 값 createBar
(예 :)으로 초기화합니다 b.this$0 = f
.
마지막으로 우리는 전화를 b.printVal()
인쇄 할 수있는 b.this$0.val
인 f.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.val
되는 f.val
5이다.
기억해야 할 핵심 사항은 내부 클래스 this
에 외부 클래스 의 복사본을 보유하는 숨겨진 멤버가 있다는 것 입니다. 외부 클래스 내에서 내부 클래스를 인스턴스화하면 현재 값으로 암시 적으로 초기화됩니다 this
. 외부 클래스 외부에서 내부 클래스를 인스턴스화 할 때 new
키워드 의 접두사를 통해 사용할 외부 클래스의 인스턴스를 명시 적으로 지정 합니다.
답변
new receiver
하나의 토큰으로 생각하십시오 . 공백이있는 함수 이름과 같습니다.
물론 클래스 KnockKnockServer
에는 문자 그대로라는 함수가 new receiver
없지만 구문이이를 제안하기위한 것으로 추측됩니다. 이는 둘러싸는 클래스 KnockKnockServer.receiver
에 KnockKnockServer
대한 모든 액세스를 위해 의 특정 인스턴스를 사용하는 새 인스턴스를 만드는 함수를 호출하는 것처럼 보입니다 .
답변
섀도 잉
특정 범위 (예 : 내부 클래스 또는 메서드 정의)의 형식 선언 (예 : 멤버 변수 또는 매개 변수 이름)이 바깥 쪽 범위의 다른 선언과 동일한 이름을 갖는 경우 선언은 선언을 섀도 잉합니다. 둘러싸는 범위의. 이름만으로 그림자가있는 선언을 참조 할 수 없습니다. 다음 예제 인 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);