web-dev-qa-db-ja.com

Java ClassLoaderは内部クラスをロードしますか?

次のような内部クラス宣言がある場合:

Class A {
    public static class B {
    }
}

に続く:

Class<?> implClass = getClass().getClassLoader().loadClass("A");

A $ B内部クラスもロードされますか? B内部クラスが「静的」として宣言されていない場合はどうなりますか?

23
Janik Zikovsky

コードがコンパイルされると、内部クラスのようなものはありません。 javacの結果を見ると、次の2つのファイルがあります。

A.class
A$B.class

したがって、Bがロードされると、クラスAはロードされず、Bhappensが定義されます。 Aで。


編集

たとえば、これら2つのファイルがある場合、

package kuporific;

public class A {
    private static class B {}
    private class C {}
}

およびbuild.gradleファイル(便宜上):

apply plugin: 'Java'

まず、gradle buildを実行してビルドします。次に、結果のJARファイル(build/libsにあります)を解凍します。

├── META-INF
│   └── MANIFEST.MF
└── kuporific
    ├── A$B.class
    ├── A$C.class
    └── A.class

各ファイルを開くと(たとえば、IntelliJで)、コンパイラーが何をしたかがわかります。

  • A.class

    package kuporific;
    
    public class A {
        public A() {
        }
    
        private class C {
            public C() {
            }
        }
    
        private static class B {
            public B() {
            }
        }
    }
    
  • A$B.class

    package kuporific;
    
    class A$B {
        private A$B() {
        }
    }
    
  • A$C.class

    package kuporific;
    
    import kuporific.A;
    
    class A$C {
        private A$C(A this$0) {
            this.this$0 = this$0;
        }
    }
    

そのことに注意してください

  1. A$Bには親Aへの参照がありませんが、A$Cには参照があります。これは、前者が静的内部クラスであり、後者がそうではないためです。
  2. A$BA$Cの両方がパッケージプライベートクラスになりました。

これは、非静的内部クラスが親インスタンスのフィールドとメソッドを直接参照できる方法であり、その逆も同様です。 (内部クラスで参照される親クラスのプライベートフィールドもパッケージプライベートになります。)

次に、クラスAの読み込みがA$BA$Cにどのような影響を与えるかを見てみましょう。

まず、次のJavaクラスを追加します:

package kuporific;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        Main.class.getClassLoader().loadClass("kuporific.A");
    }
}

次に、以下をbuild.gradleファイルに追加します。

apply plugin: 'application'
mainClassName = 'kuporific.Main'
applicationDefaultJvmArgs = ["-verbose:class"]

-verbose:classは、JVMによってロードされるすべてのクラスを出力します( Java-JVMにロードされるすべてのクラスのリストを取得する を参照)。

コマンドラインでgradle runを実行します(mainMainメソッドを実行します)。出力(私の追加されたメモを含む)は

:compileJava
:processResources UP-TO-DATE
:classes
:run
[Opened /Library/Java/JavaVirtualMachines/jdk1.8.0_20.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded Java.lang.Object from /Library/Java/JavaVirtualMachines/jdk1.8.0_20.jdk/Contents/Home/jre/lib/rt.jar]
# Lots of omitted output...
[Loaded kuporific.Main from file:/tmp/build/classes/main/]
        ^ here!
[Loaded Sun.launcher.LauncherHelper$FXHelper from /Library/Java/JavaVirtualMachines/jdk1.8.0_20.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded Java.lang.Class$MethodArray from /Library/Java/JavaVirtualMachines/jdk1.8.0_20.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded kuporific.A from file:/tmp/build/classes/main/]
        ^ here!
[Loaded Java.lang.Shutdown from /Library/Java/JavaVirtualMachines/jdk1.8.0_20.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded Java.lang.Shutdown$Lock from /Library/Java/JavaVirtualMachines/jdk1.8.0_20.jdk/Contents/Home/jre/lib/rt.jar]

BUILD SUCCESSFUL

Total time: 6.502 secs

kuporific.Mainkuporific.Aがいつロードされたかを確認できますが、kuporific.A$Bkuporific.A$Cもロードされていません。

30
kuporific

内部クラス、つまりclass Bは親クラスの外部に存在できません。親クラスを作成する必要があります。つまり、class A 最初。

また、内部クラスから静的を削除する場合、つまり非静的内部クラスの場合、内部クラスの構築中に親クラスを渡す必要があります。

Object a = Class.forName("A").newInstance();    //object of outer class

//object of inner class
Object b = implClass.getDeclaredConstructor(new Class[] { a.getClass() })
        .newInstance(new Object[] { a });
4
rachana

以下のコードは実行可能であり、他のいくつかの答えを説明できます。

public class Outer
{

   private static final String TEST01 = "I'm TEST01";

   static
   {
        System.out.println("1 - Initializing class Outer, where TEST01 = " + TEST01);
   }

   public static void main(String[] args)
   {
       System.out.println("2 - TEST01       --> " + TEST01 );
       System.out.println("3 - Inner.class  --> " + Inner.class);
       System.out.println("5 - Inner.info() --> " + Inner.info() );
   }

   private static class Inner
   {

       static
       {
          System.out.println("4 - Initializing class Inner");
       }

       public static String info()
       {
           return "I'm a method in Inner";
       }
    }
}

特にこの行のシーケンス番号に注意してください。

System.out.println("5 - Inner.info() --> " + Inner.info() );

プログラムを実行すると、コンソールに次の結果が表示されます。

1 - Initializing class Outer, where TEST01 = I'm TEST01
2 - TEST01       --> I'm TEST01
3 - Inner.class  --> class com.javasd.designpatterns.tests.Outer$Inner
4 - Initializing class Inner
5 - Inner.info() --> I'm a method in Inner

各ステップのもう少し詳細:

1-プログラムを実行すると、「外部」が初期化されます。静的変数TEST01は、静的ブロックの前に初期化されます。内部は初期化されていません。

2-「main」メソッドが呼び出され、「TEST01」の値が表示されます。その後、

3-System.outは「Inner」への参照を示しています。 内部は初期化されていませんが、ロードされました(そのため、メモリモデルに参照があります)。

4-これが最も興味深い部分です。 System.outは「Inner」(Inner.info())の「info()」メソッドにアクセスする必要があるため、「Inner」クラスはbefore 'info()'メソッドの結果を返します。これがステップ4である理由です。

5-最後に、System.outには表示する必要のあるすべてのデータがあり、最後の行がコンソールに表示されます。

したがって、@ sotirios-delimanolis( Java ClassLoaderは内部クラスをロードしますか? )によってよく指摘されているように、クラスのロードは初期化とは異なります。

1
Almir Campos

ClassLoaderは、要求されない限り(たとえば、loadClassを使用して)、クラスをロードしません。クラスのロード中に、ClassLoaderは参照されるクラスを要求します。

クラスAA.Bを参照しないため、静的であるかどうかに関係なく、A.Bはロードされません。 (正直なところ、AA.Bを参照しますが、ClassLoaderA.Bをロードするような方法ではありません。)

タイプA.Bのフィールドを追加するか、タイプA.Bを別の方法で(たとえば、メソッドの戻り値の型として)使用すると、実際にはA.classで参照されるため、ロードされます。

1
Tobias

いいえ、どちらの場合も、ネストされたクラスはロードされません。