Question Java ClassLoader charge-t-il les classes internes?


Si j'ai une déclaration de classe interne telle que:

Class A {
    public static class B {
    }
}

suivi par:

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

La classe interne A $ B sera-t-elle également chargée? Que se passe-t-il si la classe interne B n'a pas été déclarée comme "statique"?


20
2017-07-02 18:31


origine


Réponses:


Une fois le code compilé, il n'y a pas de classe interne. Si vous regardez les résultats de javac, vous verrez deux fichiers:

A.class
A$B.class

Donc classe B n'est pas chargé quand A est chargé, B juste arrive à définir dans A.


modifier

Par exemple, étant donné ces deux fichiers,

package kuporific;

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

et un build.gradle fichier (pour plus de commodité):

apply plugin: 'java'

Tout d'abord, construire en cours d'exécution gradle build. Ensuite, dézippez le fichier JAR résultant (situé dans build/libs):

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

L'ouverture de chaque fichier (dans IntelliJ, par exemple), révèle ce que le compilateur a fait:

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

Remarquerez que

  1. A$B n'a pas de référence à son parent, A, tandis que A$C Est-ce que. C’est parce que le premier est une classe interne statique et que le second ne l’est pas.
  2. tous les deux A$B et A$C sont maintenant paquet privé Des classes.

C'est ainsi que les classes internes non statiques peuvent référencer directement les champs et méthodes de leur instance parente, et vice versa. (Tous les champs privés de la classe parente référencés dans une classe interne sont également transformés en package privé.)

Ensuite, voyons quel effet charge la classe A a sur A$B et A$C.

Tout d'abord, ajoutez la classe Java suivante:

package kuporific;

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

Maintenant, ajoutez ce qui suit au build.gradle fichier:

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

le -verbose:class renvoie toutes les classes chargées par la JVM (voir Java - Récupère une liste de toutes les classes chargées dans la JVM).

Courir gradle run sur la ligne de commande (qui exécute le main méthode de Main); la sortie (avec mes notes ajoutées) est

: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

On peut voir quand kuporific.Main et kuporific.A ont été chargés, et nous ne voyons pas non plus kuporific.A$B ou kuporific.A$C en cours de chargement


25
2017-07-02 18:41



Classes internes i.e class B ne peut pas exister en dehors de la classe parente. Vous devez construire la classe parente i.e class A premier.

et si vous retirez statique de votre classe interne i.e pour classe interne non statique , vous devez passer la classe parente pendant la construction de la classe interne.

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
2017-07-02 18:47



Non, la classe imbriquée ne sera pas chargée dans les deux cas.


1
2017-07-02 18:34



UNE ClassLoader ne chargera pas une classe, sauf si elle a été demandée (par exemple en utilisant loadClass). Lors du chargement d'une classe, un ClassLoader demandera des classes référencées.

Comme votre classe A ne fait pas référence A.B, A.B ne sera pas chargé, que ce soit statique ou non. (Pour être honnête, A fait référence A.B, mais pas de manière à provoquer la ClassLoader charger A.B.)

Si vous ajoutez un champ de type A.B ou utiliser le type A.B d'une autre manière (par exemple en tant que type de retour de méthode), il sera effectivement référencé dans A.class et donc être chargé.


1
2017-07-02 18:42



Le code ci-dessous est exécutable et peut illustrer certaines des autres réponses:

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

S'il vous plaît, faites attention aux numéros de séquence, surtout dans cette ligne:

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

Lorsque vous exécutez le programme, vous verrez le résultat suivant sur la console:

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

Un peu plus de détails pour chaque étape:

1 - 'Outer' est initialisé lorsque vous exécutez le programme. La variable statique TEST01 est initalisée avant le bloc statique. L'intérieur n'est pas initialisé.

2 - La méthode 'main' est appelée et affiche la valeur de 'TEST01'; puis,

3 - Le System.out affiche la référence à 'Inner'. L'intérieur n'est pas initialisé, mais il a été chargé (c'est pourquoi il a une référence dans le modèle de mémoire).

4 - Voici la partie la plus intéressante. Étant donné que System.out doit accéder à la méthode 'info ()' dans 'Inner' (Inner.info ()), la classe 'Inner' doit être initialisée avant renvoyer le résultat de la méthode 'info ()'. C'est pourquoi c'est l'étape 4.

5 - Enfin, le System.out a toutes les données dont il a besoin pour afficher, puis la dernière ligne est affichée sur la console.

Donc, comme le soulignait @ sotirios-delimanolis ( Java ClassLoader charge-t-il les classes internes? ) le chargement d’une classe est différent de l’initialiser.


1
2017-08-22 23:22