Static x Instance

Talvez a grande maioria dos iniciantes em Java nunca tenha ouvido falar em bloco estático ou ainda mais em bloco de instancia. Há ainda os métodos estáticos e as variáveis estáticas, e veremos um pouco de como funciona cada um deles e a diferença para um bloco de instancia. 

1. Static

Em Java temos variáveis, blocos e métodos do tipo static, static está entre algumas das palavras chaves do Java. Uma variável, um bloco ou mesmo um método do tipo static, não fazem parte da instancia de uma classe e sim da classe. Quando criamos uma classe com membros estáticos, esses membros serão os primeiros códigos a serem carregados na classe, mesmo que não haja nenhuma inst ncia dessa classe.

O método estático mais conhecido é sem dúvida nenhuma o main(), e dentro de um método ou de um bloco de inicialização estático só podemos acessar variáveis ou outros métodos estáticos, para acessarmos membros não estáticos precisamos então criar uma instancia da classe.

Quando criamos variáveis e blocos estáticos em uma classe, devemos saber que eles serão executados de cima para baixo, ou melhor, do primeiro para o último criado.

Variáveis e Métodos Estáticos: O modificador static é usado para criar variáveis e métodos que irão existir independentemente de quaisquer inst ncias criadas para a classe. Em outras palavras, os membros static existem antes mesmo de você criar uma nova inst ncia de uma classe, e haverá apenas uma cópia do membro static, independentemente do número de inst ncia dessa classe. Fonte: Certificação Sun para Programadores Java, Guia de Estudo.

Listagem 1. Bloco de inicialização e método main
public class TutorialTest {

    static {
        System.out.println("TutorialTest.static");
    }

    public static void main(String[] args) {
        //sem nenhuma instancia
    }
}

Na execução do código da listagem 1, será impresso na tela a frase que está dentro do bloco estático. Veja que em nenhum momento esse bloco foi chamado dentro do método main(), ele simplesmente é executado na criação da classe e como possui uma linha de saída em tela, ela é executada.

O método main() tem uma diferença para outros métodos estáticos, ele é por padrão o método que diz ao Java que essa é a classe principal do projeto, assim o JRE saberá qual método será responsável pela inicialização de uma aplicação. Em uma aplicação precisamos de pelo menos um método main() para executá-la.

A diferença entre um método estático e um bloco estático é bem visível, o método possui nome e um tipo de retorno e pode ainda possuir uma lista de argumentos, enquanto o bloco estático não possui nada disso.

Na listagem 2 foi adicionado mais um bloco estático e também um novo método estático. O método estático pode ser chamado sem a criação de uma instancia da classe, já que ele não faz parte da instancia da classe.

Listagem 2. Executando outro método static
public class TutorialTest {

    static {
        System.out.println("TutorialTest.static bloco 1");
    }

    static {
        metodo();
        System.out.println("TutorialTest.static bloco 2");
    }

    public static void main(String[] args) {
        metodo();
    }

    private static void metodo() {
        System.out.println("TutorialTest.metodo");
    }
}

Veja que na listagem 2 o novo bloco estático faz uma chamada ao método estático metodo(), sem que haja a necessidade de usar uma instancia e o método main() também faz uma chamada da mesma forma. Na execução desta classe teremos os seguintes passos:

  1. Executa e imprime o 1° bloco estático
  2. Executa o 2° bloco estático
  3. Executa a chamada a metodo() e imprime o conteúdo de método()
  4. Imprime o conteúdo do 2° bloco estático
  5. Executa e imprime o conteúdo do método main()

A saída então será como na figura 1:

Figura 01

Figura 01

Faça um teste e mude a ordem na classe do bloco 1 pelo bloco 2 e verá a diferença na saída.

Listagem 3. Adicionando variável static
public class TutorialTest {

    static int cont = 0;

    static {
        System.out.println("TutorialTest.static bloco 1");
    }

    static {
        metodo();
        System.out.println("TutorialTest.static bloco 2");
    }

    public static void main(String[] args) {
        metodo();
    }

    private static void metodo() {
        cont = cont + 1;
        System.out.println("TutorialTest.metodo" + cont);
    }
}

Observe na listagem 3 que foi adicionada uma variável estática do tipo int e inicializada com zero. Também foi adicionado no método metodo() um incremento desta variável.

Veja na figura 2 como será a saída na execução da classe:

Figura 02

Figura 02

Neste caso a variável quando é estática funciona mais ou menos como uma variável do tipo global em linguagens como Pascal ou Delphi. Embora no Java não exista um conceito de variável global, este caso seria talvez o que teríamos mais próximo disto.

Então quando a classe é criada, a variável cont é inicializada com zero e na chamada de metodo() ela é incrementada, impressa com o valor incrementado e esse incremento continua valendo para toda a execução da classe, até que ela seja finalizada com o término da execução.

2. Bloco de instancia

Como o nome mesmo diz, esse bloco é referente a instancia de uma classe e nada mais que isso. Sempre que um bloco de instancia for criado, ele será executado em qualquer execução de uma instancia desta classe.

Listagem 4. Utilizando Herança
public class TutorialTestB {

    {
        System.out.println("TutorialTestB.instance");
    }

    public static void main(String[] args) {
        new TutorialTestB();
        TutorialTestB ttB = new TutorialTestB();
    }
}

Conforme na listagem 4, a execução do código ira imprimir na tela duas vezes a frase: TutorialTestB.instance. O bloco de instancia sempre é executado quando é executada uma nova instancia da classe, então se tivessemos mais instancias, seria impressa o mesmo número de frases do que teriamos de instancias e se não tivessemos nenhuma instancia, não teriamos nada impresso.

3. Membros estáticos na mesma classe que bloco de instancia

Podemos ter na mesma classe tanto membros estáticos quanto bloco de instancia, assim, teria alguns resultados diferentes dos anteriores já citados até agora no artigo.

Veja na listagem 5

Listagem 5. Estáticos com bloco de inst ncia
public class TutorialTestC {
    static int cont = 0;

    static {
        System.out.println("TutorialTestC.static: " + cont);
        cont = 1;
        System.out.println("TutorialTestC.static: " + cont);
    }

    {
        System.out.println("TutorialTestC.instance: " + cont++);
    }

    public static void main(String[] args) {
        TutorialTestC t1 = new TutorialTestC();
        TutorialTestC t2 = new TutorialTestC();
        TutorialTestC t3 = new TutorialTestC();
    }
}

Se você fizer um teste de mesa, deverá obter o mesmo resultado que na figura 3.

Figura 03

Figura 03

Caso não tenha entendido, os seguintes passos ocorreram:

  1. A classe inicializou e carregou a variável cont com zero
  2. O bloco de inicialização estático foi executado
  3. A 1ª linha do bloco estático foi executa e imprimiu a frase e o valor atual de cont, que é até então zero (0)
  4. A 2ª linha do bloco estático é executada e atribui o valor 1cont
  5. A 3ª linha do bloco estático foi executa e imprimiu a frase e o valor atual de cont, que é até então um (1)
  6. Agora o método main()  é executado
  7. É criada uma inst ncia da classe e atribuída a t1, quando essa instancia é criada, o bloco de instancia é executado e é impressa na tela a frase mais o valor atual de cont, que é 1, e então cont é incrementado
  8. É criada uma instancia da classe e atribuída a t2, quando essa instancia é criada, o bloco de instancia é executado e é impressa na tela a frase mais o valor atual de cont, que é 2, e então cont é incrementado
  9. É criada uma instancia da classe e atribuída a t3, quando essa instancia é criada, o bloco de instancia é executado e é impressa na tela a frase mais o valor atual de cont, que é 3, e então cont é incrementado
  10. A última instrução do método main() é executada e imprime a frase e o valor final de cont que é quatro (4)

4. Utilizando herança

Quando utilizamos herança haverá alguns pontos importantes a serem observados, quem não sabe muito bem como funciona as chamadas por herança, pode dar uma lida no artigo, Declaração de Construtores em Java.

Veja na listagem 6 que foram criadas duas novas classes, e um estende a outra.

Listagem 6. Herança
public class TutorialTestD {
    static int cont = 0;

    static {
        System.out.println("TutorialTestD.static intializer");
    }

    {
        System.out.println("TutorialTestD.instance initializer");
    }
}

public class TutorialTestE extends TutorialTestD {
    static int cont = 0;

    static {
       System.out.println("TutorialTestE.static intializer");
    }

    {
       System.out.println("TutorialTestE.instance initializer");
    }

    public static void main(String[] args) {
        new TutorialTestE();
    }
}

Na listagem 6 foi criado um método main() na classe TutorialTestE que estende, ou herda, a classe TutorialTestD. Nossa execução terá como resultado a saída referente a figura 4.

Figura 04

Figura 04

Veja que houve uma variação entre a impressão dos membros da super-classe, TutorialTestD, com os membros da sub-classe, TutorialTestE.

Isto ocorre devido as regras de herança, tentarei explicar passo a passo o que ocorreu:

  1. Quando executamos a sub-classe, a primeira chamada é feita a super-classe, conforme as regras de herança. Como na super-classe temos um bloco estático, ele acaba sendo executado antes que o bloco estático da sub-classe.
  2. Após a execução do bloco estático da super-classe, ocorre a execução do bloco estático da sub-classe.
  3. O execução chega ao método main() e executa a instancia da sub-classe, assim, é feita uma chamada ao construtor (que está implícito neste caso) e o construtor faz uma chamada a super() que executa por primeiro o bloco de instancia da super-classe.
  4. Por fim, é executado o bloco de instancia da sub-classe.

5. Instancia em método estático

Como já citado neste artigo, para acessar variáveis ou métodos não estáticos dentro de um método estático, por exemplo, o método main(), necessita-se de uma instancia da classe.

Listagem 7. Inst ncia em método estático
public class TutorialTestD {
    String classe = "TutorialTestD";

    static int cont = 0;

    static {
        System.out.println("TutorialTestD.static intializer");
    }

    {
        System.out.println("TutorialTestD.instance initializer");
    }
}

public class TutorialTestE extends TutorialTestD {
    String classe = "TutorialTestE";

    static int cont = 0;

    static {
       System.out.println("TutorialTestE.static intializer");
    }

    {
       System.out.println("TutorialTestE.instance initializer");
    }

    public static void main(String[] args) {
        TutorialTestD td = new TutorialTestE();
        System.out.println("Classe: " + td.classe);

        TutorialTestE te = new TutorialTestE();
        System.out.println("Classe: " + te.classe);     }
}

Na listagem 7 foi adicionada uma variável não estática, do tipo String chamada classe e inicializada em cada uma das duas classes com o nome da própria classe.

No método main(), foram criadas duas instancias da classe TutorialTestE. A primeira utilizamos polimorfismo e na segunda fazemos uma atribuição a própria classe TutorialTestE.

Isso implica em resultados diferentes, conforme a figura 5:

Figura 05

Figura 05

Os passos referentes aos blocos de instancia e aos blocos estáticos já se sabe como funciona, mas e nas instancias do método main(), como ocorreu esses resultados?

  1. Na primeira instancia, como foi feito uso de polimorfismo, acaba sendo impresso o nome da super-classe.
  2. Na segunda instancia, como é uma instancia normal, temos o resultado do nome da própria classe instanciada.

Conclusão

É importante ressaltar que todo acesso a métodos ou variáveis não estáticas só será possível através de uma inst ncia da classe ( new Classe(); ), mas métodos ou variáveis estáticas podem ser acessadas tanto de um método estático quanto de um método não estático, isso por que eles são visíveis para toda a classe e não para instancias da classe.

Um bloco de instancia é sempre executado quando temos a instancia de uma classe. Quando temos uma instancia, fazemos uma chamada a nosso construtor da classe e ele executa uma chamada a super(), assim, o bloco de instancia será executado sempre após a execução de super(), por isso, quando temos herança o bloco da super-classe é sempre executado antes do bloco da sub-classe.

Download PDF

Ballem

Marcio Ballem é bacharel em Sistemas de Informação pelo Centro Universitário Franciscano em Santa Maria/RS. Tem experiência com desenvolvimento Delphi e Java em projetos para gestão pública e acadêmica. Possui certificação em Java, OCJP 6.

Você pode gostar...