Javaで「someObject.new」は何をしますか?


100

Javaでは、次のコードが正当であることがわかりました。

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

参考までに、レシーバーは次のシグネチャを持つ単なるヘルパークラスです。

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

XYZ.new記法を見たことがない。それはどのように機能しますか?より慣習的にそれをコーディングする方法はありますか?


7
参考までに、内部クラス
Alvin Wong

1
また、私はそれnewが多くの言語の演算子であると信じていました。(newC ++ でもオーバーロードする可能性があると思いましたか?)ただし、Javaの内部クラスは少し奇妙です。
Alvin Wong

5
StackOverflowにばかげた質問はありません!
Isaac Rabinovitch 2013

2
@IsaacRabinovitch-愚かな質問はありません。しかし、ばかげたものがたくさんあります。(そして、時々ばかげた答えも。)
Hot Licks

2
@HotLicksそして、あなたの愚かな質問の定義は何ですか?賢くて質問する必要がない人だと思います。あなたがそんなに自尊心を持っているのは良いことです。
Isaac Rabinovitch 2013年

回答:


120

これは、Oracle docsで説明されているように、包含クラス本体の外部から非静的内部クラスをインスタンス化する方法です。

すべての内部クラスインスタンスは、それを含むクラスのインスタンスに関連付けられています。包含クラス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

18
そして、ご存知のように、これは非常に混乱する可能性があります。理想的には、内部クラスは外部クラスの実装の詳細であり、外部の世界に公開されるべきではありません。
Eric Jablow 2013年

10
@EricJablowは確かに、仕様の整合性を保つために必要な構文の1つですが、実際に使用する必要のない時間の99.9999%です。部外者が実際にBarインスタンスを作成する必要がある場合は、Fooで使用するのではなく、Fooにファクトリメソッドを提供しますf.new
Ian Roberts

正しい私私は間違っているが、場合場合publicのアクセスレベルがKnockKnockServer.receiver行われたprivate後、それが正しい、このようにインスタンス化することは不可能でしょうか?@EricJablowのコメントを拡張するには、通常、内部クラスのデフォルトを常にprivateアクセスレベルにする必要があります。
Andrew Bissell 2013年

1
@AndrewBissellはい、ただしreceiver、外部からクラスを参照することも不可能です。設計している場合は、おそらくクラスはパブリックですが、そのコンストラクターは保護またはパッケージプライベートであり、KnockKnockServerレシーバーインスタンスを作成するためのメソッドがあります。
Ian Roberts

1
@emory 言っているわけではありません。内部クラスをパブリックにして、外部クラスのメソッドから内部クラスのインスタンスを返したいという正当な理由がある可能性があることは知っていますが、「部外者」は、を直接使用して内部クラスのインスタンスを構築する必要はありませんx.new
Ian Roberts

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は最初の引数main:12としてTestインスタンスを渡します。その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;
    }
}

7

内部クラスが言語のバージョン1.1でJavaに追加されたとき、それらは元々1.0互換コードへの変換として定義されていました。この変換の例を見ると、内部クラスが実際にどのように機能するかが非常に明確になると思います。

イアン・ロバーツの答えからのコードを考えてみましょう:

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

最初に、インスタンスをインスタンス化しFoovalメンバーを5に初期化します(つまりf.val = 5)。

次にを呼び出してf.createBar()、のインスタンスをインスタンスFoo$Bar化し、this$0メンバーをthisfromから渡された値createBar(つまりb.this$0 = f)に初期化します。

最後にwhichはwhich is whichである5 b.printVal()を印刷しようとします。b.this$0.valf.val

これが内部クラスの定期的なインスタンス化です。Bar外部からインスタンス化するとどうなるか見てみましょうFoo

Foo f = new Foo(5);
Foo.Bar b = f.new Bar();
b.printVal();

1.0変換を再度適用すると、その2行目は次のようになります。

Foo$Bar b = new Foo$Bar(f);

これはf.createBar()呼び出しとほとんど同じです。繰り返しますが、インスタンスをインスタンスFoo$Bar化し、this$0メンバーをfに初期化しています。だから、もう一度、b.this$0 = f

また、を呼び出すとb.printVal()、5 が印刷さb.thi$0.valれますf.val

覚えておくべき重要なことは、内部クラスにはthis外部クラスからのコピーを保持する隠しメンバーがあるということです。外部クラス内から内部クラスをインスタンス化すると、現在の値で暗黙的に初期化されますthis。外部クラスの外部から内部クラスをインスタンス化するときは、newキーワードのプレフィックスを介して、使用する外部クラスのインスタンスを明示的に指定します。


4

new receiver単一のトークンと考えてください。中にスペースがある関数名のようなものです。

もちろん、クラスにKnockKnockServerは文字通りという関数はありませんnew receiverが、構文はそのことを示唆していると思います。これは、囲んでいるクラスへのアクセスにKnockKnockServer.receiver特定のインスタンスを使用する新しいインスタンスを作成する関数を呼び出しているように見えることを意図していますKnockKnockServer


おかげで、はい-それは今私がnew receiver1つのトークンと考えるのに役立ちます!どうもありがとう!
2013年

1

シャドウイング

特定のスコープ(内部クラスやメソッド定義など)内の型(メンバー変数やパラメーター名など)の宣言が、外側のスコープ内の別の宣言と同じ名前である場合、宣言は宣言をシャドウします囲んでいるスコープの。名前だけでシャドウされた宣言を参照することはできません。次の例、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という名前の3つの変数を定義しています。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);

ドキュメントを参照してください

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.