深入分析Java中static关键字

  static是Java中的一个比较重要的关键字。它具有多种的用法,合适地运用static关键字可以有效地提高程序的运行性能,优化程序的结构。static关键字主要具有以下四种应用场景和用途:

  • 修饰成员变量
  • 修饰成员方法
  • 静态代码块
  • 静态导包
1. 修饰成员变量

  static修饰的成员变量又称为静态变量。静态变量被所有对象共享,在内存中只存在一个副本,仅会在类初次加载时被初始化。静态变量可用类名直接访问(方便),当然也可以通过对象来访问(不推荐)。
  非静态变量由每个对象拥有,每创建一个对象实例就会被创始化,在内存中存在多个副本,各个对象的副本相互独立,互不影响。
  static成员变量的初始化顺序按照定义的顺序进行初始化。

public class Student {
    private int id; //学号
    private String name; //姓名
    private static int count = 0;   

    @Override
    public String toString() {
        return "Student [id=" + id + ", name=" + name + "]";
    }   
    public Student(String name) {
        super();
        this.name = name;
        id = count++;
    }   

    public static void main(String[] args){
        Student s1 = new Student("张三");
        Student s2 = new Student("李四");
        System.out.println(s1);
        System.out.println(s2);
    }
}

运行结果如下:
Student [id=1, name=张三]
Student [id=2, name=李四]
  由上面例子可以看出,给count变量添加了static关键字后,count变量就交给整个类管理了,一个对象如果对该变量进行修改,其它对象也会受到影响。static可以让对象共享属性,但实际中这样会让该属性变得难以控制,因为它在任何地方都有可能被改变。因此使用static修饰变量要慎重。

  注意:static不会改变类中成员的访问权限
  Java中的static关键字不会影响到变量或者方法的作用域。在Java中能够影响到访问权限的只有private、public、protected(包括包访问权限)这几个关键字。如下面例子:
这里写图片描述
  public的name变量可以访问,而private的age变量不可访问,说明static不会改变类中成员的访问权限。

2. 静态方法

  使用static修饰的方法又称为静态方法。静态方法属于类,可以采用”类名.方法名“的方式调用方法,避免了先要new出对象的繁琐和资源消耗。需要注意的是,在静态方法中不能访问类的非静态成员变量和非静态成员方法,而在非静态成员方法中是可以访问静态成员方法/变量的
  静态方法通常用于工类具当中。例如,连接数据库,可以定义一个静态的getConnection()方法,它不应该属于某个对象,而是属于整个类,因此可以直接利用类名来调用此static方法。下面举一个简单的打印工具类的静态方法全为示例:

public class PrintHelper {  
    public static void main(String[] args) {
        PrintHelper.print("Hello world");       
    }   
     public static void print(String str){
            System.out.println(str);
        }
}
3. static代码块

  static关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。
  static语句块可以优化程序性能关键在于其只会在类被JVM加载时被执行一次。下面举一个例子说明这个问题。

class Student{
    private Date birthday;

    public Student(Date birthday) {
        this.birthday = birthday;
    }

    boolean isBornBetween() {
        Date startDate = Date.valueOf("1998");
        Date endDate = Date.valueOf("2004");
        return birthday.compareTo(startDate)>=0 && birthday.compareTo(endDate) < 0;
    }
}

  isBornBetween()方法用于判断学生是否出生于1998到2004年份之间。但其每次调用都会生成startDate和endDate两个Date对象,造成了资源浪费,使用static代码块可优化如下:

class Student{
    private Date birthday;
    private static Date startDate,endDate;

    static{
        startDate = Date.valueOf("1998");
        endDate = Date.valueOf("2004");
    }

    public Student(Date birthday) {
        this.birthday = birthday;
    }

    boolean isBornBetween() {       
        return birthday.compareTo(startDate)>=0 && birthday.compareTo(endDate) < 0;
    }
}

  因此可以把一些一次性的初始化操作放在static代码块中节省资源。如:

  • JDBC中的DriverManager类中的Drivers类注册驱动、建立数据库连接
  • hibernate创建SessionFactory工厂类
static代码块的初始化顺序

  关于static代码块的初始化顺序是笔试和面试中的常见问题。下面通过一个典型例子对这个问题过行解析。

public class Test {
    Person person = new Person("Test");
    static{
        System.out.println("test static");
    }

    public Test() {
        System.out.println("test constructor");
    }

    public static void main(String[] args) {
        new MyClass();
    }
}

class Person{
    static{
        System.out.println("person static");
    }
    public Person(String str) {
        System.out.println("person "+str);
    }
}


class MyClass extends Test {
    Person person = new Person("MyClass");
    static{
        System.out.println("myclass static");
    }

    public MyClass() {
        System.out.println("myclass constructor");
    }
}

运行结果如下:
test static
myclass static
person static
person Test
test constructor
person MyClass
myclass constructor
  为什么会出现这样的运行结果,现在我们对每一步进行解释:

  1. 首先加载Test类,因此Test类中的static代码块会被执行,打印test static
  2. 接着执行new MyClass(),而MyClass类还没有被加载,因此需要加载MyClass类,在加载MyClass类的时候,发现MyClass类继承自Test类,但是由于Test类已经被加载了,所以只需要加载MyClass类,那么就会执行MyClass类的中的static块,打印myclass static
  3. 类加载完毕以后,调用MyClass类的构造方法来生成对象,而在调用子类的构造方法之前,需要先调用其父类的构造方法,对父类的成员变量进行初始化,因此会执行Test中的Person person = new Person(),而Person类还没有被加载过,因此会先加载Person类并执行Person类中的static块,打印person static
  4. 然后调用Person的构造方法,打印person Test
  5. 然后调用Test的构造方法,打印test constructor
  6. 完成父类初始化后,就来初始化MyClass自身了,首先初始化MyClass的成员变量,打印person MyClass
  7. 最后执行MyClass的构造方法,打印myclass constructor
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值