¿Cómo afecta el modificador estático a este código?

109

Aquí está mi 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);
    }
}

El resultado es 1 0, pero no puedo entenderlo.

¿Alguien me lo puede explicar?

lirui
fuente
10
¡Buena pregunta! ¿Qué debemos aprender de esto? ¡No lo hagas! ;)
isnot2bad

Respuestas:

116

En Java tienen lugar dos fases: 1. Identificación, 2. Ejecución

  1. En la fase de identificación , todas las variables estáticas se detectan y se inicializan con valores predeterminados.

    Entonces ahora los valores son:
    A obj=null
    num1=0
    num2=0

  2. La segunda fase, la ejecución , comienza de arriba a abajo. En Java, la ejecución comienza desde los primeros miembros estáticos.
    Aquí está su primera variable estática static A obj = new A();, por lo que primero creará el objeto de esa variable y llamará al constructor, de ahí el valor de num1y se num2convierte 1.
    Y luego, nuevamente, static int num2=0;se ejecutará, lo que hace num2 = 0;.

Ahora, suponga que su constructor es así:

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

Esto arrojará un NullPointerExceptioncomo objtodavía no tiene una referencia class A.

Shoaib Chikate
fuente
11
Lo extenderé: mueva la línea de static A obj = new A();abajo static int num2=0;y debería obtener 1 y 1.
Thomas
2
Lo que todavía me confunde es el hecho de que, aunque num1 no tiene una inicialización explícita, ES (implícitamente) inicializado con 0. Realmente no debería haber diferencia entre la inicialización explícita e implícita ...
isnot2bad
@ isnot2bad La "inicialización implícita" ocurre como parte de la declaración. Declaración ocurre antes de la asignación no importa el orden en que los presenta en. A obj = new A(); int num1; int num2 = 0;Obtiene convertido en esto: A obj; int num1; int num2; obj = new A(); num2 = 0;. Java hace esto, por lo que num1, num2se define en el momento en que llega al new A()constructor.
Hans Z
31

Lo que el staticmodificador significa cuando se aplica a una declaración de variable es que la variable es una variable de clase en lugar de una variable de instancia. En otras palabras ... solo hay una num1variable, y solo una num2variable.

(Aparte: una variable estática es como una variable global en algunos otros idiomas, excepto que su nombre no es visible en todas partes. Incluso si se declara como a public static, el nombre no calificado solo es visible si se declara en la clase actual o una superclase , o si se importa mediante una importación estática. Esa es la distinción. Un verdadero global es visible sin calificación en cualquier lugar).

Entonces, cuando se refiere a obj.num1y obj.num2, en realidad se refiere a las variables estáticas cuyas designaciones reales son A.num1y A.num2. Y de manera similar, cuando el constructor incrementa num1y num2, está incrementando las mismas variables (respectivamente).

La arruga confusa en su ejemplo está en la inicialización de la clase. Una clase se inicializa primero por defecto inicializando todas las variables estáticas y luego ejecutando los inicializadores estáticos declarados (y los bloques de inicializadores estáticos) en el orden en que aparecen en la clase. En este caso, tienes esto:

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

Sucede así:

  1. Las estáticas comienzan con sus valores iniciales predeterminados; A.objes nully A.num1/ A.num2son cero.

  2. La primera declaración ( A.obj) crea una instancia de A(), y el constructor para Aincrementos A.num1y A.num2. Cuando la declaración se completa, A.num1y A.num2son ambos 1, y se A.objrefiere a la Ainstancia recién construida .

  3. La segunda declaración ( A.num1) no tiene inicializador, por lo A.num1que no cambia.

  4. La tercera declaración ( A.num2) tiene un inicializador que asigna cero a A.num2.

Por lo tanto, al final de la inicialización de la clase, A.num1es 1y A.num2es 0... y eso es lo que muestran sus declaraciones impresas.

Este comportamiento confuso se debe en realidad al hecho de que está creando una instancia antes de que se complete la inicialización estática, y que el constructor que está utilizando depende y modifica una estática que aún no se ha inicializado. Esto es algo que debes evitar hacer en código real.

Stephen C
fuente
16

1,0 es correcto.

Cuando se carga la clase, todos los datos estáticos se inicializan o se declaran. Por defecto, int es 0.

  • se crea primero A. num1 y num2 se convierten en 1 y 1
  • que static int num1;no hace nada
  • que static int num2=0;esto escribe 0 en num2
Leonidos
fuente
9

Se debe al orden de los inicializadores estáticos. Las expresiones estáticas de las clases se evalúan en orden descendente.

El primero en ser llamado es el constructor de A, que establece num1y num2ambos en 1:

static A obj = new A();

Luego,

static int num2=0;

se llama y establece num2 = 0 nuevamente.

Por eso num1es 1 y num2es 0.

Como nota al margen, un constructor no debe modificar variables estáticas, eso es un diseño muy malo. En su lugar, pruebe un enfoque diferente para implementar un Singleton en Java .

Domi
fuente
6

Se puede encontrar una sección en JLS: §12.4.2 .

Procedimiento de inicialización detallado:

9.A continuación, ejecute los inicializadores de variables de clase y los inicializadores estáticos de la clase, o los inicializadores de campo de la interfaz, en orden textual, como si fueran un solo bloque, excepto que las variables de clase final y los campos de interfaces cuyos valores se compilan -Las constantes de tiempo se inicializan primero

Entonces, las tres variables estáticas se inicializarán una por una en orden textual.

Entonces

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;

Si cambio el orden a:

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

El resultado será 1,1.

Tenga en cuenta que static int num1;no es un inicializador de variable porque ( §8.3.2 ):

Si un declarador de campo contiene un inicializador de variable, entonces tiene la semántica de una asignación (§15.26) a la variable declarada y: Si el declarador es para una variable de clase (es decir, un campo estático), entonces el inicializador de variable es evaluado y la asignación realizada exactamente una vez, cuando se inicializa la clase

Y esta variable de clase se inicializa cuando se crea la clase. Esto sucede primero ( §4.12.5 ).

Cada variable en un programa debe tener un valor antes de que se use su valor: Cada variable de clase, variable de instancia o componente de matriz se inicializa con un valor predeterminado cuando se crea (§15.9, §15.10): Para el tipo byte, el valor predeterminado es cero, es decir, el valor de (byte) 0. Para el tipo corto, el valor predeterminado es cero, es decir, el valor de (corto) 0. Para el tipo int, el valor predeterminado es cero, es decir, 0. Para el tipo long, el valor predeterminado es cero, es decir, 0L. Para el tipo flotante, el valor predeterminado es cero positivo, es decir, 0.0f. Para el tipo double, el valor predeterminado es cero positivo, es decir, 0.0d. Para el tipo char, el valor predeterminado es el carácter nulo, es decir, '\ u0000'. Para el tipo booleano, el valor predeterminado es falso. Para todos los tipos de referencia (§4.3), el valor predeterminado es nulo.

StarPinkER
fuente
2

Quizás ayude pensar en ello de esta manera.

Las clases son planos de objetos.

Los objetos pueden tener variables cuando se instancian.

Las clases también pueden tener variables. Estos se declaran estáticos. Por lo tanto, se establecen en la clase en lugar de en las instancias del objeto.

Solo puede tener uno de cualquier clase en una aplicación, por lo que es una especie de almacenamiento global específicamente para esa clase. Por supuesto, se puede acceder a estas variables estáticas y modificarlas desde cualquier lugar de su aplicación (suponiendo que sean públicas).

Este es un ejemplo de una clase "Perro" que usa una variable estática para rastrear el número de instancias que ha creado.

La clase "Perro" es la nube, mientras que las casillas naranjas son instancias de "Perro".

Clase de perro

Lee mas

¡Espero que esto ayude!

Si te apetece una trivia, esta idea fue presentada por primera vez por Platón.

Goran
fuente
1

La palabra clave estática se utiliza en Java principalmente para la gestión de memoria. Podemos aplicar palabras clave estáticas con variables, métodos, bloques y clases anidadas. La palabra clave estática pertenece a la clase que a la instancia de la clase. Para una breve explicación sobre la palabra clave estática:

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

rachana
fuente
0

Muchas de las respuestas anteriores son correctas. Pero realmente para ilustrar lo que está sucediendo, he realizado algunas pequeñas modificaciones a continuación.

Como se mencionó varias veces anteriormente, lo que sucede es que se crea una instancia de la clase A antes de que la clase A esté completamente cargada. Por tanto, no se observa lo que se considera el "comportamiento" normal. Esto no es muy diferente a llamar a métodos desde un constructor que se puede anular. En ese caso, las variables de instancia pueden no estar en un estado intuitivo. En este ejemplo, las variables de clase no están en un 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);
    }
}

La salida es

Constructing singleton instance of A
Setting num2 to 0
1
0
w25r
fuente
0

java no inicializa el valor de ningún miembro de datos estático o no estático hasta que no se llama, pero lo crea.

de modo que aquí, cuando se llame num1 y num2 en main, se inicializará con valores

num1 = 0 + 1; y

num2 = 0;

deepak tiwari
fuente