Bloque estático en Java no ejecutado

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

Sé que un staticbloque se ejecuta cuando se carga la clase. Pero en este caso, la variable de instancia dentro de la clase Mnoes final, debido a que el staticbloque no se está ejecutando.

¿Por qué es así? Y si quito el final, ¿funcionaría bien?

¿Qué memoria se asignará primero, la static finalvariable o el staticbloque?

Si debido al finalmodificador de acceso la clase no se carga, ¿cómo puede la variable obtener memoria?

Sthita
fuente
1
¿Cuál es el error exacto y el mensaje que recibe?
Patashu
@Patashu, no hay error, es una duda
Sthita

Respuestas:

134
  1. Un static final intcampo es una constante en tiempo de compilación y su valor está codificado en la clase de destino sin una referencia a su origen;
  2. por lo tanto, su clase principal no activa la carga de la clase que contiene el campo;
  3. por lo tanto, el inicializador estático de esa clase no se ejecuta.

En detalle específico, el bytecode compilado corresponde a esto:

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

Tan pronto como lo elimine final, ya no será una constante en tiempo de compilación y el comportamiento especial descrito anteriormente no se aplicará. La Mnoclase se carga como espera y se ejecuta su inicializador estático.

Marko Topolnik
fuente
1
Pero, entonces, ¿cómo se evalúa el valor de la variable final en la clase sin cargar la clase?
Sumit Desai
18
Toda la evaluación ocurre en el momento de la compilación y el resultado final está codificado en todos los lugares que hacen referencia a la variable.
Marko Topolnik
1
Entonces, si en lugar de una variable primitiva, es un Objeto, entonces tal codificación no será posible. ¿No es así? Entonces, en ese caso, ¿se cargará esa clase y se ejecutará el bloque estático?
Sumit Desai
2
Marko, la duda de Sumit también es correcta si en lugar de primitivo, es algún Objeto, entonces tal codificación no será posible. ¿No es así? Entonces, en ese caso, ¿se cargará esa clase y se ejecutará el bloque estático?
Sthita
8
@SumitDesai Exactamente, esto solo funciona para valores primitivos y literales de cadena. Para obtener detalles completos, lea el capítulo correspondiente en la especificación del lenguaje Java
Marko Topolnik
8

La razón por la clase no se carga es que VALes final Y es inicializado con una expresión constante (9090). Si, y solo si, se cumplen esas dos condiciones, la constante se evalúa en tiempo de compilación y se "codifica" cuando sea necesario.

Para evitar que la expresión se evalúe en tiempo de compilación (y para que la JVM cargue su clase), puede:

  • eliminar la palabra clave final:

    static int VAL = 9090; //not a constant variable any more
    
  • o cambie la expresión del lado derecho a algo no constante (incluso si la variable aún es final):

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

Si ve el código de bytes generado usando javap -v Test.class, main () sale como:

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        

Puede ver claramente en " 11: sipush 9090" que el valor final estático se usa directamente, porque Mno.VAL es una constante de tiempo de compilación. Por lo tanto, no es necesario cargar la clase Mno. Por tanto, el bloque estático de Mno no se ejecuta.

Puede ejecutar el bloque estático cargando manualmente Mno como se muestra a continuación:

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
fuente
1
  1. En realidad, no ha extendido esa clase Mno, por lo que cuando la compilación comience, generará una constante de variable VAL y cuando la ejecución comience, cuando esa variable sea necesaria, su carga será desde la memoria. Por lo tanto, no es necesario que su clase haga referencia para que no se ejecute el bock estático.

  2. si la clase Aextiende la clase Mno, el bloque estático se incluye en la clase; Asi hace esto, se ejecuta ese bloque estático. Por ejemplo..

    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
fuente
0

Hasta donde yo sé, se ejecutará en orden de aparición. Por ejemplo :

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

imprimirá

  trace init1
  trace middle
  trace init2

Lo acabo de probar y las estáticas se inicializan (=> imprimir) cuando la clase "Statique" se usa y se "ejecuta" en otra parte del código (en mi caso hice "new Statique ()".

Fabyen
fuente
2
Obtienes este resultado porque estás cargando la Statiqueclase haciendo new Statique(). Mientras que en la pregunta formulada, la Mnoclase no se carga en absoluto.
RAS
@Fabyen, si estoy creando un objeto de Mno en una clase de prueba como esta: Mno anc = New Mno (); entonces está bien, pero el escenario actual no estoy haciendo eso, mi duda es que si estoy eliminando final, el bloque estático se ejecuta bien, de lo contrario no se ejecuta, ¿por qué?
Sthita
1
Sí, la respuesta a continuación es perfecta. En el bytecode de Main.class (usando Mno.VAL), 9090 se encuentra codificado de forma rígida. Elimine final, compile, luego use javap Main, verá getstatic # 16; // El campo Statique.VAL: I . Vuelva a poner final, compile, luego use javap Main, verá sipush 9090 .
Fabyen
1
Debido a que está codificado en Main.class, no hay razón para cargar la clase MNO, por lo tanto, no hay inicialización estática.
Fabyen
Esto responde a la segunda pregunta: "¿Qué memoria se asignará primero, la variable final estática o el bloque estático?" (orden léxico)
Hauke ​​Ingmar Schmidt