臭名昭著aTrust隔离计划1.臭名昭著aTrust隔离计划2.VPN原理与aTrust隔离网络实践3.docker-easyconnect到底做了什么4.TUN(tunnel-隧道-虚拟网卡)模式
个人项目
personal(个人展示)GitHub Actions 自动部署个人展示项目
基于Quartz搭建的个人博客1.Quartz个人博客使用教程2.使用 rsync 增量部署 Quartz 博客3.使用 GitHub Actions 自动部署 Quartz 博客5.域名绑定6.CDN加速7.Google 搜索收录排查:为什么关键词搜得到别人,却搜不到我的网站?Obsidian、双向链接与知识图谱
架构设计
分布式分布式事务RPC原理
旅行
行程攻略青岛三日游行程攻略
前端
NginxNginx配置与反向代理入门
Nodenpm与npx的区别
数据库
mysql01.MySQL基础语法与约束02.MySQL数据库类型03.MySQL多表查询04.MySQL 索引05.MySQL 事务06.MySQL 隔离级别07.MySQL MVCC08.MySQL 锁09.EXPLAIN10.慢查询与 SQL 优化
mysql面试题01.索引问题02.事务与隔离级别03.锁机制04.存储引擎与性能优化05.用户权限、字符集与备份恢复
mysql实战01.EXPLAIN实战02.测试环境菜单误删排查与恢复03.MySQL IN 详解
网络
网络安全1.无线监听网卡苹果M系列虚拟机安装Kali Linux教程3.kali怎么使用RTL8812AU4.kali基础命令
网络基础进程之间怎样通过网络传递数据
网址常用网址
中间键
esElasticsearch 入门
mq
RabbitMQRabbitMQ高级RabbitMQ基础
1. MQ 基础2. MQ 核心角色与代码对应3. MQ Consumer Group 与消费模式4. SOFAMQ 账户变更代码实践5. MQ 可靠性问题6. 消息幂等
redis缓存击穿、穿透与雪崩Redis 分布式锁Redis 缓存问题Redis 数据结构
AI
使用说明
第三方插件&技能简介Archify使用与安装指南geekgeekRun使用OfferFlow使用指南Ponytail使用指南Skills Manager使用指南
CodexCodex CLI与IDE区别及使用指南Codex Hook单独配置与提交通知Codex MCP安装与使用指南Codex第三方插件安装与使用指南
Agent开发0-学习路线1.LLM、Token、上下文窗口与模型参数2.大模型 API、请求参数与响应结构3.Spring AI ChatClient4.Prompt、System Prompt、Prompt 模板6.结构化输出、JSON Schema7.SSE 流式响应8.会话 ID、聊天记录、Redis9.超时、重试、限流、降级10.*完成可运行聊天接口11.Function Calling、Tool Calling 原理12.用 Java 方法定义 Tool13.工具参数设计和校验14.实现订单查询工具15.实现物流查询工具16.实现库存查询工具17.多个工具自动选择18.工具异常、超时、重试
diy主机
主机零件学习1.CPU2.主板3.内存4.显卡5.硬盘6.散热7.风扇8.机箱热门型号价格表
捡垃圾diy-13600kf+5060diy-14600kf+5070
English高频词汇英文基础语法简单句子音标
GitGit常用命令Sublime Merge 使用指南
Java
工具库Caffeine本地缓存Reactor教程
工作实践如何使用@Async线程池使用与排查线上服务 CPU 使用率过高排查压测下RPC接口大量失败排查记录
基础1.HashMap2.ConcurrentHashMap3.HashSet4.final5.static6.volatile7.AtomicInteger8.String动态代理反射锁与线程同步线程注解
面试题1.HashMap 存储在方法内是线程安全的吗2.ArrayList参数传递与引用重新赋值3.== 和 equals 的区别、hashcode4.重载和重写的区别5.String、StringBuffer、StringBuilder区别及使用场景6.接口和抽象类7.ArrayList 与 LinkedList 的区别和扩容机制8.HashMap 和 Hashtable 的区别及底层实现9.Object 和 Objects 的区别10.ThreadLocal的原理和使用场景11.字节码是什么及作用12.Java类加载器有哪些13.双亲委派模型14.Java异常体系15.GC是什么16.GC如何判断对象可以被回收
面试总结0.HR面试模板1.面试一2.面试二3.面试三笔试
网络与 IO(黑马程序员-纯理论)01-网络编程基础02-IO模型与零拷贝03-BIO04-NIO基础与缓冲区05-NIO通道与选择器06-AIO
juc00-目录01-进程与线程02-线程同步与通信03-并发设计模式04-Java内存模型05-CAS与原子类06-ThreadLocal07-阻塞队列08-线程池使用与原理09-任务调度10-ForkJoin与享元模式11-AQS12-ReentrantLock13-读写锁14-并发协作工具15-ConcurrentHashMap16-CopyOnWrite与同步容器17-SkipListMap18-非阻塞队列
juc(工作常见)Java并发编程三个重点拆解
jvm1. JVM运行时数据区
Spring1.Spring IoC、DI与构造方法注入CGLIB、Byte Buddy 与 Spring AOPSpring 常用注解详解Spring Bean 生命周期
Spring MVC异常处理机制
linux常用命令
mac快捷键终端常用操作
python
基础01.Python是什么,为什么要安装Python02.Python项目中.venv是什么03.Python基础语法
基础实战sql替换
HTTPXHRMS员工详情接口调用(Python HTTPX)
windowswindows激活

一、💡 一句话理解

核心结论

static 表示成员属于类本身,而不是属于某一个对象。静态成员由同一个类的对象共享,通常使用 类名.成员名 访问。

面试时先回答这一句,再展开静态变量、静态方法、静态代码块和 static final。

二、🧭 理论:它是什么

2.1 static 和普通成员有什么区别

class Student {
    // 静态变量:属于 Student 类,所有 Student 对象共享。
    static String schoolName = "第一中学";
 
    // 实例变量:属于具体对象,每个 Student 都有自己的 name。
    String name;
}
对比项静态成员实例成员
属于谁类对象
有多少份同一个类通常一份每个对象一份
推荐访问方式Student.schoolNamestudent.name
是否需要先创建对象不需要需要
能否直接使用 this不能能

2.2 static 可以修饰什么

面试常见的四种形式:

class Example {
    static int count;          // 静态变量
 
    static void print() {      // 静态方法
    }
 
    static {                   // 静态代码块
        count = 10;
    }
 
    static class Helper {      // 静态内部类,更准确叫静态嵌套类
    }
}

static 不能修饰构造方法和普通局部变量。普通顶层类也不能声明为 static。

三、⚙️ 理论:面试常问什么

3.1 static 变量和实例变量有什么区别

静态变量属于类,所有对象共享;实例变量属于对象,每个对象单独保存。

class User {
    static int total;
    String name;
}

创建两个 User 后:

  • total 只有一份,两个对象都能看到;
  • name 有两份,每个对象保存自己的值。

共享不代表线程安全

多个线程同时修改静态变量时,仍可能出现数据竞争。static 不提供原子性、可见性或互斥,相关知识见 6.volatile、7.AtomicInteger 和 锁与线程同步。

3.2 静态方法能直接访问实例变量吗

不能。静态方法属于类,调用时可能还没有创建任何对象,因此 Java 不知道应该访问哪个对象的数据。

class User {
    String name;
 
    static void printName() {
        // 编译错误:静态方法不知道要读取哪个 User 对象的 name。
        // System.out.println(name);
    }
}

如果静态方法获得了具体对象,就可以通过该对象访问:

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

访问规则可以记成:

当前位置直接访问静态成员直接访问实例成员
实例方法可以可以
静态方法可以不可以,必须先获得对象

3.3 静态方法中为什么不能使用 this

this 表示当前对象。静态方法不依赖某个对象调用,所以没有当前对象,也就不能使用 this。

class User {
    String name;
 
    void instanceMethod() {
        // 实例方法针对当前对象执行,可以使用 this。
        System.out.println(this.name);
    }
 
    static void staticMethod() {
        // 编译错误:静态上下文中没有 this。
        // System.out.println(this.name);
    }
}

3.4 static 代码块什么时候执行

静态代码块在类初始化时执行,并且对一次类初始化只执行一次。静态字段的显式赋值和静态代码块按照源码中的先后顺序执行。

class InitDemo {
    static int first = print("1. 静态变量");
 
    static {
        System.out.println("2. 静态代码块");
    }
 
    static int print(String message) {
        System.out.println(message);
        return 1;
    }
}

类的加载、准备和初始化不是同一个阶段,详细过程见 2. JVM的类加载机制。

3.5 static final 一定是常量吗

static 表示类共享,4.final 表示变量只能赋值一次。两者常一起用于声明常量:

static final int MAX_RETRY_TIMES = 3;

但如果保存的是对象引用,final 只保证引用不能重新指向其他对象,不保证对象内容不能改变:

import java.util.ArrayList;
import java.util.List;
 
static final List<String> NAMES = new ArrayList<>();
 
// 可以修改 List 内部的数据。
NAMES.add("小明");
 
// 不能让 NAMES 指向另一个 List。
// NAMES = new ArrayList<>();

因此,static final 引用不等于不可变对象。

3.6 static 方法可以被重写吗

先记住结论:static 方法不能被重写。

子类可以定义一个与父类同名、参数也相同的静态方法,但这叫方法隐藏(method hiding),不是方法重写。

3.6.1 为什么它不算重写

方法重写解决的问题是:

同一个实例方法,面对不同的实际对象,运行时应该执行哪一个版本?

例如,变量表面上是 Parent 类型,实际装入的是 Child 对象。调用实例方法时,Java 会在运行时查看实际对象,因此执行 Child 的方法:

class Parent {
    void instanceMethod() {
        System.out.println("Parent 实例方法");
    }
}
 
class Child extends Parent {
    @Override
    void instanceMethod() {
        System.out.println("Child 实例方法");
    }
}
 
Parent value = new Child();
value.instanceMethod(); // 输出 Child 实例方法

判断过程是:

  1. value 的编译时类型是 Parent;
  2. value 实际指向的对象是 Child;
  3. instanceMethod() 是实例方法,具有运行时多态;
  4. 所以运行时根据实际对象找到 Child.instanceMethod()。

但是静态方法属于类,不属于某个实际对象。调用静态方法时,编译器根据左边的类名或变量的编译时类型,就已经决定调用哪个类的方法,不会等到运行时再检查对象。

3.6.2 同名 static 方法实际发生了什么

class Parent {
    static void staticMethod() {
        System.out.println("Parent 静态方法");
    }
}
 
class Child extends Parent {
    // 这里不能写 @Override,因为这不是重写,而是隐藏父类的同名静态方法。
    static void staticMethod() {
        System.out.println("Child 静态方法");
    }
}
 
Parent first = new Child();
Child second = new Child();
 
first.staticMethod();  // 输出 Parent 静态方法
second.staticMethod(); // 输出 Child 静态方法

逐行判断:

调用代码变量的编译时类型编译器绑定的方法输出
first.staticMethod()ParentParent.staticMethod()Parent 静态方法
second.staticMethod()ChildChild.staticMethod()Child 静态方法

虽然 first 实际也指向 Child 对象,但这件事对静态方法没有影响。因为静态方法不是交给对象执行的。

不要用对象变量调用静态方法

Java 允许写 first.staticMethod(),但这种写法很容易让人误以为存在运行时多态。实际开发中应写成 Parent.staticMethod() 或 Child.staticMethod(),直接表明方法属于哪个类。

可以把两者记成:

  • 实例方法看实际对象:可以被重写,运行时决定;
  • 静态方法看声明类型:只能被隐藏,编译时决定。

3.7 main 方法为什么是 static

public static void main(String[] args) {
}

先把程序启动前的场景想清楚。执行下面的命令时:

java Application

Java 启动器只得到了一个类名 Application。它会加载这个类,然后寻找约定好的入口方法:

public static void main(String[] args)

此时启动器还没有创建 Application 对象,也不应该擅自决定怎样创建。

3.7.1 如果 main 不是 static,会出现什么问题

假设入口方法是实例方法:

public void main(String[] args) {
}

那么启动器必须先做类似下面的事情:

Application application = new Application();
application.main(args);

问题马上出现了:

  • 如果类没有无参构造方法,应该调用哪个构造方法?
  • 构造参数从哪里来?
  • 如果类是抽象类,根本不能创建对象,怎么办?
  • 如果创建对象时就要依赖配置、数据库连接或其他对象,又该由谁准备?

Java 没有替程序员猜这些规则,而是把入口方法设计为 static。这样启动器只要知道类名,就能直接调用类方法;不需要先获得某个对象。

可以把启动过程简单理解成:

执行 java Application
        ↓
加载 Application 类
        ↓
直接调用 Application.main(args)
        ↓
从 main 内部开始执行程序

3.7.2 static main 不代表程序不能创建对象

main 只是程序启动的入口。进入 main 以后,当然可以根据程序自己的规则创建对象:

public class Application {
 
    public static void main(String[] args) {
        // Java 启动器只负责调用静态入口。
        // 进入入口以后,由程序自己决定创建什么对象、传什么参数。
        Application application = new Application("生产环境");
        application.start();
    }
 
    private final String environment;
 
    Application(String environment) {
        this.environment = environment;
    }
 
    void start() {
        System.out.println("启动环境:" + environment);
    }
}

这里的职责分工是:

角色负责什么
Java 启动器根据固定规则找到并调用静态 main
main 方法接收启动参数,组织程序的启动过程
程序自己的代码按真实需要创建对象、传入参数并调用实例方法

一句话记忆

main 是 static,因为程序启动时“只有类,还没有对象”;等进入 main 后,再由程序自己决定要创建哪些对象。

3.8 static 变量存在哪里

面试时不要简单回答“都在方法区”。更稳妥的说法是:

静态字段属于类,它的生命周期通常与对应的 Class 一致。具体物理存放位置属于 JVM 实现细节,不能仅凭 Java 语言规范断言所有 JVM 都放在同一个固定区域。

如果面试官继续问 HotSpot 的内存结构,再结合具体 JDK 版本讨论。Java 内存区域见 1. JVM运行时数据区。

四、🚀 实践:把面试题跑一遍

4.1 前置准备

只需要 JDK 17 或更高版本,不需要第三方依赖:

java -version
javac -version

4.2 这个例子验证什么

下面的完整程序只验证面试高频问题:

  1. 静态变量是否被多个对象共享;
  2. 实例变量是否属于各自对象;
  3. 静态代码块何时执行;
  4. 静态方法如何访问实例数据;
  5. static final 如何使用;
  6. 静态方法隐藏和实例方法重写有什么区别。

4.3 完整实践:StaticInterviewDemo.java

创建 StaticInterviewDemo.java:

public class StaticInterviewDemo {
 
    public static void main(String[] args) {
        // 第一次主动使用 Student 时,会先初始化 Student 类。
        // 因此静态代码块会在创建第一个对象前执行。
        Student first = new Student("小明");
        Student second = new Student("小红");
 
        // name 是实例变量,每个对象保存自己的值。
        System.out.println("第一个学生:" + first.name);
        System.out.println("第二个学生:" + second.name);
 
        // total 是静态变量,两个对象共享同一份计数。
        System.out.println("学生总数:" + Student.total);
 
        // static final 常量通过类名访问。
        System.out.println("学校名称:" + Student.SCHOOL_NAME);
 
        // 静态方法没有 this,但可以通过传入的对象读取实例变量。
        Student.printName(first);
 
        // 静态方法发生隐藏:调用结果由引用的编译时类型决定。
        Parent parent = new Child();
        parent.staticMethod();
 
        // 实例方法发生重写:运行时调用实际对象 Child 的方法。
        parent.instanceMethod();
    }
 
    static class Student {
        // static final:属于类,并且不能重新赋值。
        static final String SCHOOL_NAME = "第一中学";
 
        // 静态变量:所有 Student 对象共享。
        static int total;
 
        // 实例变量:每个 Student 对象各自保存。
        String name;
 
        static {
            // 类初始化时执行一次。
            System.out.println("Student 静态代码块执行");
        }
 
        Student(String name) {
            // this 代表当前创建的 Student 对象。
            this.name = name;
 
            // 每创建一个对象,共享计数加一。
            total++;
        }
 
        static void printName(Student student) {
            // 静态方法通过明确的对象访问实例变量。
            System.out.println("静态方法打印姓名:" + student.name);
        }
    }
 
    static class Parent {
        static void staticMethod() {
            System.out.println("Parent 静态方法");
        }
 
        void instanceMethod() {
            System.out.println("Parent 实例方法");
        }
    }
 
    static class Child extends Parent {
        // 这是静态方法隐藏,不是重写。
        static void staticMethod() {
            System.out.println("Child 静态方法");
        }
 
        // 这是实例方法重写,具有运行时多态。
        @Override
        void instanceMethod() {
            System.out.println("Child 实例方法");
        }
    }
}

编译并运行:

javac StaticInterviewDemo.java
java StaticInterviewDemo

预期输出:

Student 静态代码块执行
第一个学生:小明
第二个学生:小红
学生总数:2
学校名称:第一中学
静态方法打印姓名:小明
Parent 静态方法
Child 实例方法

结果说明:

  • 静态代码块只输出一次;
  • 两个对象共享 total,最终值是 2;
  • 两个对象的 name 互不影响;
  • parent.staticMethod() 调用 Parent,说明静态方法是隐藏;
  • parent.instanceMethod() 调用 Child,说明实例方法发生了重写。

4.4 高频报错:静态方法直接访问实例变量

class User {
    String name;
 
    static void print() {
        // 编译错误:non-static variable name cannot be referenced
        // from a static context
        System.out.println(name);
    }
}

原因:静态方法没有当前对象,不知道应该读取哪个 name。

不要为了消除报错就随手把 name 改成 static。如果姓名属于具体用户,应把 print() 改成实例方法,或者向静态方法传入一个 User 对象。

五、📌 总结

  • static 成员属于类,实例成员属于对象。
  • 静态方法没有 this,不能直接访问实例成员。
  • 静态代码块在类初始化时执行,一次类初始化只执行一次。
  • static final 引用不能重新赋值,但对象内容仍可能可变。
  • 静态方法在继承中是隐藏,不是重写。
  • static 共享变量不会自动获得线程安全。

面试记忆句:类共享用 static,对象独有不用 static;静态方法没有当前对象,所以没有 this。

六、📚 官方资料