静态修饰符如何影响这个代码?
这是我的代码:
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
,但我不明白。
有人可以给我解释吗?
在Java中有两个阶段:1.识别,2.执行
-
在识别阶段,所有的静态variables都被检测到并用默认值初始化。
所以现在的价值是:
A obj=null
num1=0
num2=0
-
第二阶段, 执行 ,从上到下。 在Java中,执行从第一个静态成员开始。
这里你的第一个静态variables是static A obj = new A();
,所以首先创build该variables的对象并调用构造函数,因此num1
和num2
的值为1
。
然后,再次,static int num2=0;
将被执行,这使得num2 = 0;
。
现在,假设你的构造函数是这样的:
private A(){ num1++; num2++; System.out.println(obj.toString()); }
这将抛出一个NullPointerException
因为obj
仍然没有得到class A
的引用。
static
修饰符在应用于variables声明时意味着variables是类variables而不是实例variables。 换句话说……只有一个num1
variables,而且只有一个num2
variables。
(除此之外,静态variables在一些其他语言中就像是一个全局variables,除了它的名字在任何地方都是不可见的,即使它被声明为public static
,只有在当前类中声明了非限定名或一个超类,或者如果它是使用静态导入导入的,那就是区别,一个真正的全局在任何地方都是可见的。
所以当你引用obj.num1
和obj.num2
,实际上是指实际指定为A.num1
和A.num2
的静态variables。 同样,当构造函数递增num1
和num2
,它将分别递增相同的variables。
在你的例子中令人困惑的皱纹是在类初始化。 一个类通过初始化默认初始化所有静态variables,然后按照它们出现在类中的顺序执行声明的静态初始化程序(和静态初始化块)来初始化。 在这种情况下,你有这样的:
static A obj = new A(); static int num1; static int num2=0;
它是这样发生的:
-
静态从他们的默认初始值开始;
A.obj
为null
,A.num1
/A.num2
为零。 -
第一个声明(
A.obj
)创buildA()
的实例,A()
的构造函数增加A.num1
和A.num2
。 当声明完成时,A.num1
和A.num2
都是1
,A.obj
是指新构build的A
实例。 -
第二个声明(
A.num1
)没有初始值设定项,所以A.num1
不会改变。 -
第三个声明(
A.num2
)有一个初始值设定项,赋值为A.num2
。
因此,在类初始化结束时, A.num1
是1
, A.num2
是0
…这就是你的打印语句显示的内容。
这个令人困惑的行为实际上是由于你在静态初始化完成之前创build了一个实例,而你正在使用的构造函数依赖于并修改了一个尚未被初始化的静态。 这个东西,你应该避免在真正的代码做。
1,0是正确的。
当类加载时,所有的静态数据在oder中被初始化,它们被声明。 默认情况下,int是0。
- 第一个A被创build。 num1和num2变成1和1
- 比
static int num1;
什么也没做 - 比
static int num2=0;
这写0到num2
这是由于静态初始化器的顺序。 类中的静态expression式按照自上而下的顺序进行评估。
第一个被调用的是A
的构造函数,它将num1
和num2
都设置为1:
static A obj = new A();
然后,
static int num2=0;
被调用并且再次设置num2 = 0。
这就是为什么num1
是1而num2
是0的原因。
作为一个方面说明,构造函数不应该修改静态variables,这是非常糟糕的devise。 相反,尝试一种不同的方法来实现Java中的Singleton 。
JLS中的一个部分可以find: §12.4.2 。
详细的初始化过程:
接下来,按照文本顺序执行类的类variables初始值设定项和静态初始值设定项或接口的字段初始值设定项, 就像它们是一个单独的块一样,除了最后的类variables和值为编译的接口的字段时间常量首先被初始化
所以这三个静态variables将按照文本顺序逐一进行初始化。
所以
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;
不是一个variables初始值设定项,因为( §8.3.2 ):
如果一个字段声明符包含一个variables初始值设定项,那么它对所声明的variables具有一个赋值语义(§15.26),并且:如果声明符是一个类variables(即,一个静态字段),那么variables初始值设定项是评估和分配执行一次,当这个类被初始化
这个类variables在类创build时被初始化。 这首先发生( §4.12.5 )。
程序中的每个variables在使用其值之前都必须有一个值:创build时,每个类variables,实例variables或数组组件都使用默认值进行初始化(第15.9节,第15.10节):对于字节types,默认值是零,也就是(字节)0的值。 对于shorttypes,默认值为零,也就是(short)0的值。 对于inttypes,默认值为零,即0。对于longtypes,默认值为零,即0L。 对于浮点types,默认值为正零,即0.0f。 对于doubletypes,默认值为正零,即0.0d。 对于chartypes,默认值是空字符,即'\ u0000'。 对于布尔types,默认值为false。 对于所有的引用types(§4.3),默认值为null。
也许这将有助于以这种方式来思考。
类是对象的蓝图。
对象在实例化时可以有variables。
类也可以有variables。 这些被声明为静态的。 所以它们被设置在类而不是对象实例上。
你只能在应用程序中拥有任何一个类,所以它就像专门用于该类的全局存储一样。 这些静态variables当然可以在你的应用程序的任何地方被访问和修改(假设它们是公开的)。
下面是一个“Dog”类的示例,它使用静态variables来跟踪它创build的实例的数量。
“狗”类是云,而橙色框是“狗”实例。
阅读更多
希望这可以帮助!
如果你觉得有些琐事,这个想法是柏拉图最初提出的
Java中使用static关键字主要用于内存pipe理。 我们可以将static关键字应用于variables,方法,块和嵌套类。 静态关键字属于类而不是类的实例。有关静态关键字的简要说明:
上面的许多答案是正确的。 但真的要说明发生了什么,我已经做了一些小的修改如下。
正如上面多次提到的那样,发生的事情是在A类完全加载之前正在创build类A的一个实例。 所以没有观察到正常的“行为”。 这与从可以被覆盖的构造函数中调用方法不太相似。 在这种情况下,实例variables可能不是直观的状态。 在这个例子中,类variables不是直观的状态。
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
java不会初始化任何静态或非静态数据成员的值,直到它不被调用,但它创build它。
所以在这里,当num1和num2将在main中被调用时,它将被初始化为值
num1 = 0 + 1; 和
NUM2 = 0;