zoukankan      html  css  js  c++  java
  • 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类型的静态方法。

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

  • 相关阅读:
    转《编程之美——微软技术面试心得》勘误表
    第一次 学习使用 智能指针
    test _todel
    (转 todo阅读)Android 官方博客 Android应用程序的内存分析(翻译)
    msdn snmp trap 研究可否 重入 转《Multiple Trap Registrations》
    wpbars在博客园开博客了
    创业失败的10个教训总结
    winform 的一种登录方法。
    快速建立Subversion
    (转)SQL Server 按某一字段分组取最大(小)值所在行的数据
  • 原文地址:https://www.cnblogs.com/hongyanee/p/3270111.html
Copyright © 2011-2022 走看看