Java的static关键字浅析

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类型的静态方法。

静态方法不具备多态性。因为静态方法根本不需要实例化就可以直接执行,执行时也不会关心被实例化为那一个子类。

 

posted on 2013-08-20 16:20  洪雁君  阅读(240)  评论(0编辑  收藏  举报

导航