Java static 修饰符详解——从入门到精通
·
定位:本文深入解析 Java 中
static修饰符的核心原理、使用场景和最佳实践。从生活比喻出发,结合 JVM 内存模型和实际代码示例,帮你彻底理解 static 的本质和应用。
官方规范参考:
目录
- 1. 为什么需要理解 static?
- 2. static 的本质——从实例级到类级
- 3. static 字段——类的共享数据
- 4. static 方法——无需对象的功能
- 5. static 代码块——类的初始化逻辑
- 6. static 内部类——独立的嵌套类
- 7. static 在 JVM 中的内存模型
- 8. static 的线程安全问题
- 9. static 的设计模式应用
- 10. 常见面试题精选
- 11. 总结
1. 为什么需要理解 static?
先从生活说起
想象你在一家公司工作:
公司资源分类:
├─ 个人资源(实例成员)
│ ├─ 个人办公桌 → 每个员工都有自己的
│ ├─ 个人电脑 → 每个员工独立使用
│ └─ 个人文件 → 各自管理,互不干扰
│
└─ 公共资源(静态成员)
├─ 公司打印机 → 所有员工共用一台
├─ 会议室 → 大家都可以预约使用
└─ 公司制度 → 对所有员工都适用
Java 中的 static 就像公司的公共资源——属于整个类,而不是某个具体的对象实例。
static 的重要性
理解 static 对 Java 开发至关重要:
| 应用场景 | 涉及的 static 知识 | 不了解会怎样 |
|---|---|---|
| 工具类设计 | 静态方法的使用原则 | 设计出需要实例化的"工具类" |
| 单例模式 | 静态内部类、静态字段 | 无法实现线程安全的延迟加载 |
| 常量定义 | static final 的组合使用 | 每个对象都有一份常量副本,浪费内存 |
| 类初始化 | 静态代码块的执行时机 | 无法控制类的初始化逻辑 |
| 内存优化 | 静态成员的内存分配 | 不知道如何减少对象的内存占用 |
| 线程安全 | 静态变量的共享特性 | 多线程环境下出现数据不一致 |
2. static 的本质——从实例级到类级
归属关系的根本差异
public class Employee {
// 实例字段:每个员工对象都有自己的一份
private String name;
private int age;
private double salary;
// 静态字段:所有员工共享,属于 Employee 类
private static String companyName = "智比特科技";
private static int totalEmployees = 0;
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
totalEmployees++; // 每创建一个员工,总数加1
}
// 实例方法:需要通过具体的员工对象调用
public void work() {
System.out.println(name + " 正在工作");
}
// 静态方法:通过类名直接调用,不需要创建对象
public static void showCompanyInfo() {
System.out.println("公司名称:" + companyName);
System.out.println("员工总数:" + totalEmployees);
// ❌ 错误:静态方法不能直接访问实例字段
// System.out.println("员工姓名:" + name); // 编译错误
}
public static void main(String[] args) {
// 不创建对象就能调用静态方法
Employee.showCompanyInfo(); // 输出:员工总数:0
// 创建员工对象
Employee emp1 = new Employee("张三", 25, 8000);
Employee emp2 = new Employee("李四", 30, 12000);
// 静态字段被所有实例共享
Employee.showCompanyInfo(); // 输出:员工总数:2
// 实例方法需要通过对象调用
emp1.work(); // 输出:张三 正在工作
emp2.work(); // 输出:李四 正在工作
}
}
内存分配的差异
public class MemoryDemo {
// 静态变量:类加载时就分配内存,存储在方法区
private static int staticVar = 100;
// 实例变量:创建对象时分配内存,存储在堆中
private int instanceVar = 200;
static {
System.out.println("静态代码块执行:类加载时");
System.out.println("staticVar 初始化:" + staticVar);
}
{
System.out.println("实例代码块执行:创建对象时");
System.out.println("instanceVar 初始化:" + instanceVar);
}
public MemoryDemo() {
System.out.println("构造方法执行:创建对象时");
}
public static void main(String[] args) {
System.out.println("=== 第一次创建对象 ===");
MemoryDemo obj1 = new MemoryDemo();
System.out.println("\n=== 第二次创建对象 ===");
MemoryDemo obj2 = new MemoryDemo();
// 静态变量被所有实例共享
System.out.println("\n=== 验证共享性 ===");
System.out.println("obj1 通过实例访问静态变量:" + obj1.staticVar);
System.out.println("obj2 通过实例访问静态变量:" + obj2.staticVar);
System.out.println("通过类名访问静态变量:" + MemoryDemo.staticVar);
}
}
// 运行结果:
// 静态代码块执行:类加载时
// staticVar 初始化:100
// === 第一次创建对象 ===
// 实例代码块执行:创建对象时
// instanceVar 初始化:200
// 构造方法执行:创建对象时
//
// === 第二次创建对象 ===
// 实例代码块执行:创建对象时
// instanceVar 初始化:200
// 构造方法执行:创建对象时
//
// === 验证共享性 ===
// obj1 通过实例访问静态变量:100
// obj2 通过实例访问静态变量:100
// 通过类名访问静态变量:100
3. static 字段——类的共享数据
静态字段的特点
静态字段就像公司的公共财产,所有员工都能访问,但只有一份:
public class Counter {
// 静态字段:所有实例共享
private static int globalCount = 0;
// 实例字段:每个实例独有
private int instanceCount = 0;
public void increment() {
globalCount++; // 修改的是共享的静态字段
instanceCount++; // 修改的是当前实例的字段
}
public void showCounts() {
System.out.println("全局计数:" + globalCount +
", 实例计数:" + instanceCount);
}
public static void main(String[] args) {
Counter c1 = new Counter();
Counter c2 = new Counter();
Counter c3 = new Counter();
// 每个对象调用 increment
c1.increment();
c2.increment();
c3.increment();
// 查看结果
System.out.println("=== c1 的计数 ===");
c1.showCounts(); // 全局计数:3, 实例计数:1
System.out.println("=== c2 的计数 ===");
c2.showCounts(); // 全局计数:3, 实例计数:1
System.out.println("=== c3 的计数 ===");
c3.showCounts(); // 全局计数:3, 实例计数:1
// 静态字段可以通过类名直接访问(如果是 public)
System.out.println("通过类名访问:Counter.globalCount = " +
Counter.globalCount); // 需要将 globalCount 改为 public
}
}
静态常量的定义
public class Constants {
// 静态常量:整个应用程序共享的不变值
public static final String APP_NAME = "智比特教育平台";
public static final String VERSION = "2.1.0";
public static final int MAX_USERS = 10000;
public static final double PI = 3.14159265359;
// 复杂常量的初始化
public static final List<String> SUPPORTED_LANGUAGES;
public static final Map<String, String> ERROR_MESSAGES;
static {
// 静态代码块中初始化复杂常量
SUPPORTED_LANGUAGES = Arrays.asList("Java", "Python", "JavaScript");
Map<String, String> errors = new HashMap<>();
errors.put("001", "用户名不能为空");
errors.put("002", "密码长度不足");
errors.put("003", "邮箱格式错误");
ERROR_MESSAGES = Collections.unmodifiableMap(errors);
}
// 私有构造方法,防止实例化
private Constants() {
throw new UnsupportedOperationException("常量类不应该被实例化");
}
}
4. static 方法——无需对象的功能
静态方法的访问限制
静态方法就像公司的公共服务,不需要特定员工就能提供:
public class MathUtils {
// 静态方法:工具方法,不依赖于实例状态
public static int add(int a, int b) {
return a + b;
}
public static double calculateCircleArea(double radius) {
return Math.PI * radius * radius;
}
public static boolean isPrime(int number) {
if (number < 2) return false;
for (int i = 2; i <= Math.sqrt(number); i++) {
if (number % i == 0) return false;
}
return true;
}
// 静态方法不能直接访问实例成员
private int instanceField = 10;
public static void staticMethod() {
// ✅ 可以访问静态成员
System.out.println("调用静态方法:" + add(1, 2));
// ❌ 不能直接访问实例字段
// System.out.println(instanceField); // 编译错误
// ❌ 不能调用实例方法
// instanceMethod(); // 编译错误
// ✅ 但可以通过创建对象来访问
MathUtils utils = new MathUtils();
System.out.println("通过对象访问实例字段:" + utils.instanceField);
utils.instanceMethod();
}
public void instanceMethod() {
// ✅ 实例方法可以访问所有成员
System.out.println("实例字段:" + instanceField);
System.out.println("调用静态方法:" + add(5, 3));
}
public static void main(String[] args) {
// 静态方法通过类名调用
System.out.println("5 + 3 = " + MathUtils.add(5, 3));
System.out.println("圆面积 = " + MathUtils.calculateCircleArea(5.0));
System.out.println("17 是质数吗?" + MathUtils.isPrime(17));
// 调用静态方法
MathUtils.staticMethod();
}
}
main 方法的特殊性
public class MainMethodDemo {
private static String appName = "演示应用";
private String instanceName = "实例应用";
// main 方法必须是 static 的原因:
// JVM 启动时还没有创建任何对象实例,需要一个不依赖对象的入口点
public static void main(String[] args) {
System.out.println("程序启动:" + appName);
// main 方法中不能直接访问实例成员
// System.out.println(instanceName); // 编译错误
// 需要创建对象才能访问实例成员
MainMethodDemo demo = new MainMethodDemo();
System.out.println("实例名称:" + demo.instanceName);
// 处理命令行参数
if (args.length > 0) {
System.out.println("命令行参数:");
for (int i = 0; i < args.length; i++) {
System.out.println(" args[" + i + "] = " + args[i]);
}
}
}
}
5. static 代码块——类的初始化逻辑
静态代码块的执行时机
静态代码块就像公司的开业准备工作,只在公司成立时执行一次:
public class InitializationOrder {
// 1. 静态字段按声明顺序初始化
private static String field1 = getField1();
private static String field2 = getField2();
// 2. 静态代码块按出现顺序执行
static {
System.out.println("第一个静态代码块执行");
System.out.println("此时 field1 = " + field1);
System.out.println("此时 field2 = " + field2);
field1 = "静态代码块修改的 field1";
}
static {
System.out.println("第二个静态代码块执行");
System.out.println("修改后 field1 = " + field1);
}
// 3. 实例字段和代码块在创建对象时执行
private String instanceField = getInstanceField();
{
System.out.println("实例代码块执行");
}
// 4. 构造方法最后执行
public InitializationOrder() {
System.out.println("构造方法执行");
}
private static String getField1() {
System.out.println("初始化静态字段 field1");
return "初始 field1";
}
private static String getField2() {
System.out.println("初始化静态字段 field2");
return "初始 field2";
}
private String getInstanceField() {
System.out.println("初始化实例字段 instanceField");
return "实例字段";
}
public static void main(String[] args) {
System.out.println("=== 开始创建第一个对象 ===");
InitializationOrder obj1 = new InitializationOrder();
System.out.println("\n=== 开始创建第二个对象 ===");
InitializationOrder obj2 = new InitializationOrder();
}
}
// 运行结果:
// 初始化静态字段 field1
// 初始化静态字段 field2
// 第一个静态代码块执行
// 此时 field1 = 初始 field1
// 此时 field2 = 初始 field2
// 第二个静态代码块执行
// 修改后 field1 = 静态代码块修改的 field1
// === 开始创建第一个对象 ===
// 初始化实例字段 instanceField
// 实例代码块执行
// 构造方法执行
//
// === 开始创建第二个对象 ===
// 初始化实例字段 instanceField
// 实例代码块执行
// 构造方法执行
复杂初始化场景
public class DatabaseConfig {
// 数据库连接配置
private static final String DB_URL;
private static final String DB_USERNAME;
private static final String DB_PASSWORD;
private static final Properties CONFIG_PROPERTIES;
// 静态代码块进行复杂初始化
static {
System.out.println("开始加载数据库配置...");
// 从配置文件加载属性
CONFIG_PROPERTIES = new Properties();
try (InputStream input = DatabaseConfig.class
.getResourceAsStream("/database.properties")) {
if (input != null) {
CONFIG_PROPERTIES.load(input);
System.out.println("配置文件加载成功");
} else {
System.out.println("配置文件不存在,使用默认配置");
// 设置默认配置
CONFIG_PROPERTIES.setProperty("db.url", "jdbc:mysql://localhost:3306/test");
CONFIG_PROPERTIES.setProperty("db.username", "root");
CONFIG_PROPERTIES.setProperty("db.password", "password");
}
} catch (IOException e) {
System.err.println("配置文件加载失败:" + e.getMessage());
throw new RuntimeException("数据库配置初始化失败", e);
}
// 从属性中获取配置值
DB_URL = CONFIG_PROPERTIES.getProperty("db.url");
DB_USERNAME = CONFIG_PROPERTIES.getProperty("db.username");
DB_PASSWORD = CONFIG_PROPERTIES.getProperty("db.password");
System.out.println("数据库配置加载完成");
System.out.println("DB_URL: " + DB_URL);
System.out.println("DB_USERNAME: " + DB_USERNAME);
}
// 获取数据库连接的静态方法
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(DB_URL, DB_USERNAME, DB_PASSWORD);
}
// 私有构造方法,防止实例化
private DatabaseConfig() {}
}
6. static 内部类——独立的嵌套类
静态内部类 vs 非静态内部类
public class OuterClass {
private String outerInstanceField = "外部类实例字段";
private static String outerStaticField = "外部类静态字段";
// 静态内部类
public static class StaticInnerClass {
private String innerField = "静态内部类字段";
public void method() {
// ✅ 可以访问外部类的静态成员
System.out.println("访问外部类静态字段:" + outerStaticField);
// ❌ 不能直接访问外部类的实例成员
// System.out.println(outerInstanceField); // 编译错误
// ✅ 但可以通过外部类实例访问
OuterClass outer = new OuterClass();
System.out.println("通过实例访问:" + outer.outerInstanceField);
System.out.println("静态内部类自己的字段:" + innerField);
}
// 静态内部类可以有静态成员
public static void staticMethod() {
System.out.println("静态内部类的静态方法");
System.out.println("访问外部类静态字段:" + outerStaticField);
}
}
// 非静态内部类(普通内部类)
public class NonStaticInnerClass {
private String innerField = "非静态内部类字段";
public void method() {
// ✅ 可以直接访问外部类的所有成员
System.out.println("直接访问外部类实例字段:" + outerInstanceField);
System.out.println("直接访问外部类静态字段:" + outerStaticField);
System.out.println("非静态内部类自己的字段:" + innerField);
}
// ❌ 非静态内部类不能有静态成员(除了常量)
// public static void staticMethod() {} // 编译错误
// ✅ 但可以有静态常量
public static final String CONSTANT = "常量";
}
public static void main(String[] args) {
System.out.println("=== 静态内部类使用 ===");
// 静态内部类:可以直接创建,不需要外部类实例
StaticInnerClass staticInner = new StaticInnerClass();
staticInner.method();
// 调用静态内部类的静态方法
StaticInnerClass.staticMethod();
System.out.println("\n=== 非静态内部类使用 ===");
// 非静态内部类:需要先创建外部类实例
OuterClass outer = new OuterClass();
NonStaticInnerClass nonStaticInner = outer.new NonStaticInnerClass();
nonStaticInner.method();
// 或者这样创建
NonStaticInnerClass nonStaticInner2 = new OuterClass().new NonStaticInnerClass();
nonStaticInner2.method();
}
}
静态内部类的实际应用
// 建造者模式中的静态内部类应用
public class Person {
private final String name;
private final int age;
private final String email;
private final String phone;
// 私有构造方法,只能通过 Builder 创建
private Person(Builder builder) {
this.name = builder.name;
this.age = builder.age;
this.email = builder.email;
this.phone = builder.phone;
}
// 静态内部类 Builder
public static class Builder {
// 必填字段
private final String name;
private final int age;
// 可选字段
private String email;
private String phone;
public Builder(String name, int age) {
this.name = name;
this.age = age;
}
public Builder email(String email) {
this.email = email;
return this;
}
public Builder phone(String phone) {
this.phone = phone;
return this;
}
public Person build() {
return new Person(this);
}
}
// getter 方法
public String getName() { return name; }
public int getAge() { return age; }
public String getEmail() { return email; }
public String getPhone() { return phone; }
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age +
", email='" + email + "', phone='" + phone + "'}";
}
public static void main(String[] args) {
// 使用建造者模式创建对象
Person person1 = new Person.Builder("张三", 25)
.email("zhangsan@example.com")
.phone("13800138000")
.build();
Person person2 = new Person.Builder("李四", 30)
.email("lisi@example.com")
.build();
System.out.println(person1);
System.out.println(person2);
}
}
7. static 在 JVM 中的内存模型
内存分配位置
public class MemoryLocationDemo {
// 静态变量:存储在方法区(JDK 8+ 为元空间)
private static int staticVar = 100;
private static String staticString = "静态字符串";
private static final String CONSTANT = "常量字符串";
// 实例变量:存储在堆内存中
private int instanceVar = 200;
private String instanceString = "实例字符串";
// 静态方法:字节码存储在方法区
public static void staticMethod() {
System.out.println("静态方法执行");
// 局部变量:存储在虚拟机栈中
int localVar = 300;
String localString = "局部字符串";
System.out.println("静态变量:" + staticVar);
System.out.println("局部变量:" + localVar);
}
// 实例方法:字节码存储在方法区,但需要通过对象调用
public void instanceMethod() {
System.out.println("实例方法执行");
// 局部变量:存储在虚拟机栈中
int localVar = 400;
System.out.println("实例变量:" + instanceVar);
System.out.println("静态变量:" + staticVar);
System.out.println("局部变量:" + localVar);
}
public static void main(String[] args) {
// 演示内存分配
System.out.println("=== 内存分配演示 ===");
// 调用静态方法(不需要对象)
MemoryLocationDemo.staticMethod();
// 创建对象(在堆中分配内存)
MemoryLocationDemo obj1 = new MemoryLocationDemo();
MemoryLocationDemo obj2 = new MemoryLocationDemo();
// 调用实例方法
obj1.instanceMethod();
obj2.instanceMethod();
// 验证静态变量的共享性
System.out.println("\n=== 验证静态变量共享 ===");
System.out.println("obj1 访问静态变量:" + obj1.staticVar);
System.out.println("obj2 访问静态变量:" + obj2.staticVar);
System.out.println("类名访问静态变量:" + MemoryLocationDemo.staticVar);
// 修改静态变量
MemoryLocationDemo.staticVar = 999;
System.out.println("修改后 obj1 访问:" + obj1.staticVar);
System.out.println("修改后 obj2 访问:" + obj2.staticVar);
}
}
类加载与静态成员的关系
public class ClassLoadingDemo {
static {
System.out.println("ClassLoadingDemo 类被加载");
}
private static int staticField = initStaticField();
private static int initStaticField() {
System.out.println("初始化静态字段");
return 42;
}
public ClassLoadingDemo() {
System.out.println("创建 ClassLoadingDemo 实例");
}
public static void staticMethod() {
System.out.println("调用静态方法");
}
public static void main(String[] args) {
System.out.println("=== 程序开始 ===");
// 第一次访问类的静态成员,触发类加载
System.out.println("准备调用静态方法...");
ClassLoadingDemo.staticMethod();
System.out.println("\n=== 创建对象 ===");
ClassLoadingDemo obj = new ClassLoadingDemo();
System.out.println("\n=== 再次调用静态方法 ===");
ClassLoadingDemo.staticMethod(); // 类已加载,不会再次初始化
}
}
// 运行结果:
// === 程序开始 ===
// 准备调用静态方法...
// ClassLoadingDemo 类被加载
// 初始化静态字段
// 调用静态方法
//
// === 创建对象 ===
// 创建 ClassLoadingDemo 实例
//
// === 再次调用静态方法 ===
// 调用静态方法
8. static 的线程安全问题
静态变量的线程安全
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadSafetyDemo {
// 非线程安全的静态变量
private static int unsafeCounter = 0;
// 使用 volatile 保证可见性(但不保证原子性)
private static volatile int volatileCounter = 0;
// 线程安全的静态变量(使用同步)
private static int safeCounter = 0;
// 非线程安全的递增操作
public static void unsafeIncrement() {
unsafeCounter++; // 这不是原子操作!包含:读取、计算、写入
}
// volatile 变量的递增(仍然不是线程安全的)
public static void volatileIncrement() {
volatileCounter++; // volatile 只保证可见性,不保证原子性
}
// 线程安全的递增操作
public static synchronized void safeIncrement() {
safeCounter++; // synchronized 保证原子性和可见性
}
// 使用锁对象的线程安全操作
private static final Object lock = new Object();
private static int lockCounter = 0;
public static void lockIncrement() {
synchronized (lock) {
lockCounter++;
}
}
public static void main(String[] args) throws InterruptedException {
final int THREAD_COUNT = 10;
final int INCREMENT_COUNT = 1000;
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
// 启动多个线程同时操作静态变量
for (int i = 0; i < THREAD_COUNT; i++) {
executor.submit(() -> {
try {
for (int j = 0; j < INCREMENT_COUNT; j++) {
unsafeIncrement();
volatileIncrement();
safeIncrement();
lockIncrement();
}
} finally {
latch.countDown();
}
});
}
// 等待所有线程完成
latch.await();
executor.shutdown();
// 输出结果
int expectedValue = THREAD_COUNT * INCREMENT_COUNT;
System.out.println("期望值:" + expectedValue);
System.out.println("非安全计数器:" + unsafeCounter +
(unsafeCounter == expectedValue ? " ✓" : " ✗"));
System.out.println("volatile 计数器:" + volatileCounter +
(volatileCounter == expectedValue ? " ✓" : " ✗"));
System.out.println("安全计数器:" + safeCounter +
(safeCounter == expectedValue ? " ✓" : " ✗"));
System.out.println("锁计数器:" + lockCounter +
(lockCounter == expectedValue ? " ✓" : " ✗"));
}
}
静态初始化的线程安全
public class StaticInitializationSafety {
// JVM 保证静态初始化的线程安全
private static final ExpensiveResource RESOURCE = createResource();
private static ExpensiveResource createResource() {
System.out.println("开始创建昂贵资源... 线程:" +
Thread.currentThread().getName());
// 模拟耗时操作
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("昂贵资源创建完成... 线程:" +
Thread.currentThread().getName());
return new ExpensiveResource();
}
public static ExpensiveResource getResource() {
return RESOURCE;
}
static class ExpensiveResource {
private final long creationTime = System.currentTimeMillis();
public long getCreationTime() {
return creationTime;
}
}
public static void main(String[] args) throws InterruptedException {
// 创建多个线程同时访问静态资源
Thread[] threads = new Thread[5];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
System.out.println("线程 " + Thread.currentThread().getName() +
" 获取资源,创建时间:" +
StaticInitializationSafety.getResource().getCreationTime());
}, "Thread-" + i);
}
// 同时启动所有线程
for (Thread thread : threads) {
thread.start();
}
// 等待所有线程完成
for (Thread thread : threads) {
thread.join();
}
System.out.println("所有线程完成");
}
}
// 运行结果显示:
// 1. 只有一个线程执行资源创建
// 2. 所有线程获得的是同一个资源实例
// 3. JVM 保证了静态初始化的线程安全
9. static 的设计模式应用
单例模式
// 1. 饿汉式单例(静态字段)
public class EagerSingleton {
// 类加载时就创建实例
private static final EagerSingleton INSTANCE = new EagerSingleton();
private EagerSingleton() {
// 防止反射创建实例
if (INSTANCE != null) {
throw new RuntimeException("单例类不能被实例化");
}
}
public static EagerSingleton getInstance() {
return INSTANCE;
}
public void doSomething() {
System.out.println("饿汉式单例执行操作");
}
}
// 2. 懒汉式单例(静态内部类)
public class LazySingleton {
private LazySingleton() {}
// 静态内部类,实现延迟加载
private static class SingletonHolder {
private static final LazySingleton INSTANCE = new LazySingleton();
}
public static LazySingleton getInstance() {
return SingletonHolder.INSTANCE; // 第一次调用时才加载内部类
}
public void doSomething() {
System.out.println("懒汉式单例执行操作");
}
}
// 3. 枚举单例(推荐方式)
public enum EnumSingleton {
INSTANCE;
public void doSomething() {
System.out.println("枚举单例执行操作");
}
}
// 单例模式测试
class SingletonTest {
public static void main(String[] args) {
// 饿汉式
EagerSingleton eager1 = EagerSingleton.getInstance();
EagerSingleton eager2 = EagerSingleton.getInstance();
System.out.println("饿汉式相同实例:" + (eager1 == eager2));
// 懒汉式
LazySingleton lazy1 = LazySingleton.getInstance();
LazySingleton lazy2 = LazySingleton.getInstance();
System.out.println("懒汉式相同实例:" + (lazy1 == lazy2));
// 枚举式
EnumSingleton enum1 = EnumSingleton.INSTANCE;
EnumSingleton enum2 = EnumSingleton.INSTANCE;
System.out.println("枚举式相同实例:" + (enum1 == enum2));
}
}
工厂模式
// 静态工厂方法
public class ShapeFactory {
// 私有构造方法,防止实例化
private ShapeFactory() {}
// 静态工厂方法
public static Shape createShape(String type) {
if (type == null || type.isEmpty()) {
throw new IllegalArgumentException("形状类型不能为空");
}
switch (type.toLowerCase()) {
case "circle":
return new Circle();
case "rectangle":
return new Rectangle();
case "triangle":
return new Triangle();
default:
throw new IllegalArgumentException("不支持的形状类型:" + type);
}
}
// 带参数的静态工厂方法
public static Shape createCircle(double radius) {
return new Circle(radius);
}
public static Shape createRectangle(double width, double height) {
return new Rectangle(width, height);
}
}
// 形状接口
interface Shape {
void draw();
double getArea();
}
// 具体形状类
class Circle implements Shape {
private double radius;
public Circle() {
this.radius = 1.0;
}
public Circle(double radius) {
this.radius = radius;
}
@Override
public void draw() {
System.out.println("绘制圆形,半径:" + radius);
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
}
class Rectangle implements Shape {
private double width, height;
public Rectangle() {
this.width = this.height = 1.0;
}
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public void draw() {
System.out.println("绘制矩形,宽:" + width + ",高:" + height);
}
@Override
public double getArea() {
return width * height;
}
}
class Triangle implements Shape {
@Override
public void draw() {
System.out.println("绘制三角形");
}
@Override
public double getArea() {
return 0; // 简化实现
}
}
// 工厂模式测试
class FactoryTest {
public static void main(String[] args) {
// 使用静态工厂方法创建对象
Shape circle = ShapeFactory.createShape("circle");
Shape rectangle = ShapeFactory.createShape("rectangle");
circle.draw();
rectangle.draw();
// 使用带参数的工厂方法
Shape bigCircle = ShapeFactory.createCircle(5.0);
Shape bigRectangle = ShapeFactory.createRectangle(10.0, 8.0);
bigCircle.draw();
System.out.println("圆形面积:" + bigCircle.getArea());
bigRectangle.draw();
System.out.println("矩形面积:" + bigRectangle.getArea());
}
}
工具类模式
// 字符串工具类
public final class StringUtils {
// 私有构造方法,防止实例化
private StringUtils() {
throw new UnsupportedOperationException("工具类不应该被实例化");
}
// 判断字符串是否为空
public static boolean isEmpty(String str) {
return str == null || str.length() == 0;
}
public static boolean isNotEmpty(String str) {
return !isEmpty(str);
}
// 判断字符串是否为空白
public static boolean isBlank(String str) {
if (isEmpty(str)) {
return true;
}
for (int i = 0; i < str.length(); i++) {
if (!Character.isWhitespace(str.charAt(i))) {
return false;
}
}
return true;
}
public static boolean isNotBlank(String str) {
return !isBlank(str);
}
// 字符串反转
public static String reverse(String str) {
if (isEmpty(str)) {
return str;
}
return new StringBuilder(str).reverse().toString();
}
// 首字母大写
public static String capitalize(String str) {
if (isEmpty(str)) {
return str;
}
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
// 驼峰转下划线
public static String camelToSnake(String camelCase) {
if (isEmpty(camelCase)) {
return camelCase;
}
StringBuilder result = new StringBuilder();
for (int i = 0; i < camelCase.length(); i++) {
char c = camelCase.charAt(i);
if (Character.isUpperCase(c)) {
if (i > 0) {
result.append('_');
}
result.append(Character.toLowerCase(c));
} else {
result.append(c);
}
}
return result.toString();
}
// 下划线转驼峰
public static String snakeToCamel(String snakeCase) {
if (isEmpty(snakeCase)) {
return snakeCase;
}
StringBuilder result = new StringBuilder();
boolean capitalizeNext = false;
for (int i = 0; i < snakeCase.length(); i++) {
char c = snakeCase.charAt(i);
if (c == '_') {
capitalizeNext = true;
} else {
if (capitalizeNext) {
result.append(Character.toUpperCase(c));
capitalizeNext = false;
} else {
result.append(c);
}
}
}
return result.toString();
}
}
// 工具类测试
class UtilsTest {
public static void main(String[] args) {
// 测试字符串工具类
System.out.println("isEmpty(''): " + StringUtils.isEmpty(""));
System.out.println("isEmpty('hello'): " + StringUtils.isEmpty("hello"));
System.out.println("isBlank(' '): " + StringUtils.isBlank(" "));
System.out.println("reverse('hello'): " + StringUtils.reverse("hello"));
System.out.println("capitalize('hello'): " + StringUtils.capitalize("hello"));
System.out.println("camelToSnake('userName'): " + StringUtils.camelToSnake("userName"));
System.out.println("snakeToCamel('user_name'): " + StringUtils.snakeToCamel("user_name"));
}
}
10. 常见面试题精选
Q1:static 关键字的作用是什么?
回答思路:
static 的核心作用:
1. 改变成员的归属关系 — 从实例级别提升到类级别
2. 改变内存分配时机 — 从对象创建时提前到类加载时
3. 改变访问方式 — 可以通过类名直接访问,无需创建对象
4. 改变生命周期 — 从对象生命周期延长到类生命周期
具体应用:
• static 字段:类的共享数据,所有实例共享一份
• static 方法:工具方法,不依赖实例状态
• static 代码块:类的初始化逻辑
• static 内部类:独立的嵌套类,不持有外部类引用
Q2:static 方法为什么不能访问非静态成员?
回答思路:
根本原因:时机问题
• static 方法在类加载时就存在,可能在没有任何对象实例的情况下被调用
• 非静态成员属于具体的对象实例,只有创建对象后才存在
• static 方法执行时,可能还没有创建任何对象,所以不能访问实例成员
内存角度:
• static 方法存储在方法区,属于类级别
• 实例成员存储在堆中,属于对象级别
• static 方法没有 this 引用,无法定位到具体的对象实例
解决方案:
• 在 static 方法中创建对象实例,然后通过实例访问非静态成员
Q3:静态变量和实例变量的区别?
| 对比维度 | 静态变量 | 实例变量 |
|---|---|---|
| 归属 | 属于类 | 属于对象实例 |
| 内存位置 | 方法区(元空间) | 堆内存 |
| 分配时机 | 类加载时 | 对象创建时 |
| 生命周期 | 类加载到类卸载 | 对象创建到对象回收 |
| 访问方式 | 类名.变量名 | 对象.变量名 |
| 共享性 | 所有实例共享一份 | 每个实例独有一份 |
| 初始化 | 类初始化时 | 对象初始化时 |
Q4:静态代码块的执行时机?
public class ExecutionOrderDemo {
// 1. 静态变量按声明顺序初始化
private static String staticField = "静态字段初始化";
// 2. 静态代码块按出现顺序执行
static {
System.out.println("第一个静态代码块");
}
static {
System.out.println("第二个静态代码块");
}
// 3. 实例变量初始化(创建对象时)
private String instanceField = "实例字段初始化";
// 4. 实例代码块(创建对象时)
{
System.out.println("实例代码块");
}
// 5. 构造方法(创建对象时)
public ExecutionOrderDemo() {
System.out.println("构造方法");
}
}
// 执行顺序:
// 类加载时:静态字段初始化 → 静态代码块(按顺序)
// 创建对象时:实例字段初始化 → 实例代码块 → 构造方法
Q5:静态内部类和非静态内部类的区别?
public class InnerClassDemo {
private String outerField = "外部类字段";
private static String staticOuterField = "外部类静态字段";
// 静态内部类
static class StaticInner {
public void method() {
// ✅ 可以访问外部类静态成员
System.out.println(staticOuterField);
// ❌ 不能直接访问外部类实例成员
// System.out.println(outerField); // 编译错误
}
}
// 非静态内部类
class NonStaticInner {
public void method() {
// ✅ 可以访问外部类所有成员
System.out.println(outerField);
System.out.println(staticOuterField);
}
}
public static void main(String[] args) {
// 静态内部类:直接创建
StaticInner staticInner = new StaticInner();
// 非静态内部类:需要外部类实例
InnerClassDemo outer = new InnerClassDemo();
NonStaticInner nonStaticInner = outer.new NonStaticInner();
}
}
Q6:如何保证静态变量的线程安全?
public class ThreadSafeSolutions {
// 1. 使用 synchronized 方法
private static int counter1 = 0;
public static synchronized void increment1() {
counter1++;
}
// 2. 使用 synchronized 代码块
private static int counter2 = 0;
private static final Object lock = new Object();
public static void increment2() {
synchronized (lock) {
counter2++;
}
}
// 3. 使用 AtomicInteger
private static final AtomicInteger counter3 = new AtomicInteger(0);
public static void increment3() {
counter3.incrementAndGet();
}
// 4. 使用 volatile(只保证可见性,不保证原子性)
private static volatile int counter4 = 0;
// 注意:counter4++ 仍然不是线程安全的
// 5. 不可变对象(天然线程安全)
private static final String CONSTANT = "不可变字符串";
private static final List<String> IMMUTABLE_LIST =
Collections.unmodifiableList(Arrays.asList("a", "b", "c"));
}
11. 总结
static 关键字速记
"类共享,早分配,无实例,需同步"
类共享 — static 成员属于类,所有实例共享
早分配 — 类加载时就分配内存,不等对象创建
无实例 — static 方法不能访问实例成员,没有 this
需同步 — 多线程访问静态变量需要同步机制
核心特点总结
| 特点 | 说明 | 示例 |
|---|---|---|
| 归属关系 | 属于类,不属于实例 | Math.PI、System.out |
| 内存位置 | 方法区(元空间) | 所有静态成员都在这里 |
| 访问方式 | 类名.成员名 | String.valueOf() |
| 生命周期 | 类加载到程序结束 | 比对象生命周期更长 |
| 共享性 | 所有实例共享一份 | 计数器、配置信息 |
| 初始化 | 类加载时初始化 | 静态代码块、静态字段 |
使用建议
✅ 适合使用 static 的场景
- 工具方法:
StringUtils.isEmpty() - 常量定义:
public static final String CONSTANT - 单例模式:静态内部类实现
- 工厂方法:
valueOf()、getInstance() - 计数器:统计创建的对象数量
- 配置信息:全局配置参数
❌ 不适合使用 static 的场景
- 依赖实例状态的方法:需要访问实例字段
- 需要多态的方法:static 方法不能被重写
- 频繁变化的数据:可能导致线程安全问题
- 大量数据:静态变量不会被垃圾回收
最佳实践
- 工具类设计:私有构造方法 + 静态方法
- 常量定义:使用
static final组合 - 单例实现:推荐静态内部类方式
- 线程安全:静态变量需要考虑同步
- 内存管理:避免静态变量持有大对象引用
- 测试友好:避免过度使用静态方法,影响单元测试
通过深入理解 static 关键字,你将能够:
- 设计出更优雅的工具类和单例
- 正确处理类级别的数据和行为
- 避免常见的线程安全陷阱
- 写出更高效、更易维护的 Java 代码
记住:static 不仅仅是一个关键字,它代表了一种设计思想——将通用的、不依赖实例状态的功能提升到类级别,实现更好的代码复用和组织。
更多推荐

所有评论(0)