Java static 关键字

static 关键字的作用可以用一句话来描述:‘方便在没有创建对象的情况下进行调用,包括变量和方法’。也就是说,只要类被加载了,就可以通过类名进行访问。static 可以用来修饰类的成员变量,以及成员方法。我们一个个来看。

01、静态变量

如果在声明变量的时候使用了 static 关键字,那么这个变量就被称为静态变量。静态变量只在类加载的时候获取一次内存空间,这使得静态变量很节省内存空间。

来看如下的Student类

public class Student {
    String name;
    int age;
    String school = "郑州大学";
}

假设清华大学录取了一万名新生,那么在创建一万个 Student 对象的时候,所有的字段(name、age 和 school)都会获取到一块内存。学生的姓名和年纪不尽相同,但都属于清华大学,如果每创建一个对象,school 这个字段都要占用一块内存的话,就很浪费,对吧?

public class Student {
    String name;
    int age;
    static String school = "清华大学";

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public static void main(String[] args) {
        Student s1 = new Student("Hanserwei", 18);
        Student s2 = new Student("Hanserwei", 16);
    }
}

s1 和 s2 这两个引用变量存放在栈区(stack),Hanserwei+18 这个对象和Hanserwei+16 这个对象存放在堆区(heap),school 这个静态变量存放在静态区。

image-20250722140159955

来看如下的代码:

public class Counter {
    int count = 0;

    Counter() {
        count++;
        System.out.println(count);
    }

    public static void main(String args[]) {
        Counter c1 = new Counter();
        Counter c2 = new Counter();
        Counter c3 = new Counter();
    }
}

我们创建一个成员变量 count,并且在构造函数中让它自增。因为成员变量会在创建对象的时候获取内存,因此每一个对象都会有一个 count 的副本, count 的值并不会随着对象的增多而递增。

输出:

1
1
1

再来看这个代码:

public class StaticCounter {
    static int count = 0;

    StaticCounter() {
        count++;
        System.out.println(count);
    }

    public static void main(String args[]) {
        StaticCounter c1 = new StaticCounter();
        StaticCounter c2 = new StaticCounter();
        StaticCounter c3 = new StaticCounter();
    }
}

输出:

1
2
3

简单解释一下哈,由于静态变量只会获取一次内存空间,所以任何对象对它的修改都会得到保留,所以每创建一个对象,count 的值就会加 1,所以最终的结果是 3。

另外,需要注意的是,由于静态变量属于一个类,所以不要通过对象引用来访问,而应该直接通过类名来访问,否则编译器会发出警告。

image-20250722140831719

02、静态方法

如果方法上加了 static 关键字,那么它就是一个静态方法。静态方法有以下这些特征。

  • 静态方法属于这个类而不是这个类的对象;
  • 调用静态方法的时候不需要创建这个类的对象;
  • 静态方法可以访问静态变量。

继续上代码

public class StaticMethodStudent {
    String name;
    int age;
    static String school = "北京大学";

    public StaticMethodStudent(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    static void change() {
        school = "清华大学";
    }
    
    void out() {
        System.out.println(name + " " + age + " " + school);
    }

    public static void main(String[] args) {
        StaticMethodStudent.change();
        
        StaticMethodStudent s1 = new StaticMethodStudent("Hanserwei", 18);
        StaticMethodStudent s2 = new StaticMethodStudent("Hanser", 16);
        
        s1.out();
        s2.out();
    }
}

change() 方法就是一个静态方法,所以它可以直接访问静态变量 school,把它的值更改为清华大学;并且,可以通过类名直接调用 change() 方法,就像 StaticMethodStudent.change() 这样。

输出:

Hanserwei 18 河南大学
Hanser 16 河南大学

需要注意的是,静态方法不能访问非静态变量和调用非静态方法。我稍微改动一下代码,编译器就会报错。

image-20250722141422957

然后在静态方法中访问非静态方法,编译器同样不允许。

image-20250722141810221

思考一下:main方法是静态的吗?

如果 main 方法不是静态的,就意味着 Java 虚拟机在执行的时候需要先创建一个对象才能调用 main 方法,而 main 方法作为程序的入口,创建一个额外的对象显得非常多余。

此外,java.lang.Math 类的几乎所有方法都是静态的,可以直接通过类名来调用,不需要创建类的对象。

image-20250722142007254

03、静态代码块

除了静态变量和静态方法,static 关键字还有一个重要的作用。用一个 static 关键字,外加一个大括号括起来的代码被称为静态代码块。

就像下面这串代码。

public class StaticBlock {
    static {
        System.out.println("静态代码块");
    }

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

输出结果

静态代码块
main 方法

既然静态代码块先于 main() 方法执行,那没有 main() 方法的 Java 类能执行成功吗?

Java 1.6 是可以的,但 Java 7 开始就无法执行了。

public class StaticBlockNoMain {
    static {
        System.out.println("静态代码块,没有 main");
    }
}

在命令行中执行 java StaticBlockNoMain 的时候,会抛出 NoClassDefFoundError 的错误。

image-20250722142319331

来看如下的代码:

public class StaticBlockDemo {
    public static List<String> writes = new ArrayList<>();

    static {
        writes.add("Wuyi");
        writes.add("Wuer");
        writes.add("Wusan");

        System.out.println("第一块");
    }

    static {
        writes.add("Wuwu");
        writes.add("Wuliu");

        System.out.println("第二块");
    }
}

writes 是一个静态的 ArrayList,所以不太可能在声明的时候完成初始化,因此需要在静态代码块中完成初始化。静态代码块在初始集合的时候,真的非常有用。在实际的项目开发中,通常使用静态代码块来加载配置文件到内存当中。

04、静态内部类

除了以上只写,static 还有一个不太常用的功能——静态内部类。Java 允许我们在一个类中声明一个内部类,它提供了一种令人信服的方式,允许我们只在一个地方使用一些变量,使代码更具有条理性和可读性。常见的内部类有四种,成员内部类、局部内部类、匿名内部类和静态内部类,限于篇幅原因,前三种不在我们本次的讨论范围之内,以后有机会再细说。

看如下代码

public class Singleton {
    private Singleton() {}

    private static class SingletonHolder {
        public static final Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

第一次加载 Singleton 类时并不会初始化 instance,只有第一次调用 getInstance() 方法时 Java 虚拟机才开始加载 SingletonHolder 并初始化 instance,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。不过,创建单例更优雅的一种方式是使用枚举,以后再说。

需要注意的是。

  • 第一,静态内部类不能访问外部类的所有成员变量;
  • 第二,静态内部类可以访问外部类的所有静态变量,包括私有静态变量。
  • 第三,外部类不能声明为 static。