Java关键字之static

1 前言

static意思是静态的、全局的,在java中一旦被static修饰,说明被修饰的东西在一定范围内是共享的,谁都可以访问,这时候需要注意并发读写时的线程安全问题。被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问,这使得我们可以很方便的在没有创建对象的情况下来进行方法/变量的调用/访问。

2 修饰的对象

static 可以用来修饰变量、方法、内部类和代码块。

2.1 静态变量

使用 static 修饰的变量是属于类的,而不是属于对象的,静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。
注意,static 关键字并不能修改变量的访问权限,也就是说 private 变量即使使用 static 关键字修饰,依然只能由本类及本类的对象访问,而用 static 关键字修饰的 public 变量可以被任何类直接访问,而无需初始化类,直接使用 类名.static变量 这种形式访问即可。
在使用多个线程对静态变量进行读写时,需要注意它的线程安全问题,比如对于public static List list = new ArrayList();这样的共享变量在并发环境下进行读写时就会产生线程安全问题,这时我们可以使用线程安全的 CopyOnWriteArrayList 或者在读写时手动进行加锁来保证多线程读写下的线程安全。
静态变量在字节码层面是使用访问标志位来进行标识的,如下图所示:

static变量的访问标志位

也就是说,在类加载时,JVM会通过class文件中的访问标志位来判断某个变量是否为静态变量。

2.2 静态方法

和静态变量一样,静态方法也可以不依赖于任何对象就直接进行访问,因此对于静态方法来说,是没有 this 的,因为它不依附于任何对象,而 this 本身就是个对象。由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用/访问的,而反过来,非静态成员方法是可以调用/访问静态成员方法/变量的。
static 方法内部的变量在执行时是没有线程安全问题的。方法执行时,数据运行在栈里面,栈里面的数据每个线程都是隔离开的,所以不会有线程安全的问题。
static 方法在字节码层面是使用一个ACC_STATIC的标志位来进行标识的,如下图所示:


static方法的标志位

2.3 静态内部类

普通类是不允许声明为静态的,只有内部类才可以,使用 static 修饰的内部类可以直接由外部类来创建和访问,而没有用 static 修饰的内部类则必须要先实例化一个外部类的对象,再通过外部类的对象来创建内部类的实例。

2.4 静态代码块

static 关键字还可以修饰代码块用于在类启动之前,初始化一些值。static 块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。在静态代码块中只能调用同样被 static 修饰的变量,并且 static 的变量需要写在静态块的前面,不然编译会报错。
和 static 方法一样,静态代码块在字节码层面使用一个ACC_STATIC的标志位来进行标识。

3 初始化时机

我们通过一个演示程序来测试一下被 static 修饰的类变量、代码块和方法的初始化时机:

public class Parent {

    private static List parentList = new ArrayList(){{
        System.out.println("父类静态变量初始化");
    }};

    static {
        System.out.println("父类静态代码块初始化");
    }

    public Parent() {
        System.out.println("父类构造器初始化");
    }

    public static void testStatic() {
        System.out.println("父类静态方法被调用");
    }

}
public class Child extends Parent {

    static {
        System.out.println("子类静态代码块初始化");
    }

    private static List childList = new ArrayList(){{
        System.out.println("子类静态变量初始化");
    }};

    public Child() {
        System.out.println("子类构造器初始化");
    }

    public static void main(String[] args) {
        System.out.println(" main 方法执行");
        new Child();
    }

}

运行程序,发现打印的结果是:

父类静态变量初始化
父类静态代码块初始化
子类静态代码块初始化
子类静态变量初始化
 main 方法执行
父类构造器初始化
子类构造器初始化

因此,我们可以得出以下结论:

  1. 父类的静态变量和静态代码块比子类优先初始化;
  2. 静态变量和静态块比类构造器优先初始化;
  3. 静态变量和静态代码块按照定义的顺序依次进行初始化;
  4. 被 static 修饰的方法,在类初始化的时候并不会执行,只有当自己被调用时,才会被执行。

4 面试题

如何证明 static 静态变量和类无关?

  1. 不需要初始化类就可直接使用静态变量;
  2. 在类中写个 main 方法运行,即便不写初始化类的代码,静态变量都会自动初始化;
  3. 静态变量只会初始化一次,初始化完成之后,不管再 new 多少个类出来,静态变量都不会再初始化了。

你可能感兴趣的:(Java关键字之static)