Como o modificador estático afeta este código?


109

Aqui está o meu código:

class A {
    static A obj = new A();
    static int num1;
    static int num2=0;

    private A() {
        num1++;
        num2++;
    }
    public static A getInstance() {
        return obj;
    }
}

public class Main{
    public static void main(String[] arg) {
        A obj = A.getInstance();
        System.out.println(obj.num1);
        System.out.println(obj.num2);
    }
}

A saída é 1 0, mas não consigo entender.

Alguém pode me explicar?


10
Boa pergunta! O que devemos aprender com isso: Não faça isso! ;)
isnot2bad

Respostas:


116

Em Java ocorrem duas fases: 1. Identificação, 2. Execução

  1. Em identificação fase de , todas as variáveis ​​estáticas são detectadas e inicializadas com valores padrão.

    Então agora os valores são:
    A obj=null
    num1=0
    num2=0

  2. A segunda fase, execução , começa de cima para baixo. Em Java, a execução começa a partir dos primeiros membros estáticos.
    Aqui é sua primeira variável estática static A obj = new A();, portanto, primeiro ela criará o objeto dessa variável e chamará o construtor, daí o valor de num1e num2se torna 1.
    E então, novamente, static int num2=0;será executado, o que torna num2 = 0;.

Agora, suponha que seu construtor seja assim:

 private A(){
    num1++;
    num2++;
    System.out.println(obj.toString());
 }

Isso vai lançar um NullPointerExceptioncomo objainda não tenho uma referência de class A.


11
Vou estender: mova a linha static A obj = new A();abaixo static int num2=0;e você deve obter 1 e 1.
Thomas

2
O que ainda me confunde é o fato de que, embora num1 não tenha uma inicialização explícita, ele É (implicitamente) inicializado com 0. Realmente não deve haver nenhuma diferença entre a inicialização explícita e implícita ...
isnot2bad

@ isnot2bad "inicialização implícita" ocorre como parte da declaração. Declaração acontece antes da atribuição não importa que ordem você apresentá-los em. A obj = new A(); int num1; int num2 = 0;Se transformou em esta: A obj; int num1; int num2; obj = new A(); num2 = 0;. Java faz isso, portanto, num1, num2é definido no momento em que você chega ao new A()construtor.
Hans Z

31

O que o staticmodificador significa quando aplicado a uma declaração de variável é que a variável é uma variável de classe em vez de uma variável de instância. Em outras palavras ... há apenas uma num1variável e apenas uma num2variável.

(À parte: uma variável estática é como uma variável global em algumas outras linguagens, exceto que seu nome não é visível em todos os lugares. Mesmo se for declarado como umpublic static , o nome não qualificado só é visível se for declarado na classe atual ou em uma superclasse , ou se for importado usando uma importação estática. Essa é a diferença. Um verdadeiro global é visível sem qualificação em qualquer lugar.)

Então, quando você se refere obj.num1e obj.num2, na verdade você está se referindo às variáveis estáticas cujas designações real são A.num1e A.num2. E da mesma forma, quando o construtor incrementa num1e num2, está incrementando as mesmas variáveis ​​(respectivamente).

O problema confuso em seu exemplo está na inicialização da classe. Uma classe é inicializada por padrão inicializando primeiro todas as variáveis ​​estáticas e, em seguida, executando os inicializadores estáticos declarados (e blocos inicializadores estáticos) na ordem em que aparecem na classe. Nesse caso, você tem:

static A obj = new A();
static int num1;
static int num2=0;

Acontece assim:

  1. A estática começa com seus valores iniciais padrão; A.objé nulle A.num1/ A.num2são zero.

  2. A primeira declaração ( A.obj) cria uma instância de A(), e o construtor para Aincrementos A.num1e A.num2. Quando a declaração é concluída, A.num1e A.num2são ambos 1, e A.objse refere à Ainstância recém-construída .

  3. A segunda declaração ( A.num1) não tem inicializador, então A.num1não muda.

  4. A terceira declaração ( A.num2) tem um inicializador que atribui zero a A.num2.

Assim, no final da inicialização da classe, A.num1é 1e A.num2é 0... e é isso que suas instruções de impressão mostram.

Esse comportamento confuso se deve ao fato de que você está criando uma instância antes da conclusão da inicialização estática e de que o construtor que está usando depende e modifica uma estática que ainda não foi inicializada. Isso é algo que você deve evitar fazer em código real.


16

1,0 está correto.

Quando a classe é carregada, todos os dados estáticos são inicializados antes de serem declarados. Por padrão, o int é 0.

  • primeiro A é criado. num1 e num2 tornando-se 1 e 1
  • do static int num1;que não faz nada
  • do que static int num2=0;isso escreve 0 a num2

9

É devido à ordem dos inicializadores estáticos. As expressões estáticas nas classes são avaliadas de cima para baixo.

O primeiro a ser chamado é o construtor de A, que define num1e num2ambos como 1:

static A obj = new A();

Então,

static int num2=0;

é chamado e define num2 = 0 novamente.

É por isso que num1é 1 enum2 é 0.

Como uma observação lateral, um construtor não deve modificar variáveis ​​estáticas, isso é um design muito ruim. Em vez disso, tente uma abordagem diferente para implementar um Singleton em Java .


6

Uma seção no JLS pode ser encontrada: §12.4.2 .

Procedimento de inicialização detalhado:

9. Em seguida, execute os inicializadores de variável de classe e inicializadores estáticos da classe ou os inicializadores de campo da interface, em ordem textual, como se fossem um único bloco, exceto que as variáveis ​​de classe finais e os campos de interfaces cujos valores são compilados - as constantes de tempo são inicializadas primeiro

Portanto, as três variáveis ​​estáticas serão inicializadas uma a uma na ordem textual.

assim

static A obj = new A();
//num1 = 1, num2 = 1;
static int num1;
//this is initilized first, see below.
static int num2=0;
//num1 = 1, num2 = 0;

Se eu mudar o pedido para:

static int num1;
static int num2=0;
static A obj = new A();

O resultado será 1,1.

Observe que o static int num1;não é um inicializador de variável porque ( §8.3.2 ):

Se um declarador de campo contém um inicializador de variável, então ele tem a semântica de uma atribuição (§15.26) para a variável declarada e: Se o declarador é para uma variável de classe (ou seja, um campo estático), então o inicializador de variável é avaliada e a atribuição realizada exatamente uma vez, quando a classe é inicializada

E essa variável de classe é inicializada quando a classe é criada. Isso acontece primeiro ( §4.12.5 ).

Cada variável em um programa deve ter um valor antes de seu valor ser usado: Cada variável de classe, variável de instância ou componente de matriz é inicializado com um valor padrão quando é criado (§15.9, §15.10): Para o tipo byte, o valor padrão é zero, ou seja, o valor de (byte) 0. Para o tipo short, o valor padrão é zero, ou seja, o valor de (short) 0. Para o tipo int, o valor padrão é zero, ou seja, 0. Para o tipo long, o valor padrão é zero, ou seja, 0L. Para o tipo float, o valor padrão é zero positivo, ou seja, 0,0f. Para o tipo double, o valor padrão é zero positivo, ou seja, 0,0d. Para o tipo char, o valor padrão é o caractere nulo, ou seja, '\ u0000'. Para o tipo booleano, o valor padrão é falso. Para todos os tipos de referência (§4.3), o valor padrão é nulo.


2

Talvez ajude pensar dessa forma.

As classes são projetos para objetos.

Os objetos podem ter variáveis ​​quando são instanciados.

As classes também podem ter variáveis. Eles são declarados como estáticos. Portanto, eles são definidos na classe em vez das instâncias do objeto.

Você só pode ter um de qualquer classe em um aplicativo, então é uma espécie de armazenamento global especificamente para essa classe. Essas variáveis ​​estáticas podem, é claro, ser acessadas e modificadas de qualquer lugar em seu aplicativo (assumindo que sejam públicas).

Aqui está um exemplo de uma classe "Dog" que usa variável estática para rastrear o número de instâncias que criou.

A classe "Dog" é a nuvem, enquanto as caixas laranja são instâncias de "Dog".

Aula de cachorro

consulte Mais informação

Espero que isto ajude!

Se você gosta de curiosidades, essa ideia foi introduzida pela primeira vez por Platão


1

A palavra-chave static é usada em java principalmente para gerenciamento de memória. Podemos aplicar palavras-chave estáticas com variáveis, métodos, blocos e classes aninhadas. A palavra-chave static pertence à classe do que à instância da classe. Para uma breve explicação sobre a palavra-chave estática:

http://www.javatpoint.com/static-keyword-in-java


0

Muitas das respostas acima estão corretas. Mas, realmente, para ilustrar o que está acontecendo, fiz algumas pequenas modificações abaixo.

Conforme mencionado várias vezes acima, o que está acontecendo é que uma instância da classe A está sendo criada antes que a classe A seja totalmente carregada. Portanto, o que é considerado o 'comportamento' normal não é observado. Isso não é muito diferente de chamar métodos de um construtor que pode ser substituído. Nesse caso, as variáveis ​​de instância podem não estar em um estado intuitivo. Neste exemplo, as variáveis ​​de classe não estão em um estado intuitivo.

class A {
    static A obj = new A();
    static int num1;
    static int num2;
    static {
        System.out.println("Setting num2 to 0");
        num2 = 0;
    }

    private A() {
        System.out.println("Constructing singleton instance of A");
        num1++;
        num2++;
    }

    public static A getInstance() {
        return obj;
    }
}

public class Main {

    public static void main(String[] arg) {
        A obj = A.getInstance();
        System.out.println(obj.num1);
        System.out.println(obj.num2);
    }
}

A saída é

Constructing singleton instance of A
Setting num2 to 0
1
0

0

java não inicializa o valor de nenhum membro de dados estático ou não estático até que não seja chamado, mas o crie.

de modo que aqui quando num1 e num2 forem chamados em main então serão inicializados com os valores

num1 = 0 + 1; e

num2 = 0;

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.