Java static 关键字

背景与动机#

在 Java 里,不是所有字段和方法都属于“某个对象”。有些成员属于类本身,而不是类创建出来的某个实例,这就是 static 的作用。

static 最关键的是分清一件事:

  • 实例成员:跟着对象走,每个对象各有一份
  • 静态成员:跟着类走,所有对象共享一份

核心原理拆解#

1. 静态字段属于类,所有对象共享#

class User {
static int count = 0;
User() {
count++;
}
}

这里的 count 不是每个 User 对象各有一份,而是整个 User 类共享一份。只要创建一个新对象,count 就会累加。

这类静态字段常用于:

  • 统计实例数量
  • 保存全局共享配置
  • 定义类级别状态

1.1 静态字段和实例字段在内存上的区别#

这里很容易混淆一个点:类里声明了字段,不代表所有字段的具体值都放在同一个地方。

可以先这样理解:

  • 类的信息本身,比如有哪些字段、有哪些方法,属于类级信息
  • 实例字段的具体值,跟着对象走,通常存在堆中的对象实例里
  • static 字段不跟某个对象绑定,可以先简化理解为放在类级区域中,整个类共享一份

所以要分清两个层面:

  • “字段定义”属于类信息
  • “字段具体值”要看它到底是实例字段还是静态字段

例如:

class User {
static int count = 2;
String name;
int age;
}

在这个例子里:

  • User 这个类有哪些字段,属于类信息
  • 某个对象里的 name = "Tom"age = 18,是某个实例自己的状态
  • count = 2 则是整个 User 类共享的一份类级状态

img
img

  • 为什么 static 只有一份
  • 为什么普通字段每个对象各有一份
  • 为什么不能把实例字段值和静态字段值都笼统理解成“都在方法区里”

2. 静态方法属于类,不依赖具体对象#

class MathUtil {
static int add(int a, int b) {
return a + b;
}
}

调用时不需要先 new 对象:

int sum = MathUtil.add(1, 2);

这类方法适合放“只做计算或工具处理,不依赖对象状态”的逻辑。

2.1 从内存角度理解静态字段和静态方法#

如果把 static 字段和 static 方法放到一张图里理解,重点要看清楚它们和对象的关系:

  • static 字段属于类,所有对象共享
  • static 方法也属于类,调用时不依赖某个具体对象
  • 对象实例仍然在堆中创建,栈里拿到的通常只是对象引用

例如下面这段代码:

class User {
static int count = 0;
static void printCount() {
System.out.println(count);
}
}

理解时可以抓住这几点:

  1. count 不是某个对象私有的字段,而是类级字段。
  2. printCount() 不需要 new User() 后才能调用,直接 User.printCount() 就行。
  3. static 方法里可以直接访问 static 字段,因为它们都属于同一个类级上下文。
  4. static 方法如果要处理某个对象的实例状态,还是得显式拿到对象。

img
img

  • 栈里的引用
  • 堆里的对象
  • 类级区域中的静态成员和类信息

3. 为什么静态方法里不能直接访问实例字段#

class User {
String name;
static void printName() {
// System.out.println(name); // 编译失败
}
}

原因很直接:静态方法属于类,但 name 属于具体对象。类级方法执行时,并不知道你要访问的是哪个对象的 name

如果静态方法确实要处理对象状态,就必须显式接收对象:

static void printName(User user) {
System.out.println(user.name);
}

4. 静态方法不是多态重写#

很多人学到继承后,会自然把静态方法也当成“可以像实例方法那样重写”。实际上,静态方法更接近“类自己的方法”,子类写了同名静态方法时,发生的是隐藏,不是运行时多态。

class Parent {
static void hello() {
System.out.println("parent");
}
}
class Child extends Parent {
static void hello() {
System.out.println("child");
}
}
Parent p = new Child();
p.hello(); // 输出 parent

上面这段代码按引用类型 Parent 来决定调用哪个静态方法,而不是按真实对象类型 Child 决定。

5. static final 常量#

class Config {
static final String APP_NAME = "demo";
}

这是 Java 里非常常见的写法:

  • static:说明它属于类
  • final:说明它不会再被重新赋值

所以 static final 很适合定义常量。

6. 静态代码块#

class Config {
static {
System.out.println("class init");
}
}

静态代码块会在类加载时执行一次,通常用于初始化复杂静态资源。

入门阶段先记住两点:

  • 它不是每次创建对象都执行
  • 它通常只执行一次

7. 什么时候适合用 static#

  • 工具类方法:比如 MathObjectsCollections
  • 常量:比如 static final int MAX_SIZE = 100
  • 计数器:比如统计对象创建数量
  • 工厂方法:比如 User.of(...)

如果某段逻辑明显依赖对象自己的状态,就不应该轻易写成 static

最小可运行代码示例#

public class StaticDemo {
public static void main(String[] args) {
User u1 = new User("Tom");
User u2 = new User("Jerry");
System.out.println(User.count);
System.out.println(MathUtil.add(3, 5));
System.out.println(Config.APP_NAME);
User.printName(u1);
}
}
class User {
static int count = 0;
private final String name;
User(String name) {
this.name = name;
count++;
}
static void printName(User user) {
System.out.println(user.name);
}
}
class MathUtil {
static int add(int a, int b) {
return a + b;
}
}
class Config {
static final String APP_NAME = "demo";
}

常见陷阱与错误示例#

1. 把静态字段误当成对象独有状态#

静态字段是所有对象共享的。改动一次,所有实例看到的都是同一份值。

2. 在静态方法里直接访问实例字段#

静态方法没有默认的“当前对象”,所以不能直接使用实例字段和实例方法。

3. 把依赖对象状态的逻辑写成静态方法#

如果一个方法需要频繁读写对象字段,通常说明它更适合作为实例方法,而不是静态方法。

4. 误以为静态方法也会参与运行时多态#

子类可以写同名静态方法,但这不是实例方法那种重写,而是类级别的隐藏。调用结果主要看引用类型,不看实际对象类型。

面试高频问题#

1. static 修饰字段和方法分别表示什么#

修饰字段表示该字段属于类,所有实例共享;修饰方法表示该方法属于类,不依赖某个对象即可调用。

2. 为什么静态方法不能直接访问实例字段#

因为静态方法属于类,执行时不绑定具体对象,自然无法知道要访问哪一个实例的字段。

3. static final 常用于什么场景#

最常见的是定义类级常量,比如配置项、固定字符串、固定数值等。

4. 静态方法能不能被重写#

严格说不是“重写”,而是“隐藏”。实例方法有运行时多态,静态方法没有。

一句话总结#

static 的本质是“把成员提升到类级别”,理解它之后,类成员和实例成员的边界就会清楚很多。

文章目录

文章目录