Bloco estático em Java não executado

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

Eu sei que um staticbloco é executado quando a classe é carregada. Mas, neste caso, a variável de instância dentro da classe Mnoé final, por isso o staticbloco não está executando.

Por que? E se eu removesse o final, funcionaria bem?

Qual memória será alocada primeiro, a static finalvariável ou o staticbloco?

Se devido ao finalmodificador de acesso a classe não for carregada, então como a variável pode obter memória?

Sthita
fonte
1
Qual é o erro exato e a mensagem que você recebe?
Patashu
@Patashu, não há erro, é uma dúvida
Sthita

Respostas:

132
  1. Um static final intcampo é uma constante de tempo de compilação e seu valor é codificado na classe de destino sem uma referência à sua origem;
  2. portanto, sua classe principal não aciona o carregamento da classe que contém o campo;
  3. portanto, o inicializador estático dessa classe não é executado.

Em detalhes específicos, o bytecode compilado corresponde a este:

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

Assim que você remove final, ele não é mais uma constante de tempo de compilação e o comportamento especial descrito acima não se aplica. A Mnoclasse é carregada conforme o esperado e seu inicializador estático é executado.

Marko Topolnik
fonte
1
Mas, então, como o valor da variável final na classe é avaliado sem carregar a classe?
Sumit Desai
18
Toda avaliação acontece em tempo de compilação e o resultado final é codificado em todos os lugares que fazem referência à variável.
Marko Topolnik
1
Portanto, se em vez de uma variável primitiva, for algum Objeto, essa codificação não será possível. Não é? Então, nesse caso, essa classe será carregada e o bloco estático será executado?
Sumit Desai
2
Marko, a dúvida de Sumit está certa também se em vez de primitivo, for algum objeto, então tal codificação não será possível. Não é? Então, nesse caso, essa classe será carregada e o bloco estático será executado?
Sthita
8
@SumitDesai Exatamente, isso funciona apenas para valores primitivos e literais de string. Para obter todos os detalhes, leia o capítulo relevante na Especificação da linguagem Java
Marko Topolnik
8

A razão pela qual a classe não é carregada é que VALé final E ela é inicializada com uma expressão constante (9090). Se, e somente se, essas duas condições forem atendidas, a constante é avaliada em tempo de compilação e "codificada" quando necessário.

Para evitar que a expressão seja avaliada em tempo de compilação (e para fazer a JVM carregar sua classe), você pode:

  • remova a palavra-chave final:

    static int VAL = 9090; //not a constant variable any more
  • ou altere a expressão do lado direito para algo não constante (mesmo se a variável ainda for final):

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

Se você javap -v Test.classvir bytecode gerado usando , main () será assim:

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        

Você pode ver claramente em " 11: sipush 9090" que o valor final estático é usado diretamente, porque Mno.VAL é uma constante de tempo de compilação. Portanto, não é necessário carregar a classe Mno. Portanto, o bloco estático de Mno não é executado.

Você pode executar o bloco estático carregando manualmente o Mno como abaixo:

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
fonte
1
  1. Na verdade, você não estendeu essa classe Mno, então quando começar a compilação ela irá gerar uma constante da variável VAL e quando a execução começar quando essa variável for necessária, ela será carregada da memória. Portanto, não é necessário que sua classe faça referência para que o bock estático não seja executado.

  2. se a classe Aestende a classe Mno, o bloco estático é incluído na classe. ASe você fizer isso, esse bloco estático será executado. Por exemplo..

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

Pelo que eu sei, ele será executado em ordem de aparecimento. Por exemplo :

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

irá imprimir

  trace init1
  trace middle
  trace init2

Acabei de testá-lo e os estáticos são inicializados (=> imprimir) quando a classe "Statique" é realmente usada e "executada" em outro trecho de código (no meu caso eu fiz "new Statique ()".

Fabyen
fonte
2
Você está obtendo essa saída porque está carregando a Statiqueclasse fazendo new Statique(). Enquanto na pergunta feita, a Mnoclasse não foi carregada.
RAS
@Fabyen, se estou criando um objeto de Mno em uma classe de teste como este: Mno anc = New Mno (); então está tudo bem, mas o cenário atual eu não estou fazendo isso, minha dúvida é se eu estou removendo o final então o bloco estático executa bem caso contrário ele não executa, por que então ??
Sthita
1
Sim, a resposta abaixo é perfeita. No bytecode de Main.class (usando Mno.VAL), 9090 é encontrado codificado. Remova o final, compile e use o javap Main, você verá getstatic # 16; // O campo Statique.VAL: I . Coloque de volta o final, compile e use javap Main, você verá sipush 9090 .
Fabyen
1
Como está codificado em Main.class, não há razão para carregar a classe MNO, portanto, não há inicialização estática.
Fabyen
Isso responde à segunda pergunta: "Qual memória será alocada primeiro, a variável final estática ou o bloco estático?" (ordem lexical)
Hauke ​​Ingmar Schmidt