33
package com.test;

public class OuterClass {
    public class InnerClass {
        public class InnerInnerClass {

        }
    }

    public class InnerClass2 {

    }

    //this class should not exist in OuterClass after dummifying
    private class PrivateInnerClass {
        private String getString() {
            return "hello PrivateInnerClass";
        }
    }

    public String getStringFromPrivateInner() {
        return new PrivateInnerClass().getString();
    }
}

javacコマンドラインで。を使用して実行するとSun JVM 1.6.0_20、このコードは6つの.classファイルを生成します。

OuterClass.class
OuterClass $ 1.class
OuterClass $ InnerClass.class
OuterClass $ InnerClass2.class
OuterClass $ InnerClass $ InnerInnerClass.class
OuterClass $ PrivateInnerClass.class

EclipseでJDTを実​​行すると、5つのクラスしか生成されません。

OuterClass.class
OuterClass $ 1.class
OuterClass $ InnerClass.class
OuterClass $ InnerClass2.class
OuterClass $ InnerClass $ InnerInnerClass.class
OuterClass $ PrivateInnerClass.class

逆コンパイルすると、OuterClass$1.class何も含まれません。この余分なクラスはどこから来て、なぜ作成されたのですか?

4

5 に答える 5

26

polygenelubricantsの小さいスニペットを使用しています。

バイトコードにはネストされたクラスの概念がないことを忘れないでください。ただし、バイトコードはアクセス修飾子を認識しています。コンパイラがここで回避しようとしている問題は 、メソッドinstantiate()がの新しいインスタンスを作成する必要があることですPrivateInnerClass。ただし、のコンストラクターOuterClassにはアクセスできませんPrivateInnerClassOuterClass$PrivateInnerClassパブリックコンストラクターなしでパッケージ保護されたクラスとして生成されます)。

では、コンパイラは何ができるのでしょうか?PrivateInnerClass明らかな解決策は、パッケージで保護されたコンストラクターを持つように変更することです。ここでの問題は、これにより、クラスとインターフェイスする他のコードが、PrivateInnerClass明示的にプライベートとして宣言されている場合でも、の新しいインスタンスを作成できるようになることです。

これを防ぐために、javacコンパイラはちょっとしたトリックを行っています。PrivateInnerClass通常のコンストラクタを他のクラスから見えるようにする代わりに、非表示のままにします(実際には、まったく定義していませんが、外部からは同じことです)。 。代わりに、特別なタイプの追加パラメーターを受け取る新しいコンストラクターを作成しますOuterClass$1

さて、あなたが見るとinstantiate()、それはその新しいコンストラクターを呼び出します。実際にnullは、(タイプのOuterClass$1)2番目のパラメーターとして送信されます。このパラメーターは、このコンストラクターが呼び出される必要があることを指定するためにのみ使用されます。

では、なぜ2番目のパラメーターに新しいタイプを作成するのでしょうか。たとえば、使ってみませんObjectか?これは、通常のコンストラクターと区別するためにのみ使用され、nullとにかく渡されます。そして答えは、OuterClass$1OuterClassのプライベートであるようOuterClass$PrivateInnerClassに、必要なパラメータータイプの1つであるOuterClass$1、が非表示になっているため、正当なコンパイラーはユーザーが特別なコンストラクターを呼び出すことを決して許可しないということです。

JDTのコンパイラは、同じ問題を解決するために別の手法を使用していると思います。

于 2010-05-21T15:48:06.633 に答える
12

答えはわかりませんが、それを確認して、スニペットを次のように減らすことができます。

public class OuterClass {
    private class PrivateInnerClass {
    }
    public void instantiate() {
        new PrivateInnerClass();
    }
}

これにより、OuterClass$1.class

Compiled from "OuterClass.java"
class OuterClass$1 extends java.lang.Object{
}

そしてここjavap -cにありますOuterClass.class

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

public void instantiate();
  Code:
   0:   new     #2; //class OuterClass$PrivateInnerClass
   3:   dup
   4:   aload_0
   5:   aconst_null
   6:   invokespecial #3; //Method OuterClass$PrivateInnerClass."<init>":
                          //(LOuterClass;LOuterClass$1;)V
   9:   pop
   10:  return

}

そしてのためにOuterClass$PrivateInnerClass

Compiled from "OuterClass.java"
class OuterClass$PrivateInnerClass extends java.lang.Object{
final OuterClass this$0;

OuterClass$PrivateInnerClass(OuterClass, OuterClass$1);
  Code:
   0:   aload_0
   1:   aload_1
   2:   invokespecial   #1; //Method "<init>":(LOuterClass;)V
   5:   return

}

ご覧のとおり、合成されたコンストラクターはOuterClass$1引数を取ります。

したがってjavac、タイプが追加の引数を取るデフォルトのコンストラクターを作成します。$1そのデフォルトの引数の値はです5: aconst_null


$1次のいずれかが当てはまる場合、それは作成されないことがわかりました。

  • あなたが作るpublic class PrivateInnerClass
  • のnullaryコンストラクターを宣言しますPrivateInnerClass
  • newまたはあなたはそれに電話しません
  • おそらく他のもの(例えばstatic、ネストされているなど)。

おそらく関連している

  • バグID:4295934:プライベート内部クラスをコンパイルすると、間違ったディレクトリに匿名クラスファイルが作成されます

testというディレクトリに次のソースを作成します。

package test;
public class testClass
{
    private class Inner
    {
    }
    public testClass()
    {
        Inner in = new Inner();
    }
}

親ディレクトリからファイルをコンパイルしますjavac test/testClass.java

ファイルtestClass$1.classが現在のディレクトリに作成されていることに注意してください。作成されたものもあるので、なぜこのファイルが作成されたのかわかりませんtest/testClass$Inner.class

評価

このtestClass$1.classファイルは、プライベート内部クラスのプライベートコンストラクターの「アクセスコンストラクター」に必要なダミークラス用です testClass$Inner。Dissassemblyは、このクラスの完全修飾名が正しく記録されていることを示しているため、クラスファイルが間違ったディレクトリに配置される理由は不明です。

于 2010-05-21T15:15:39.087 に答える
7

多遺伝子潤滑剤の回答に基づくと、この不思議なクラスは、にアクセスできないため、他の人(つまり、外部からOuterClass)がをインスタンス化するのを妨げると思います。OuterClass$PrivateInnerClassOuterClass$1

于 2010-05-21T15:32:58.790 に答える
4

検索した後、私はこのリンクを見つけました。http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6378717

コメントは、特定のリンクで利用可能なソースコードを参照しています。

これはバグではありません。

コンパイラはアクセスの問題を解決しようとしています。内部クラスTest.Requestはプライベートであるため、そのコンストラクターはプライベートです。これは、-privateをjavapに使用するとわかります。

$ javap -private Test \$Request「Test.java」からコンパイルされたfinalクラスTest$Request extends java.lang.Object {final Test this $ 0; プライベートTest$Request(Test); Test $ Request(Test、Test $ 1); }

ただし、JVMは、Coucouの匿名サブクラス(Test $ 1)がこのプライベートコンストラクターにアクセスすることを許可しません。これは、ネストされたクラスに関しては、JVMとJavaプログラミング言語の根本的な違いです。この言語により、ネストされたクラスは、囲んでいるクラスのプライベートメンバーにアクセスできます。

もともと、ネストされたクラスが言語に追加されたとき、この問題の解決策はコンストラクターパッケージをプライベートにすることであり、次のようになりました。

$ javap -private Test \$Request「Test.java」からコンパイルされたfinalクラスTest$Request extends java.lang.Object {final Test this $ 0; Test $ Request(Test); }

ただし、これにより、必要のないときにコンストラクターにアクセスできるという問題が発生する可能性があります。この問題に対処するために、現在のソリューションが発明されました。「実際の」コンストラクターはプライベートのままになります。

private Test$Request(Test);

ただし、他のネストされたクラスがこのコンストラクターを呼び出すことを許可する必要があります。したがって、アクセスコンストラクターを提供する必要があります。ただし、このアクセスコンストラクターは「実際の」コンストラクターとは異なる必要があります。この問題を解決するために、コンパイラーはアクセスコンストラクターに追加のパラメーターを追加します。この追加パラメーターのタイプは、ユーザーが書き込んだものと競合しない一意のものでなければなりません。したがって、明らかな解決策は、匿名クラスを追加し、それを2番目のパラメーターのタイプとして使用することです。

Test$Request(Test, Test$1);

ただし、コンパイラは巧妙であり、匿名クラスが存在する場合はそれを再利用します。匿名クラスを含まないように例を変更すると、コンパイラーが匿名クラスを作成することがわかります。

public abstract class Test {private final class Request {} private final class OtherRequest {Request test(){return new Request(); }}}

プライベートコンストラクターへのアクセスがない場合、コンパイラーは、この例の動作を説明するアクセスコンストラクターを生成する必要はありません。

パブリック抽象クラステスト{プライベート最終クラスリクエスト{}}

于 2016-06-09T19:22:22.107 に答える
0

もう1つのスポット-OuterClass$1ユーザーによってすでに宣言されている場合は、OuterClass$PrivateInnerClassとにかくコンストラクター引数としてそれを持ちます:

public class OuterClass { 

    ... 

    public String getStringFromPrivateInner() { 
        PrivateInnerClass c = new PrivateInnerClass();
        Object o = new Object() {};
        return null;
    }
}

-

public java.lang.String getStringFromPrivateInner();
  コード:
   0:新しい#2; //クラスOuterClass$PrivateInnerClass
   3:重複
   4:aload_0
   5:aconst_null
   6:invokespecial#3; //メソッドOuterClass$PrivateInnerClass。"":
(LOuterClass; L OuterClass $ 1 ;)V
   9:astore_1
   10:新しい#4; //クラスOuterClass$1
   13:重複
   14:aload_0
   15:invokespecial#5; //メソッドOuterClass$1。"":( LOuterClass;)V
   18:astore_2
   19:aconst_null
   20:帰り
于 2010-05-21T15:43:22.137 に答える