15

この質問は、Java の興味深い動作に関するものです。状況によっては、ネストされたクラスの追加の (デフォルトではない) コンストラクターが生成されます。

この質問は、Javaがその奇妙なコンストラクターで生成する奇妙な匿名クラスに関するものでもあります。


次のコードを検討してください。

package a;

import java.lang.reflect.Constructor;

public class TestNested {    
    class A {    
        A() {
        }   

        A(int a) {
        }
    }    

    public static void main(String[] args) {
        Class<A> aClass = A.class;
        for (Constructor c : aClass.getDeclaredConstructors()) {
            System.out.println(c);
        }

    }
}

これにより、次が出力されます。

a.TestNested$A(a.TestNested)
a.TestNested$A(a.TestNested,int)

Ok。次に、コンストラクターをA(int a)非公開にします。

    private A(int a) {
    }

プログラムを再度実行します。受け取る:

a.TestNested$A(a.TestNested)
private a.TestNested$A(a.TestNested,int)

こちらもOKです。しかし今、main()そのような方法でメソッドを変更しましょう(クラスA作成の新しいインスタンスの追加):

public static void main(String[] args) {
    Class<A> aClass = A.class;
    for (Constructor c : aClass.getDeclaredConstructors()) {
        System.out.println(c);
    }

    A a = new TestNested().new A(123);  // new line of code
}

入力は次のようになります。

a.TestNested$A(a.TestNested)
private a.TestNested$A(a.TestNested,int)
a.TestNested$A(a.TestNested,int,a.TestNested$1) 

それは何ですか: a.TestNested$A(a.TestNested,int,a.TestNested$1) <<<---??

わかりました、再びコンストラクタA(int a)パッケージをローカルにしましょう:

    A(int a) {
    }

プログラムを再度実行します (作成のインスタンスを含む行は削除しませんA!)、出力は最初と同じです:

a.TestNested$A(a.TestNested)
a.TestNested$A(a.TestNested,int)

質問:

1)これはどのように説明できますか?

2)この 3 番目の奇妙なコンストラクターは何ですか?


更新:以下に示す調査。

1) 他のクラスからのリフレクションを使用して、この奇妙なコンストラクターを呼び出してみましょう。TestNested$1その奇妙なクラスのインスタンスを作成する方法がないため、これを行うことはできません。

2) わかりました。トリックをしましょう。TestNestedそのような静的フィールドをクラスに追加しましょう:

public static Object object = new Object() {
    public void print() {
        System.out.println("sss");
    }
};

良い?わかりました。これで、この 3 番目の奇妙なコンストラクターを別のクラスから呼び出すことができます。

    TestNested tn = new TestNested();
    TestNested.A a = (TestNested.A)TestNested.A.class.getDeclaredConstructors()[2].newInstance(tn, 123, TestNested.object);

申し訳ありませんが、まったくわかりません。


UPDATE-2:追加の質問は次のとおりです。

3) Java がこの 3 番目の合成コンストラクターの引数の型に特別な匿名内部クラスを使用するのはなぜですか? Object特別な名前を持つコンストラクターのタイプだけではないのはなぜですか?

4)これらの目的で、既に定義されている匿名内部クラスを使用できる Java はどれですか? これはある種のセキュリティ違反ではありませんか?

4

2 に答える 2

9

3 番目のコンストラクターは、外部クラスからプライベート コンストラクターへのアクセスを許可するために、コンパイラによって生成される合成コンストラクターです。これは、内部クラス (およびそれらを囲むクラスのプライベート メンバーへのアクセス) が Java 言語に対してのみ存在し、JVM に対しては存在しないためです。そのため、コンパイラは舞台裏でギャップを埋める必要があります。

リフレクションは、メンバーが合成であるかどうかを示します。

for (Constructor c : aClass.getDeclaredConstructors()) {
    System.out.println(c + " " + c.isSynthetic());
}

これは以下を出力します:

a.TestNested$A(a.TestNested) false
private a.TestNested$A(a.TestNested,int) false
a.TestNested$A(a.TestNested,int,a.TestNested$1) true

詳細については、この投稿を参照してください: Java のプライベート静的ネストされたクラスの合成アクセサーに関する Eclipse の警告?

編集:興味深いことに、Eclipse コンパイラーは javac とは異なる方法でそれを行います。Eclipse を使用する場合、内部クラス自体の型の引数が追加されます。

a.TestNested$A(a.TestNested) false
private a.TestNested$A(a.TestNested,int) false
a.TestNested$A(a.TestNested,int,a.TestNested$A) true

そのコンストラクターを事前に公開して、つまずかせようとしました。

class A {    
    A() {
    }   

    private A(int a) {
    }

    A(int a, A another) { }
}

合成コンストラクターに別の引数を追加するだけでこれに対処しました。

a.TestNested$A(a.TestNested) false
private a.TestNested$A(a.TestNested,int) false
a.TestNested$A(a.TestNested,int,a.TestNested$A) false
a.TestNested$A(a.TestNested,int,a.TestNested$A,a.TestNested$A) true
于 2013-01-10T21:12:36.190 に答える
8

まず、この興味深い質問をありがとう。私は非常に興味をそそられたので、バイトコードを見ずにはいられませんでした。これはのバイトコードですTestNested:

Compiled from "TestNested.java"
  public class a.TestNested {
    public a.TestNested();
      Code:
         0: aload_0       
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return        

    public static void main(java.lang.String[]);
      Code:
         0: ldc_w         #2                  // class a/TestNested$A
         3: astore_1      
         4: aload_1       
         5: invokevirtual #3                  // Method java/lang/Class.getDeclaredConstructors:()[Ljava/lang/reflect/Constructor;
         8: astore_2      
         9: aload_2       
        10: arraylength   
        11: istore_3      
        12: iconst_0      
        13: istore        4
        15: iload         4
        17: iload_3       
        18: if_icmpge     41
        21: aload_2       
        22: iload         4
        24: aaload        
        25: astore        5
        27: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        30: aload         5
        32: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        35: iinc          4, 1
        38: goto          15
        41: new           #2                  // class a/TestNested$A
        44: dup           
        45: new           #6                  // class a/TestNested
        48: dup           
        49: invokespecial #7                  // Method "<init>":()V
        52: dup           
        53: invokevirtual #8                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
        56: pop           
        57: bipush        123
        59: aconst_null   
        60: invokespecial #9                  // Method a/TestNested$A."<init>":(La/TestNested;ILa/TestNested$1;)V
        63: astore_2      
        64: return        
  }

ご覧のとおり、コンストラクターa.TestNested$A(a.TestNested,int,a.TestNested$1)はメソッドから呼び出されますmain。さらに、パラメータnullの値として渡されa.TestNested$1ます。

それでは、謎の無名クラスを見てみましょうa.TestNested$1:

Compiled from "TestNested.java"
class a.TestNested$1 {
}

奇妙な - 私はこのクラスが実際に何かをすることを期待していた. それを理解するために、次のコンストラクターを見てみましょうa.TestNested$A

  a.TestNested$A(a.TestNested);
    Code:
       0: aload_0       
       1: aload_1       
       2: putfield      #2                  // Field this$0:La/TestNested;
       5: aload_0       
       6: invokespecial #3                  // Method java/lang/Object."<init>":()V
       9: return        

  private a.TestNested$A(a.TestNested, int);
    Code:
       0: aload_0       
       1: aload_1       
       2: putfield      #2                  // Field this$0:La/TestNested;
       5: aload_0       
       6: invokespecial #3                  // Method java/lang/Object."<init>":()V
       9: return        

  a.TestNested$A(a.TestNested, int, a.TestNested$1);
    Code:
       0: aload_0       
       1: aload_1       
       2: iload_2       
       3: invokespecial #1                  // Method "<init>":(La/TestNested;I)V
       6: return        
}

package-visible constructora.TestNested$A(a.TestNested, int, a.TestNested$1)を見ると、3 番目の引数が無視されていることがわかります。

これで、コンストラクターと匿名の内部クラスについて説明できます。プライベート コンストラクターの可視性の制限を回避するには、追加のコンストラクターが必要です。この追加のコンストラクターは、単純にプライベート コンストラクターに委譲します。ただし、プライベート コンストラクターとまったく同じ署名を持つことはできません。このため、匿名の内部クラスが追加され、シグネチャを持つコンストラクター(int,int)(int,Object). この匿名の内部クラスは一意の署名を作成するためにのみ必要なため、インスタンス化する必要はなく、コンテンツを持つ必要もありません。

于 2013-01-10T21:23:20.947 に答える