Java中的static关键字

来源:互联网 发布:淘宝卖家更换支付宝 编辑:程序博客网 时间:2024/06/01 22:57

什么是static?static关键字可以用来做什么?

static表示“全局” 或者“静态”的意思,它是一个修饰符,可以用来修饰方法、变量、代码块、类。


静态对象和非静态对象的区别(这里对象的意思不是指实例化对象,用来代表方法、变量):

       1)静态对象是所有实例化对象所共同拥有的,即共享的,而非静态变量是类的各个实例化对象独立拥有的;

        2)静态对象随着类的加载而加载,也意味着随着类的消失而消失。生命周期最长;

        3)静态对象的内存空间是固定的,而非静态变量的内存空间是在实例化对象时才会被分配;

        4)静态对象可以直接被类名调用,是属于类的,

             非静态对象被对象调用的,是属于具体对象的;


一、static修饰变量、方法、类、代码块

1、修饰变量

静态变量与成员变量最大的特点是,静态变量在整个程序中都是唯一的,例:

public class Test2 {    public static void main(String[] args) {    Animal t1 = new Animal();    Animal t2 = new Animal();    //类名.变量调用    System.out.println(Animal.voice);    //对象.变量调用    System.out.println(t1.voice);    t1.voice += " world";    System.out.println(t2.voice);        //再次类名.变量调用        System.out.println(Animal.voice); }}class Animal {public static String voice="hello";}

输出结果为:

hellohellohello worldhello world

可以看出静态变量的方法的值是唯一的,一旦改变,再次调用该静态变量时只会是改变后的值

2、修饰方法

调用方法和静态变量调用方法一致,可以直接使用类名来调用静态方法,静态方法有几点需要注意:

1)它们仅能调用其他的static方法。

2)它们只能访问static数据。

3)它们不能以任何形式引用this或super。

package com.good;public class Test3 {public static void main(String[] args) {Cat.run();Cat cat = new Cat();cat.run();}}class Cat {private static int count=0;private String s1 = "s1";private static String s2 = "s2";public static void run(){System.out.println("猫跑了" + count++ + "步");String s3 = "s3";//System.out.println(s1);//错误!只能调用static数据;//System.out.println(this.s1);//错误!无法调用this或superSystem.out.println(s2);//调用类里的静态变量System.out.println(Cat.s2);System.out.println(s3);//调用方法里的变量System.out.println(Dog.s4);//调用其他类里的静态变量Dog.run();//调用其他类里的静态方法}}class Dog {public static String s4 = "s4";private static int count = 0;public static void run(){System.out.println("狗跑了" + count++ + "步");}}


输出结果:
猫跑了0步s2s2s3s4狗跑了0步猫跑了1步s2s2s3s4狗跑了1步


3、修饰类

通常一个普通类不允许声明为静态的,只有一个内部类才可以。这时这个声明为静态的类可以直接作为一个普通类来使用,而不需要实例一个外部类

public class StaticCls {    public static void main(String[] args) {       OuterCls.InnerCls oi = new OuterCls.InnerCls();    }} class OuterCls {    public static class InnerCls {       InnerCls() {           System.out.println("InnerCls");       }    }} 

结果为:InnerCls


4、修饰代码块

static{}(即static块),会在类被加载的时候执行且仅会被执行一次,一般用来初始化静态变量和调用静态方法

1)在程序的一次执行过程中,static{}语句块中的内容只被执行一次,看下面的示例:

package com.good;public class StaticBlockTest {public static void main(String args[]) {try {Class.forName("com.good.Test");Class.forName("com.good.Test");Test t1 = new Test();Test t2 = new Test();} catch (Exception e) {e.printStackTrace();}}}class Test {public static int X = 100;public final static int Y = 200;public Test(){              System.out.println("Test构造函数执行");          }static {System.out.println("static语句块执行");}public static void display() {System.out.println("静态方法被执行");}public void display_1() {System.out.println("实例方法被执行");}}

运行结果:
static语句块执行Test构造函数执行Test构造函数执行

可以看出虽然执行了两条Class.forName("com.good.Test")语句,但是,只输出了一条"static语句块执行";其实第二条Class.forName()语句已经无效了,因为在虚拟机的声明周期中一个类只被加载一次;又因为static{}是伴随类的加载而执行的,所以不管你new多少次实例对象,static{}都只执行一次

static{}的应用:

1)jdbc中的应用

熟悉JDBC的读者应该知道,java中有一个DriverManager类,用于管理各种数据库驱动程序、建立新的数据库连接。DriverManager类包含一些列Drivers类,这些Drivers类必须通过调用DriverManager的registerDriver()方法来对自己进行注册,那么注册是什么时候发生的呢?下面会给出答案:

所有Drivers类都必须包含有一个静态方法,利用这个静态方法可以创建该类的实例,然后在加载该实例时向DriverManage类进行注册。我们经常用Class.forName()对驱动程序进行加载,那么注册就发生在这条语句的执行过程中,前面说的Drivers的静态方法是放在static{}中的,当对驱动程序进行加载的时候,会执行该static{},便完成了注册。

2)hibernate中的应用

hibernate中的SessionFactory是一个重量级的类,创建该类的对象实例会耗费比较多的系统资源,如果每次需要时都创建一个该类的实例,显然会降低程序的执行效率,所以经常将对该类的实例化放在一个static{}中,只需第一次调用时执行,提高程序的执行效率,如下:

static {     try {   configuration.configure(configFile);   sessionFactory = configuration.buildSessionFactory();  } catch (Exception e) {   System.err.println("%%%% Error Creating SessionFactory %%%%");   e.printStackTrace();  }    }


二、类的加载特性

在虚拟机的生命周期中一个类只被加载一次

类加载的原则:延迟加载,能少加载就少加载,因为虚拟机的空间是有限的

类加载的时机:

1)第一次创建对象要加载类

2)调用静态方法时要加载类,访问静态属性时会加载类

3)加载子类时必定会先加载父类

4)创建对象引用不用加载类

5)子类调用父类的静态方法时

      a、当子类没有覆盖父类的静态方法时,只加载父类,不加载子类

      b、当子类有覆盖父类的静态方法时,既加载父类,又加载子类

6)访问静态常量,如果编译器可以计算出常量的值,则不会加载类,例如:public static final int a = 123;否则会加载类,例如:public staticfinal int a=math.PI;



三、趣题小练

1:猜一猜输出结果

package com.good;public class StaticTest {public static void main(String[] args) {staticFunction();}static  StaticTest st = new StaticTest();static{System.out.println("1");}{System.out.println("2");}StaticTest(){System.out.println("3");System.out.println("a=" + a + "b=" + b);}public static void staticFunction(){System.out.println("4");}int a = 110;static int b;}

输出结果:

23a=110b=014

分析:

1、类的生命周期是:加载->验证->准备->解析->初始化->使用->卸载,只有在准备阶段和初始化阶段才会涉及类变量的初始化和赋值,因此只针对这两个阶段进行分析;

2、类的准备阶段需要做是为类变量分配内存并设置默认值,因此类变量st为null、b为0;(需要注意的是如果类变量是final在加载阶段就已经完成了初始化,可以把b设置为final试试);

3、类的初始化阶段需要做是执行类构造器,先执行第一条静态变量的赋值语句即st = new StaticTest (),此时会进行对象的初始化,对象的初始化是先初始化成员变量再执行构造方法,因此打印2->设置a为110->执行构造方法(打印3,此时a已经赋值为110,但是b只是设置了默认值0,并未完成赋值动作),等对象的初始化完成后继续执行之前的类构造器的语句

4、接下来再按顺序输出1、4



2:

package com.good;public class StaticTest2 {public static void main(String[] args) {new Child().printX();}}class Child extends Father{static{System.out.println("child--static");}private String n = "20";{n = "30";}//public int x = 200;static{System.out.println("child--static");}public Child(){this("the other constructor");System.out.println("child constructor Body:" + n);}private  static String ff = "ddd";public Child(String s) {System.out.println(s);}public void age(){System.out.println("age="+n);}public void printX(){System.out.println("x="+x);}}class Father{static{System.out.println("super--static");}public static String n= "a";public int x = 100;public Father(){System.out.println("super's x="+x);age();}public void age(){System.out.println("nothing");}public void printX(){System.out.println("x="+x);}}


输出结果:

super--static
child--static
child--static
super's x=100
age=null
the other constructor
child constructor Body:30
x=100

分析:

1、加载Child()类时,发现这个类需要一个父类Father,所以先加载父类Father,输出super--static,以及将静态变量n初始化

2、再来加载子类的静态块,输出child--static,child--static

3、加载完父类和子类的static部分,再来加载成员变量和构造方法,因为是先加载父类Father,所以先加载父类的构造方法,输出super's x=100

4、执行父类构造方法里的age()方法,age方法已经被子类重写,因此输出子类里的age()方法,因为此时尚未加载子类的成员变量以及构造方法,所以输出age=null

5、加载完父类构造方法后,再加载子类成员变量和构造方法,输出the other constructor、child constructor Body:30

6、最后输出x的值,x继承于父类,在父类Father加载成员变量和构造方法时已经赋值完毕,为100,因此输出x=100


趣题总结:

1、静态变量、静态方法>变量、初始化块>构造器

2、如果存在继承关系,那么加载顺序应该为:父类static>子类static>父类成员变量>父类构造方法>子类成员变量>子类构造方法

原创粉丝点击