Skip to content

static 平时天天用——工具类的方法、常量、单例,但"静态和实例到底啥区别、静态内部类为什么不持有外部引用"这些点一直没系统梳理过。这篇把 static 从头讲透:静态变量、静态方法、静态代码块、静态内部类,以及它们和"实例成员"的根本区别。

一、核心思想:static = 属于类,不属于实例

Java 的成员(变量、方法)分两类:

  • 实例成员:属于每个对象(new 一个就有自己的一份)
  • 静态成员(static):属于类本身所有实例共享一份,甚至不需要实例就能访问
java
public class Counter {
    public static int total = 0;     // 静态变量:属于类,所有实例共享
    public int count = 0;            // 实例变量:属于每个对象,各一份

    public void increment() {
        total++;     // 静态的,共享
        count++;     // 实例的,自己的
    }
}

Counter a = new Counter();
Counter b = new Counter();
a.increment();
System.out.println(a.total);   // 1(共享,b 也能看到)
System.out.println(b.total);   // 1
System.out.println(a.count);   // 1(自己的)
System.out.println(b.count);   // 0(b 自己的,还是 0)

一句话static 让成员"属于类",所有实例共享,不用 new 就能用。

二、静态变量:共享的"类变量"

静态变量在类加载时初始化一次,之后所有实例共享这一个变量:

java
public class Config {
    public static final String APP_NAME = "BinManager";  // 静态常量:最常用
    public static int visitCount = 0;                    // 静态变量:全局共享
}
// 用:Config.APP_NAME(不用 new)

最常见的静态变量是静态常量static final)——配置、枚举值都这么写,通过类名直接访问,全局只有一份。

三、静态方法:不用 new 就能调

静态方法属于类,通过类名调用,不用创建实例:

java
public class MathUtils {
    public static int add(int a, int b) { return a + b; }
}
int sum = MathUtils.add(1, 2);   // 类名调用,不用 new MathUtils()

关键限制:静态方法不能访问实例成员(实例变量、实例方法)——因为它不依赖任何实例,自然也就没有"某个对象的成员"可用:

java
public class Demo {
    private String name = "BinMaker";   // 实例变量

    public static void staticMethod() {
        // System.out.println(name);   // ❌ 编译报错:静态方法不能访问实例变量
    }
    public void instanceMethod() {
        System.out.println(name);      // ✅ 实例方法能访问
    }
}

记忆:静态方法里只有静态成员和参数可用,碰不到"对象"。

四、静态代码块:类加载时执行一次

静态代码块在类第一次被加载时执行一次,适合做一次性的初始化:

java
public class DBConfig {
    static {
        System.out.println("类加载时执行一次,加载数据库配置");
        // 初始化静态变量
    }
}

类加载一次,静态代码块就执行一次——之后不管 new 多少个对象都不会再执行。

五、静态内部类:不持有外部类引用

这是最容易混的:静态内部类和普通内部类的根本区别是"持有不持有外部类实例":

java
public class Outer {
    private String name = "外部";

    // 普通内部类:持有外部类实例引用,能访问外部成员
    class Inner {
        public void hello() {
            System.out.println(name);   // 能访问外部类成员
        }
    }

    // 静态内部类:不持有外部类引用,不能访问外部实例成员
    static class StaticInner {
        public void hello() {
            // System.out.println(name);  // ❌ 没有外部类引用,访问不了
        }
    }
}

// 使用区别:静态内部类可以独立 new,普通内部类必须先有外部类实例
Outer.StaticInner s = new Outer.StaticInner();   // ✅ 独立创建
Outer.Inner i = new Outer().new Inner();         // 要先 new 外部类

为什么有静态内部类:很多内部类其实不需要访问外部类成员(比如只是为了分组、或作为辅助结构),用 static 声明后:

  1. 不持有外部类引用 → 省内存(不隐式带上外部实例)
  2. 可以独立创建 → 不需要外部实例

经典例子:Map.EntryInteger.IntegerCache 都是静态内部类。

六、常见场景

场景例子
工具类MathCollectionsArrays——全是静态方法,不用 new
常量static final 常量,类名直接访问
单例静态变量 + 私有构造(见《单例模式》)
静态工厂Integer.valueOf() 返回静态缓存的实例(见《享元模式》)

小结

  • static = 成员属于,所有实例共享,不用 new 就能用
  • 静态变量:类加载时初始化一次,全局共享
  • 静态方法:类名调用,不能访问实例成员
  • 静态代码块:类加载时执行一次
  • 静态内部类:不持有外部类引用,可独立创建,省内存

想了解 static 在面向对象里的位置,看《Java 面向对象》;想了解静态变量和实例变量的内存区别(都在 JVM 的哪块区域),看《JVM 内存模型与垃圾回收》。