这是我的代码:
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);
}
}
输出为1 0
,但我无法理解。
有人可以向我解释一下吗?
答案 0 :(得分:115)
在Java中,有两个阶段:1。识别,2。执行
在识别阶段,检测所有静态变量并使用默认值进行初始化。
所以现在价值是:
A obj=null
num1=0
num2=0
第二阶段执行,从上到下开始。在Java中,执行从第一个静态成员开始
这里你的第一个静态变量是static A obj = new A();
,所以首先它将创建该变量的对象并调用构造函数,因此num1
和num2
的值变为1
。<登记/>
然后,再次执行static int num2=0;
,即num2 = 0;
。
现在,假设你的构造函数是这样的:
private A(){
num1++;
num2++;
System.out.println(obj.toString());
}
由于NullPointerException
obj
尚未获得class A
的引用,因此会抛出{{1}}。
答案 1 :(得分:31)
static
修饰符在应用于变量声明时的含义是变量是类变量而不是实例变量。换句话说......只有一个num1
变量,只有一个num2
变量。
(旁白:静态变量就像一些其他语言中的全局变量,除了它的名称在任何地方都不可见。即使它被声明为public static
,也是不合格的name只有在当前类或超类中声明,或者使用静态导入导入时才可见。这就是区别。真正的全局在任何地方都是可见的。)
因此,当您引用obj.num1
和obj.num2
时,您实际上指的是 静态变量,其真实名称为A.num1
和A.num2
。同样,当构造函数递增num1
和num2
时,它会递增相同的变量(分别)。
示例中令人困惑的皱纹是在类初始化中。首先通过默认初始化所有静态变量初始化类,然后按照它们在类中出现的顺序执行声明的静态初始化程序(和静态初始化程序块)。在这种情况下,你有这个:
static A obj = new A();
static int num1;
static int num2=0;
就像这样:
静态以其默认初始值开始; A.obj
为null
,A.num1
/ A.num2
为零。
第一个声明(A.obj
)创建A()
的实例,A
的构造函数递增A.num1
和A.num2
。声明完成后,A.num1
和A.num2
都是1
,而A.obj
是指新构建的A
实例。
第二个声明(A.num1
)没有初始值设定项,因此A.num1
不会更改。
第三个声明(A.num2
)有一个初始值设定项,为A.num2
指定零。
因此,在类初始化结束时,A.num1
为1
而A.num2
为0
...这就是您的打印语句显示的内容。
这种令人困惑的行为实际上是因为您在静态初始化完成之前创建了一个实例,并且您使用的构造函数依赖于并修改尚未成为的静态初始化。这是你应该避免在实际代码中做的事情。
答案 2 :(得分:16)
1,0是正确的。
当加载类时,所有静态数据都在oder中初始化,然后声明它们。默认情况下,int为0.
static int num1;
什么都不做static int num2=0;
这会将0写入num2 答案 3 :(得分:9)
这是由于静态初始化器的顺序。类中的静态表达式以自上而下的顺序进行评估。
第一个被调用的是A
的构造函数,它将num1
和num2
都设置为1:
static A obj = new A();
然后,
static int num2=0;
调用并再次设置num2 = 0。
这就是num1
为1且num2
为0的原因。
作为旁注,构造函数不应该修改静态变量,这是非常糟糕的设计。相反,尝试使用implementing a Singleton in Java的不同方法。
答案 4 :(得分:6)
可以找到JLS中的一个部分:§12.4.2。
详细的初始化程序:
9.接下来,执行类的类变量初始值设定项和静态初始值设定项,或接口的字段初始值设定项, 在文本顺序中,好像它们只是一个块,除此之外 最终的类变量和值为的接口的字段 编译时常量首先初始化
因此,三个静态变量将按文本顺序逐个初始化。
所以
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;
如果我将订单更改为:
static int num1;
static int num2=0;
static A obj = new A();
结果为1,1
。
请注意,static int num1;
不是变量初始值设定项,因为(§8.3.2):
如果字段声明符包含变量初始值设定项,则它具有 对赋值变量的赋值(第15.26节)的语义,以及:If 声明器用于类变量(即静态字段),然后 评估变量初始化程序并执行赋值 当类初始化时,恰好一次
在创建类时初始化此类变量。这首先发生(§4.12.5)。
程序中的每个变量在其值之前必须具有值 used:每个类变量,实例变量或数组组件 在创建时使用默认值初始化(§15.9,§15.10): 对于type byte,默认值为零,即值 (字节)0。对于short类型,默认值为零,即值 (短)0。对于int类型,默认值为零,即0 type long,默认值为零,即0L。对于类型float, 默认值为正零,即0.0f。对于double类型, 默认值为正零,即0.0d。对于char类型, 默认值是空字符,即'\ u0000'。对于类型 boolean,默认值为false。对于所有参考类型(§4.3), 默认值为null。
答案 5 :(得分:2)
也许以这种方式思考它会有所帮助。
类是对象的蓝图。
对象在实例化时可以有变量。
类也可以有变量。这些被声明为静态。所以它们是在类而不是对象实例上设置的。
您只能拥有应用程序中任何类的一个类,因此它有点像专门用于该类的全局存储。当然,可以从应用程序的任何位置访问和修改这些静态变量(假设它们是公共的)。
这是“Dog”类的示例,它使用静态变量来跟踪它创建的实例数。
“Dog”类是云,而Orange框是“Dog”实例。
希望这有帮助!
如果您觉得有些琐事,这个想法最初是由Plato
引入的答案 6 :(得分:1)
静态关键字在java中主要用于内存管理。我们可以将static关键字与变量,方法,块和嵌套类一起应用。 static关键字属于类而不是类的实例。有关静态关键字的简要说明:
答案 7 :(得分:0)
java不会初始化任何静态或非静态数据成员的值,直到它没有被调用但是会创建它。
所以这样,当在main中调用num1和num2时,它将被初始化为值
num1 = 0 + 1;和
NUM2 = 0;
答案 8 :(得分:0)
上面的许多答案都是正确的。但是真的要说明发生了什么我在下面做了一些小修改。
正如上面多次提到的,正在发生的是在A类完全加载之前创建的A类实例。因此,没有观察到被认为是正常的“行为”。这与从可以重写的构造函数中调用方法并没有太大的不同。在这种情况下,实例变量可能不是直观的状态。在此示例中,类变量不处于直观状态。
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);
}
}
输出
Constructing singleton instance of A
Setting num2 to 0
1
0