Java 中的 static 关键字介绍

更新于 2025-12-26

baeldung 2024-01-08

1. 概述

在本教程中,我们将深入探讨 Java 语言中的 static 关键字。static 关键字表示某个成员(如字段或方法)属于类本身,而不是该类的任何特定实例。因此,我们可以无需创建对象实例,直接访问静态成员。

我们将首先讨论静态字段和方法与非静态字段和方法之间的区别;接着介绍静态内部类和静态代码块;最后解释为什么在静态上下文中无法访问非静态组件。


2. 静态字段(或称类变量)

在 Java 中,当我们声明一个字段为 static 时,该字段只会创建唯一一份副本,并被该类的所有实例共享。

无论我们创建多少个该类的对象,静态字段始终只有一份。这个值在所有同类型对象之间共享。从内存角度看,静态变量存储在堆内存中。

设想一个类包含多个实例变量:每次创建新对象时,这些变量都会拥有各自的副本。但如果我们希望有一个变量用于统计已创建对象的数量,就应该使用静态变量。这样,每创建一个新对象,计数器就会递增:

public class Car {
    private String name;
    private String engine;
    
    public static int numberOfCars;
    
    public Car(String name, String engine) {
        this.name = name;
        this.engine = engine;
        numberOfCars++;
    }

    // getter 和 setter 方法
}

因此,每次实例化 Car 类时,静态变量 numberOfCars 都会递增。让我们创建两个 Car 对象,并期望计数器值为 2:

@Test
public void whenNumberOfCarObjectsInitialized_thenStaticCounterIncreases() {
    new Car("Jaguar", "V8");
    new Car("Bugatti", "W16");
 
    assertEquals(2, Car.numberOfCars);
}

由此可见,静态字段在以下场景中非常有用:

  • 变量的值与具体对象无关;
  • 该值需要在所有对象之间共享。

此外需要注意的是,静态字段既可以通过实例访问(例如 ford.numberOfCars++),也可以直接通过类名访问(例如 Car.numberOfCars++)。推荐使用后者,因为它能清晰表明这是一个类变量,而非实例变量。


3. 静态方法(或称类方法)

与静态字段类似,静态方法也属于类本身,而非某个对象。因此,我们可以在不实例化类的情况下调用它们。通常,我们会使用静态方法来执行那些不依赖于对象创建的操作。

例如,我们可以使用静态方法在所有类实例之间共享逻辑:

static void setNumberOfCars(int numberOfCars) {
    Car.numberOfCars = numberOfCars;
}

此外,静态方法常用于创建工具类或辅助类。一些著名的例子包括 JDK 中的 CollectionsMath 工具类、Apache 的 StringUtils,以及 Spring Framework 的 CollectionUtils

与静态字段一样,静态方法不能被重写(override)。这是因为 Java 中的静态方法在编译时就已确定(静态绑定),而方法重写是运行时多态的一部分。

以下是实例方法、类方法与变量之间合法的访问组合:

  • 实例方法可以直接访问其他实例方法和实例变量;
  • 实例方法也可以直接访问静态变量和静态方法;
  • 静态方法可以访问所有静态变量和其他静态方法;
  • 静态方法不能直接访问实例变量和实例方法,必须通过某个对象引用来访问。

4. 静态代码块

通常,我们会在声明时直接初始化静态变量。但如果静态变量的初始化需要多条语句的逻辑,就可以使用静态代码块(static block)。

例如,我们使用静态代码块来初始化一个带有预定义值的 List 对象:

public class StaticBlockDemo {
    public static List<String> ranks = new LinkedList<>();

    static {
        ranks.add("Lieutenant");
        ranks.add("Captain");
        ranks.add("Major");
    }
    
    static {
        ranks.add("Colonel");
        ranks.add("General");
    }
}

显然,我们无法在声明时一次性完成如此复杂的初始化,因此使用了静态代码块。

一个类可以包含多个静态成员(字段和代码块)。JVM 会按照它们在类中声明的顺序依次解析静态字段和静态代码块。

使用静态代码块的主要原因包括:

  • 初始化静态变量需要除简单赋值之外的额外逻辑;
  • 初始化静态变量时需要自定义异常处理。

5. 静态内部类

Java 允许我们在一个类内部定义另一个类。这种机制有助于将相关元素组织在一起,使代码更清晰、更具可读性。

嵌套类主要分为两类:

  • 使用 static 声明的嵌套类称为 静态嵌套类(static nested class)
  • 未使用 static 声明的嵌套类称为 内部类(inner class)

两者的主要区别在于:

  • 内部类可以访问外部类的所有成员(包括私有成员);
  • 静态嵌套类只能访问外部类的静态成员

静态嵌套类的行为与其他顶层类完全相同,只是它被封装在唯一会使用它的外部类中,从而提供更好的封装性和代码组织性。

例如,我们可以使用静态嵌套类实现单例模式:

public class Singleton {
    private Singleton() {}

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

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

这种方法不需要同步机制,且易于理解和实现。

此外,静态嵌套类与外部类之间的可见性如下所示:

public class Pizza {

    private static String cookedCount;
    private boolean isThinCrust;

    public static class PizzaSalesCounter {

        private static String orderedCount;
        public static String deliveredCount;

        PizzaSalesCounter() {
            System.out.println("Static field of enclosing class is "
              + Pizza.cookedCount);
            System.out.println("Non-static field of enclosing class is "
              + new Pizza().isThinCrust);
        }
    }

    Pizza() {
        System.out.println("Non private static field of static class is "
          + PizzaSalesCounter.deliveredCount);
        System.out.println("Private static field of static class is "
          + PizzaSalesCounter.orderedCount);
    }

    public static void main(String[] a) {
           new Pizza.PizzaSalesCounter();
    }
}

运行 main 方法后的输出为:

Static field of enclosing class is null
Non private static field of static class is null
Private static field of static class is null
Non-static field of enclosing class is false

本质上,静态嵌套类无法直接访问外部类的任何实例成员,只能通过对象引用来访问。

在代码中使用静态内部类的主要原因包括:

  • 将仅在一个地方使用的类进行分组,增强封装性;
  • 将代码放在唯一使用它的地方附近,提高可读性和可维护性;
  • 如果嵌套类不需要访问外部类的实例成员,应将其声明为 static。这样可以避免与外部类耦合,同时减少堆栈内存开销。

6. 理解 “Non-static variable” 错误

错误信息 “Non-static variable cannot be referenced from a static context”(非静态变量无法在静态上下文中引用)发生在静态上下文中使用了非静态变量时。

如前所述,JVM 在类加载时就初始化静态变量,它们属于类本身;而非静态变量则必须通过对象实例才能访问。

因此,Java 编译器会报错,因为调用非静态变量必须依赖某个对象实例。

下面通过一个例子说明该错误:

public class MyClass { 
    int instanceVariable = 0; 
    
    public static void staticMethod() { 
        System.out.println(instanceVariable); 
    } 
    
    public static void main(String[] args) {
        MyClass.staticMethod();
    }
} 

这里,我们在静态方法 staticMethod() 中使用了非静态变量 instanceVariable,因此会触发上述编译错误。


7. 结论

在本文中,我们深入探讨了 Java 中 static 关键字的用法,并详细说明了使用静态字段、方法、代码块和内部类的主要原因。

最后,我们还了解了导致编译器报错 “Non-static variable cannot be referenced from a static context” 的根本原因。

合理使用 static 能提升代码的性能与可维护性,但需注意其限制——尤其是静态上下文无法直接访问实例成员这一关键规则。