1. 概述

本文将深入探讨Java语言中的static关键字。我们将学习如何将static应用于变量、方法、代码块和嵌套类,并分析它们之间的关键区别。

2. static关键字核心原理

在Java中,**static关键字表示特定成员属于类型本身,而非该类型的实例**。这意味着无论创建多少个实例,该static成员只会存在一个副本,被所有实例共享。

Java中静态变量共享示意图

static可应用于:

  • 变量
  • 方法
  • 代码块
  • 嵌套类

3. static字段(或类变量)

在Java中,当我们将字段声明为static时,该字段只会创建一个副本,并在该类的所有实例间共享。无论实例化多少次类,static字段始终只有一个副本,其值在所有同类对象间共享。

从内存角度看,静态变量存储在堆内存中

3.1 static字段示例

假设有一个Car类包含多个属性(实例变量):

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++;
    }

    // getters and setters
}

每次实例化该类时,numberOfCars变量会被递增。因此以下断言成立:

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

3.2 使用static字段的理由

✅ 当变量值独立于对象时
✅ 当值需要在所有对象间共享时

3.3 关键要点

  • static变量属于类,可直接通过类名访问,无需对象引用
  • 只能在类级别声明static变量
  • 无需初始化对象即可访问static字段
  • 虽然可通过对象引用访问(如ford.numberOfCars++),但应避免这种写法,因为难以区分是实例变量还是类变量。始终使用类名引用静态变量(Car.numberOfCars++

4. static方法(或类方法)

static字段类似,static方法也属于类而非对象。因此无需创建所在类的实例即可调用它们。

4.1 static方法示例

通常使用static方法执行不依赖实例创建的操作。为在类的所有实例间共享代码,可将其写入static方法:

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

也常用于创建工具类/辅助类,无需实例化即可使用。例如:

  • JDK中的CollectionsMath工具类
  • Apache的StringUtils
  • Spring框架的CollectionUtils

这些工具类的所有方法都是static的。

4.2 使用static方法的理由

✅ 访问/操作不依赖对象的静态变量和其他静态方法
✅ 在工具类和辅助类中广泛使用

4.3 关键要点

  • Java中的static方法在编译时解析。由于方法重载是运行时多态的一部分,**static方法不能被重载**
  • 抽象方法不能是static
  • static方法不能使用thissuper关键字

以下组合是有效的:

  1. 实例方法可直接访问实例方法和实例变量
  2. 实例方法也可直接访问static变量和static方法
  3. static方法可访问所有static变量和其他static方法
  4. static方法不能直接访问实例变量和实例方法,需要对象引用

4.4 在static方法中调用非static方法

要在static方法中调用非static方法,必须使用包含该非static方法的类的实例。这是在main()静态方法中调用非静态方法的常见场景

以之前的Car类为例:

public String getName() {
    return name;
}

public String getEngine() {
    return engine;
}

public static String getCarsInformation(Car car) {
    return car.getName() + "-" + car.getEngine();
}

getCarsInformation()静态方法中调用getName()getEngine()非静态方法,必须通过Car对象实例访问。否则会报错:"Non-static method 'getName()' cannot be referenced from a static context"。

5. static代码块

使用static代码块初始化static变量。虽然可以直接在声明时初始化,但有时需要多行处理逻辑,此时static代码块就派上用场。

static变量初始化需要额外的多语句逻辑时,可使用static代码块

5.1 static代码块示例

假设要初始化包含预定义值的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");
    }
}

在声明时无法直接初始化包含所有初始值的List对象,因此这里使用了static代码块。

5.2 使用static代码块的理由

✅ 当static变量初始化需要除赋值外的额外逻辑时
✅ 当static变量初始化容易出错且需要异常处理时

5.3 关键要点

  • 一个类可以有多个static代码块
  • static字段和static代码块按在类中出现的顺序解析和执行

6. static类

Java允许在类中创建类,提供了一种将元素组织在单一位置的方式,使代码更有条理、更易读。

嵌套类架构分为两种:

  • 声明为static的嵌套类称为静态嵌套类
  • static的嵌套类称为内部类

主要区别在于:内部类可访问外部类的所有成员(包括private成员),而静态嵌套类只能访问外部类的静态成员

实际上,静态嵌套类的行为与任何顶级类完全相同,只是被封装在唯一会访问它的类中,以提供更好的封装便利性

6.1 static类示例

创建单例对象最常用的方式是通过静态嵌套类:

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

6.2 使用static内部类的理由

✅ 将仅在单一位置使用的类分组,增强封装性
✅ 将代码靠近唯一使用它的位置,提高可读性和可维护性
✅ 如果嵌套类不需要访问外部类的实例成员,最好声明为static。这样不会与外部类耦合,更高效(不需要堆或栈内存)

6.3 关键要点

  • 静态嵌套类不能访问外部类的任何实例成员,只能通过对象引用访问
  • 静态嵌套类可访问外部类的所有静态成员(包括private成员)
  • **Java规范不允许将顶级类声明为static**,只有嵌套类可以声明为static

7. 理解错误“Non-static variable cannot be referenced from a static context”

此错误通常发生在静态上下文中使用非静态变量时。

如前所述,静态变量属于类,在类加载时加载。而非静态变量需要创建对象才能引用。

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,导致编译错误:Non-static variable cannot be referenced from a static context

8. 总结

本文深入探讨了static关键字的应用场景,分析了使用静态字段、静态方法、静态代码块和静态内部类的理由和优势。

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

完整代码可在GitHub获取。


« 上一篇: Micrometer 快速指南
» 下一篇: Java数组初始化详解