Подтвердить что ты не робот

Загружает ли Java ClassLoader внутренние классы?

Если у меня есть внутреннее объявление класса, например:

Class A {
    public static class B {
    }
}

а затем:

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

Будет ли загружен внутренний класс A $B? Что делать, если внутренний класс B не был объявлен как "статический"?

4b9b3361

Ответ 1

После компиляции кода нет такой вещи, как внутренний класс. Если вы посмотрите на результаты javac, вы увидите два файла:

A.class
A$B.class

Таким образом, класс B не загружается, когда загружается A, B просто определяется как A.


Изменить

Например, учитывая эти два файла,

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;
        }
    }
    

Обратите внимание, что

  • A$B не имеет ссылки на его родителя, A, а A$C. Это связано с тем, что первый является статическим внутренним классом, а последний - нет, а
  • оба A$B и A$C теперь являются частными классами пакетов.

Таким образом, нестатические внутренние классы могут напрямую ссылаться на свои родительские экземпляры полей и методов и наоборот. (Любые частные поля родительского класса, упомянутые во внутреннем классе, также становятся закрытыми для пакета.)

Далее, посмотрим, какой эффект загружает класс A на A$B и A$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 в командной строке (который запускает метод main main); вывод (с моими добавленными примечаниями)

: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.Main и kuporific.A, и мы не видим загрузки kuporific.A$B или kuporific.A$C.

Ответ 2

Внутренние классы i.e class B не могут существовать вне родительского класса. Сначала вам нужно построить родительский класс i.e class A.

и если вы удалите статику из своего внутреннего класса i.e для нестатического внутреннего класса, вам необходимо передать родительский класс во время построения внутреннего класса.

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

Ответ 3

Нет, вложенный класс не будет загружен в любом случае.

Ответ 4

A ClassLoader не будет загружать класс, если только он не был запрошен (например, с помощью loadClass). При загрузке класса ClassLoader запрашивает ссылочные классы.

Поскольку ваш класс A не ссылается на A.B, A.B не будет загружен, является ли он статическим или нет. (Честно говоря, A ссылается на A.B, но не на то, что вызывает ClassLoader для загрузки A.B.)

Если вы добавите поле типа A.B или используйте тип A.B по-другому (например, как возвращаемый тип метода), на самом деле он будет ссылаться на A.class и, следовательно, будет загружен.

Ответ 5

Ниже приведен код и может проиллюстрировать некоторые другие ответы:

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 - "Outer" инициализируется при запуске программы. Статическая переменная TEST01 инициализируется перед статическим блоком. Внутренний не инициализирован.

2 - вызывается метод "main" и показывает значение "TEST01"; Затем,

3 - System.out показывает ссылку на "Внутренний" . Внутренний не инициализирован, но он загружен (вот почему он имеет ссылку в модели памяти).

4 - Здесь самая интересная часть. Поскольку System.out должен получить доступ к методу "info()" в "Внутренний" (Inner.info()), класс "Внутренний" должен быть инициализирован до, возвращая результат "info" (). Вот почему это шаг 4.

5 - Наконец, у System.out есть все данные, которые ему нужно показать, а затем последняя строка отображается на консоли.

Итак, как это было хорошо указано @sotirios-delimanolis (Загружает ли Java ClassLoader внутренние классы?) загрузка класса отличается от инициализации.