Bloc statique en Java non exécuté

87
class Test {
    public static void main(String arg[]) {    
        System.out.println("**MAIN METHOD");
        System.out.println(Mno.VAL); // SOP(9090);
        System.out.println(Mno.VAL + 100); // SOP(9190);
    }

}

class Mno {
    final static int VAL = 9090;
    static {
        System.out.println("**STATIC BLOCK OF Mno\t: " + VAL);
    }
}

Je sais qu'un staticbloc exécuté lors du chargement de la classe. Mais dans ce cas, la variable d'instance à l'intérieur de la classe Mnoest final, à cause de cela, le staticbloc ne s'exécute pas.

Pourquoi est-ce si? Et si je supprimais le final, cela fonctionnerait-il bien?

Quelle mémoire sera allouée en premier, la static finalvariable ou le staticbloc?

Si, en raison du finalmodificateur d'accès, la classe n'est pas chargée, comment la variable peut-elle obtenir de la mémoire?

Sthita
la source
1
Quelle est l'erreur exacte et le message que vous recevez?
Patashu
@Patashu, il n'y a pas d'erreur, c'est un doute
Sthita

Réponses:

133
  1. Un static final intchamp est une constante de compilation et sa valeur est codée en dur dans la classe de destination sans référence à son origine;
  2. donc votre classe principale ne déclenche pas le chargement de la classe contenant le champ;
  3. par conséquent, l'initialiseur statique de cette classe n'est pas exécuté.

Plus précisément, le bytecode compilé correspond à ceci:

public static void main(String arg[]){    
    System.out.println("**MAIN METHOD");
    System.out.println(9090)
    System.out.println(9190)
}

Dès que vous supprimez final, il ne s'agit plus d'une constante de compilation et le comportement spécial décrit ci-dessus ne s'applique pas. La Mnoclasse est chargée comme prévu et son initialiseur statique s'exécute.

Marko Topolnik
la source
1
Mais, alors comment la valeur de la variable finale dans la classe est-elle évaluée sans charger la classe?
Sumit Desai
18
Toutes les évaluations ont lieu au moment de la compilation et le résultat final est codé en dur dans tous les emplacements qui référencent la variable.
Marko Topolnik
1
Donc, si au lieu d'une variable primitive, il s'agit d'un objet, alors un tel codage en dur ne sera pas possible. N'est-ce pas? Donc, dans ce cas, cette classe sera-t-elle chargée et le bloc statique sera-t-il exécuté?
Sumit Desai
2
Marko, le doute de Sumit est vrai aussi si au lieu de primitif, c'est un objet, alors un tel codage en dur ne sera pas possible. N'est-ce pas? Donc, dans ce cas, cette classe sera-t-elle chargée et le bloc statique sera-t-il exécuté?
Sthita
8
@SumitDesai Exactement, cela ne fonctionne que pour les valeurs primitives et les chaînes littérales. Pour plus de détails, lisez le chapitre correspondant dans la spécification du langage Java
Marko Topolnik
8

La raison pour laquelle la classe n'est pas chargée est qu'elle VALest final ET qu'elle est initialisée avec une expression constante (9090). Si, et seulement si, ces deux conditions sont remplies, la constante est évaluée au moment de la compilation et "codée en dur" si nécessaire.

Pour éviter que l'expression ne soit évaluée au moment de la compilation (et pour que la JVM charge votre classe), vous pouvez soit:

  • supprimer le mot clé final:

    static int VAL = 9090; //not a constant variable any more
  • ou changez l'expression de droite en quelque chose de non constant (même si la variable est toujours finale):

    final static int VAL = getInt(); //not a constant expression any more
    static int getInt() { return 9090; }
    
assylies
la source
5

Si vous voyez le bytecode généré en utilisant javap -v Test.class, main () sort comme:

public static void main(java.lang.String[]) throws java.lang.Exception;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String **MAIN METHOD
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        11: sipush        9090
        14: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
        17: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        20: sipush        9190
        23: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
        26: return        

Vous pouvez clairement voir dans " 11: sipush 9090" que la valeur finale statique est directement utilisée, car Mno.VAL est une constante de temps de compilation. Par conséquent, il n'est pas nécessaire de charger la classe Mno. Par conséquent, le bloc statique de Mno n'est pas exécuté.

Vous pouvez exécuter le bloc statique en chargeant manuellement Mno comme ci-dessous:

class Test{
    public static void main(String arg[]) throws Exception {
        System.out.println("**MAIN METHOD");
        Class.forName("Mno");                 // Load Mno
        System.out.println(Mno.VAL);
        System.out.println(Mno.VAL+100);
    }

}

class Mno{
    final static int VAL=9090;
    static{
        System.out.println("**STATIC BLOCK OF Mno\t:"+VAL);
    }
}
Xolve
la source
1
  1. En fait, vous n'avez pas étendu cette classe Mno, donc lorsque la compilation démarre, elle génère la constante de la variable VAL et lorsque l'exécution démarre lorsque cette variable est nécessaire, sa charge est de la mémoire. Il n'est donc pas nécessaire que votre classe référence pour que le bock statique ne soit pas exécuté.

  2. si la classe Aétend la classe Mno, le bloc statique est inclus dans la classe Asi vous faites cela, alors ce bloc statique est exécuté. Par exemple..

    public class A extends Mno {
    
        public static void main(String arg[]){    
            System.out.println("**MAIN METHOD");
            System.out.println(Mno.VAL);//SOP(9090);
            System.out.println(Mno.VAL+100);//SOP(9190);
        }
    
    }
    
    class Mno {
        final static int VAL=9090;
        static {
            System.out.println("**STATIC BLOCK OF Mno\t:"+VAL);
        }
    }
Ketan_Patel
la source
0

Autant que je sache, il sera exécuté par ordre d'apparition. Par exemple :

 public class Statique {
     public static final String value1 = init1();

     static {
         System.out.println("trace middle");
     }
     public static final String value2 = init2();


     public static String init1() {
         System.out.println("trace init1");
         return "1";
     }
     public static String init2() {
         System.out.println("trace init2");
         return "2";
     }
 }

va imprimer

  trace init1
  trace middle
  trace init2

Je viens de le tester et les statiques sont initialisées (=> print) lorsque la classe "Statique" est effectivement utilisée et "exécutée" dans un autre morceau de code (mon cas j'ai fait "new Statique ()".

Fabyen
la source
2
Vous obtenez cette sortie parce que vous chargez la Statiqueclasse en faisant new Statique(). Alors que dans la question posée, la Mnoclasse n'est pas du tout chargée.
RAS
@Fabyen, si je crée un objet de Mno dans la classe de test comme ceci: Mno anc = New Mno (); alors c'est bien, mais le scénario actuel je ne fais pas cela, mon doute est que si je supprime final alors le bloc statique s'exécute bien sinon il ne s'exécute pas, pourquoi donc ??
Sthita
1
Oui, la réponse ci-dessous est parfaite. Dans le bytecode de Main.class (utilisant Mno.VAL), 9090 est trouvé codé en dur. Supprimez final, compilez, puis utilisez javap Main, vous verrez getstatic # 16; // Champ Statique.VAL: Je . Remettez-le final, compilez, puis utilisez javap Main, vous verrez sipush 9090 .
Fabyen
1
Comme il est codé en dur dans Main.class, il n'y a aucune raison de charger la classe MNO, il n'y a donc pas d'initialisation statique.
Fabyen
Cela répond à la deuxième question: "Quelle mémoire sera allouée en premier, la variable finale statique ou le bloc statique?" (ordre lexical)
Hauke ​​Ingmar Schmidt