什么是static
通常来说,创建类之后,只有执行new来创建对象时,数据存储空间才被分配,这个类的方法才能被外界调用。但是有两种特殊情况用这个方法不能解决:
- 只想为某特定域分配单一存储空间,而不去考虑是否创建对象或创建多少对象。
- 希望方法不于包含它的类的任何对象关联。即:即使没有创建对象,也可以调用这个方法。
static关键字可以满足这两个需要。即使从未创建某个类的任何对象,也可以调用其static方法或访问static域。
static作用于字段
static作用于某个字段时,会改变数据创建的方式,其本质是:一个static字段对每个类来说都只有一份存储空间,而非static字段则是对每个对象都有一个存储空间。
class StaticTest {
static int i = 47;
}
class Static {
public static void main(String[] args) {
StaticTest st1 = new StaticTest();
StaticTest st2 = new StaticTest();
System.out.println("st1:" + st1.i + " st2:" + st2.i);
StaticTest.i++;
System.out.println("st1Increment:" + st1.i + " st2Increment:" + st2.i);
}
} /* output:
st1:47 st2:47
st1Increment:48 st2Increment:48
*///:~
在示例中,即使创建了两个StaticTest对象,StaticTest.i也只有一份存储空间(即:st1.i 和st2.i 指向同一个存储空间)。
这也是为什么我们可以通过类名去直接引用静态成员:
StaticTest.i++;
如上使用类名是引用static变量的首选方式(另一种方式是通过对象定位,如st1.i
),因为它强调了变量的static结构,某些情况下为编译器进行优化提供了更好的机会。
static作用于方法
static作用于方法时,不像其作用于字段时有那么大的差别。static方法的重要用法是 在不创建任何对象的前提下就可以调用static方法。这一点对定义main()方法十分重要,因为它是运行一个应用时的切入点。
静态数据的初始化
在类的内部,即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化。
public class ExplicitStatic {
public static void main(String[] args) {
System.out.println("Inside main");
Cups.cup1.f(99); //(1)
}
// static Cups cups1 = new Cups(); //(2)
// static Cups cups2 = new Cups(); //(2)
}
class Cups {
static Cup cup1;
static Cup cup2;
static {
cup1 = new Cup(1);
cup2 = new Cup(2);
}
Cups() {
System.out.println("Cups()");
}
}
class Cup {
Cup(int marker) {
System.out.println("Cup(" + marker + ")");
}
void f(int marker) {
System.out.println("f(" + marker + ")");
}
}
此时(执行(1)时)的输出为:
Inside main
Cup(1)
Cup(2)
f(99)
当注释掉(1),执行(2)时,输出为:
Cup(1)
Cup(2)
Cups()
Cups()
Inside main
可见,初始化的顺序是:先静态对象,后非静态对象,且静态初始化动作只进行一次 。
假设有名为Dog的类,总结一下对象的创建过程:
- 构造器(constructor)实际上是static静态方法。首次创建类型为Dog的对象时,或者Dog类的静态方法/静态域被首次访问时,Java解释器必须查找类路径,定位Dog.class文件。
- 载入Dog.class并创建Class对象,此时有关静态初始化的所有动作都会执行。静态初始化只在Class对象首次加载时进行一次。
- 用new Dog()创建对象时,首先在堆上为Dog对象分配足够的存储空间。
- 这块存储空间被清零,自动将Dog对象所有基本数据类型设置成默认值,引用设置成null.
- 执行出现于字段定义处的初始化动作。
- 执行构造器。
class SelfCounter {
private static int count;
private int id = count++;
public String toString() { return "SelfCounter " + id; }
}
class WithFinalFields {
final SelfCounter scf = new SelfCounter();
static final SelfCounter scsf = new SelfCounter();
public String toString() {
return "scf = " + scf + "\nscsf = " + scsf;
}
}
public class E18_FinalFields {
public static void main(String args[]) {
System.out.println("First object:");
System.out.println(new WithFinalFields());
System.out.println("Second object:");
System.out.println(new WithFinalFields());
}
} /* Output:
First object:
scf = SelfCounter 1
scsf = SelfCounter 0
Second object:
scf = SelfCounter 2
scsf = SelfCounter 0
*///:~
non-static field 的初始化(第 3 行),目的是初始化对象的 non-static field。所以它们在对象“被创建”的时候,才会执行。总共只有一个第 9 行的 self_counter_static_final 对象会被创建。
整个程序总共创建了 3 个 SelfCounter 对象,顺序分别是:
- WithFinalFields 的 static field
- SelfCounter 对象被创建,执行第 3 行 (id=0) - 第 1 个 WithFinalFields 对象的 non-static field
- SelfCounter 对象被创建,执行第 3 行 (id=1) - 第 2 个 WithFinalFields 对象的 non-static field
- SelfCounter 对象被创建,执行第 3 行 (id=2)
继承中的初始化
基类在导出类构造器可以访问它之前,就已经完成了初始化。
参考:《Thinking in Java 4th Edition》第二、五、七章