static关键字可以修饰静态变量和静态方法。静态变量一旦创建,可以一直存放在内存中,直到JVM停止。静态方法可以不用实例化对象,就可以使用该对象的静态方法。这篇文章主要是研究static变量如何被创建、以及在内存中如何管理、以及static关键字使用过程中可能会遇到的风险。static关键字的使用应该非常慎重,因为一个类中的static变量只会初始化一次,不会因为类的初始化而回到初值;而对于静态方法,千万不要用于那些可能被设计为多态的方法上。
一. 何时创建static变量。
沿用了《Java编程思想》中的一个例子。
class Bowl { Bowl (int marker) { System.out.println("Bowl(" + marker + ")"); } void f1 (int marker) { System.out.println("f1(" + marker + ")"); } } class Table { static Bowl bowl1 = new Bowl(1); Table() { System.out.println("Table()"); bowl2.f1(1); } void f2(int marker) { System.out.println("f2(" + marker + ")"); } static Bowl bowl2 = new Bowl(2); } class Cupboard { Bowl bowl3 = new Bowl(3); static Bowl bowl4 = new Bowl(4); Cupboard() { System.out.println("Cupboard()"); bowl4.f1(2); } void f3(int marker) { System.out.println("f3(" + marker + ")"); } static Bowl bowl5 = new Bowl(5); } public class StaticInitialization { public static void main(String[] args) { System.out.println("Create new Cupboard in main"); Cupboard cupboard = new Cupboard(); table.f2(0); cupboard.f3(1); } static Table table = new Table(); }
运行结果:
Bowl(1) Bowl(2) Table() f1(1) Create new Cupboard in main Bowl(4) Bowl(5) Bowl(3) Cupboard() f1(2) f2(0) f3(1)
Bowl类使得看到类的创建,Table类和Cupboard类在他们类定义时加入了Bowl类型的静态数据成员。注意,在静态数据定义之前,Cupboard类首先定义了一个Bowl类型的非静态成员变量bowl3.
由静态输出可见,在main函数执行之前,StaticInitialization类的Table类型的成员变量首先初始化,类的静态变量bowl1和bowl2被初始化,然后执行Table的构造函数。当执行到main函数体之后,某个Cupboard类型的变量被初始化,Cupboard类中的静态成员变量bowl4和bowl5被初始化,然后是非静态成员变量bowl3被初始化,然后执行Cupboard的构造函数。
由上面静态输出的分析可以得到一下结果:
1. 静态变量只有在被创建或者第一次被访问的时候被初始化。Table最先被初始化,因为作为StaticInitialization的成员变量。Cupboard类并没有在main函数执行前得到初始化,是因为该类当时未被创建或者访问。
2. 同一个类中,静态成员变量最先被初始化。Cupboard类中的成员变量被初始化时,作为静态成员变量的bowl4和bowl5先被初始化,然后才是非静态成员变量bowl3。
二 static变量在内存中的管理
静态变量是一直存放在内存的变量,只要这个类被加入到运行时中,这个变量就已经存在于内存中。然后该变量可以被引入该类的所有对象访问,但是该变量在内存中应该被如何管理呢?
依然是上面的那个例子。Bowl类使得看到类的创建,Cupboard类在类定义时加入了Bowl类型的静态数据成员和非静态数据成员变量。
class Bowl { public int marker; Bowl (int marker) { this.marker = marker; System.out.println("Bowl(" + marker + ")"); } } class Cupboard { static int marker = 0; Bowl bowl3 = new Bowl(3); static Bowl bowl4 = new Bowl(4); Cupboard() { System.out.println("Cupboard()"); } } public class StaticInitialization { public static void main(String[] args) { Cupboard cupboard1 = new Cupboard(); System.out.println("Cupboard.marker is " + Cupboard.marker); cupboard1.marker = 1; System.out.println("Cupboard.marker is " + Cupboard.marker); Cupboard cupboard2 = new Cupboard(); System.out.println("Cupboard.marker is " + Cupboard.marker); System.out.println("cupboard1.bowl3's adress is " + cupboard1.bowl3.hashCode()); System.out.println("cupboard2.bowl3's adress is " + cupboard2.bowl3.hashCode()); System.out.println("cupboard1.bowl4's adress is " + cupboard1.bowl4.hashCode()); System.out.println("cupboard2.bowl4's adress is " + cupboard2.bowl4.hashCode()); System.out.println("cupboard2.bowl4's marker is " + cupboard2.bowl4.marker); cupboard1.bowl4.marker = 40; System.out.println("cupboard2.bowl4's marker is " + cupboard2.bowl4.marker); } }
执行结果:
Bowl(4) Cupboard.marker is 0 Bowl(3) Cupboard() Cupboard.marker is 1 Bowl(3) Cupboard() Cupboard.marker is 1 cupboard1.bowl3's adress is 1383884648 cupboard2.bowl3's adress is 1701381926 cupboard1.bowl4's adress is 1381270477 cupboard2.bowl4's adress is 1381270477 cupboard2.bowl4's marker is 4 cupboard2.bowl4's marker is 40
在main函数中,实例化了两个Cupboard类cupboard1和cupboard2。当实例化cupboard1时,Cupboard类最先被访问时。其静态成员变量bowl4被初始化,然后是非静态成员变量bowl3. 然而,当实例化cupboard1和cupboard2时,静态成员变量bowl4已经在内存中存在了,不需要再初始化。因此这里只是初始化非静态成员变量bowl3。
在比较另一个静态成员变量marker,在Cupboard最初被访问时,marker被初始化为0. 当实例化cupboard1和cupboard2时,marker并不会被再一次初始化。然后再比较cupboard1和cupboard2中变量的位置,两个对象的非静态成员变量bowl3的位置是不同的,而两个对象的静态成员变量bowl4的位置就是相同的。
上面的分析说明了几点:
1. 静态变量一旦被初始化,就获得了固定的一块内存,不会被再次初始化。Cupboard类中的bowl4和bowl5就没有被再次初始化。
2. 静态变量一旦被分配到内存中,就一直占据这块内存,且值或者引用的位置再也不会被初始化。
三 static方法存在的问题
static方法的使用有一个缺陷必须注意,static方法不具备多态性。在设计类的时候,需要设计为多态的方法一定不能用static关键字。看下面例子。
class StaticSup { public static String staticGet () { return "Base staticGet()"; } public String dynamicGet () { return "Base dynamicGet()"; } } class StaticSub extends StaticSup { public static String staticGet () { return "Derived staticGet()"; } public String dynamicGet () { return "Derived dynamicGet()"; } } public class StaticPolymorphism { public static void main(String[] args) { StaticSup sup = new StaticSub(); System.out.println(sup.staticGet()); System.out.println(sup.dynamicGet()); } }
运行结果:
Base staticGet()
Derived dynamicGet()
上述代码是一个典型的多态的实例,静态的staticGet方法在多态实现过程中,发现静态方法并没有多态性。虽然StaticSup类型的sup被实例化为StaticSub对象,但是在执行静态方法的时候,依然直接执行了StaticSup类型的静态方法。
静态方法不具备多态性。因为静态方法根本不需要实例化就可以直接执行,执行时也不会关心被实例化为那一个子类。