乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Java概述与环境搭建

    • Java概述与环境搭建
    • Java语言概述
    • 解释型语言与编译型语言对比
    • JDK安装与配置
    • JDK、JRE、JVM 详解
    • HelloWorld程序详解
    • IDE 介绍
  • 第2章 标识符与基本数据类型

    • 章节导读
    • 变量概述
    • 常量概述
    • 基本类型与包装类
    • 字节型 byte
    • 短整型 short
    • 整型 int
    • 长整型 long
    • 单精度浮点型 float
    • 双精度浮点型 double
    • 字符型 char
    • 布尔型 boolean
    • 类型转换
  • 第3章 运算符与表达式

    • 章节导读
    • 算术运算符
    • 赋值运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 运算符优先级
    • 表达式
  • 第4章 流程控制

    • 章节导读
    • 常见的程序运行流程
    • if-else 选择结构
    • switch 多分支选择
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
  • 第5章 数组

    • 章节导读
    • 一维数组
    • 多维数组
    • Arrays 工具类
  • 第6章 类与对象

    • 章节导读
    • 类与对象
    • 方法定义与调用
    • 构造方法
    • 封装
    • 访问修饰符
    • package 与 import
    • static 关键字
    • this 关键字
    • 参数传递 详解
    • 枚举
    • 成员内部类
    • 局部内部类
    • 静态内部类
    • 匿名内部类
  • 第7章 接口与继承

    • 章节导读
    • 继承
    • super 关键字
    • final 关键字
    • 多态
    • 向上转型与向下转型
    • 抽象类
    • 接口
    • 抽象类与接口对比
  • 第8章 注解

    • 章节导读
    • 注解基础
    • 元注解详解
    • 自定义注解
  • 第9章 常用类

    • 章节导读:Java 常用类
    • Object 类:万类之祖
    • 包装类:基本类型的对象化
    • String:不可变的字符串
    • StringBuffer:线程安全的可变字符串
    • StringBuilder:可变的字符串构建器
    • Math:数学运算工具类
    • Random:伪随机数生成器
    • 大数值运算 详解
    • 日期时间API 详解
  • 第10章 异常机制

    • 章节导读
    • 异常体系与分类
    • try-catch-finally
    • try-with-resources
    • throws 与 throw
    • 自定义异常
  • 第11章 泛型

    • 章节导读
    • 泛型基础
    • 通配符与PECS原则
    • 类型擦除
  • 第12章 集合框架

    • 章节导读
    • 集合框架概述
    • ArrayList
    • LinkedList
    • HashMap 详解
    • LinkedHashMap 详解
    • TreeMap 详解
    • HashSet
    • TreeSet 详解
    • TreeSet 与 Comparable
    • Collections 工具类详解
  • 第13章 IO流

    • 章节导读
    • IO流概述
    • 字节流
    • 字符流
    • 缓冲流
    • 转换流 详解
    • 序列化 详解
    • NIO与Files 详解
    • NIO与Files工具类
  • 第14章 多线程与并发

    • 第十六章 多线程与并发 —— 章节导读
    • 线程基础详解
    • synchronized 详解
    • Lock 与显式锁详解
    • volatile 详解
    • wait 与 notify 详解
    • ThreadLocal详解
    • 原子类详解
    • 并发工具类详解
    • 线程池详解
  • 第15章 反射

    • 章节导读
    • 反射概述与 Class 对象
    • Constructor 与对象创建
    • Field与Method详解
    • 反射应用详解
  • 第16章 JDK8新特性

    • 章节导读
    • Lambda 表达式
    • Stream API 基础
    • Stream API 高级详解
    • Optional 详解
    • 新日期时间API详解
  • 第17章 JDK9-11新特性

    • 章节导读
    • 模块化系统 — Project Jigsaw(JDK 9)
    • var 局部变量类型推断(JDK 10)
    • 集合工厂方法与增强(JDK 9 / 10 / 11)
    • 接口增强:private 方法(JDK 9)
    • Stream API 增强(JDK 9)
    • Optional 增强(JDK 9 / 10 / 11)
    • String 新增方法(JDK 11)
    • HTTP Client 与 Files 增强(JDK 11)
    • 直接运行 Java 源文件 — JEP 330(JDK 11)
  • 第18章 JDK12-17新特性

    • 章节导读
    • Switch 表达式(JDK 12 预览 / JDK 14 正式)
    • 文本块 Text Blocks(JDK 13 预览 / JDK 15 正式)
    • Records 记录类(JDK 14 预览 / JDK 16 正式)
    • 密封类 Sealed Classes(JDK 15 预览 / JDK 17 正式)
    • instanceof 模式匹配(JDK 14 预览 / JDK 16 正式)
    • Switch 模式匹配 — Pattern Matching for switch(JDK 17 预览 / JDK 21 正式)
    • Helpful NPE 与 String 增强(JDK 12 / JDK 14 / JDK 15)
    • Stream 增强(JDK 12 / JDK 16)
    • 日期时间增强 — Day Period 支持(JDK 16)
  • 第19章 JDK18-21新特性

    • 章节导读
    • 虚拟线程(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 序列集合(JDK 21 正式)
    • Switch 模式匹配(JDK 17 预览 / JDK 18 第二预览 / JDK 20 第四预览 / JDK 21 正式)
    • Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 未命名模式与变量(JDK 21 预览 / JDK 22 正式)
  • 第20章 JDK 22-25 新特性

    • 章节导读
    • 字符串模板(JDK 22 预览 / JDK 23 第二预览 / JDK 24 第三预览)
    • Stream Gatherers(JDK 22 预览 / JDK 24 第二预览)
    • 隐式声明类与实例方法(JDK 23 预览 / JDK 24 第二预览)
    • 原始类型模式匹配(JDK 24 预览)
  • 附录

    • Java 核心知识点
    • Java SE 专业术语
    • Java特性索引(JDK 8 → 25)

static 关键字

本章定位:static 将成员从"对象级别"提升到"类级别"。理解 static 变量是所有实例共享的"全局黑板"、static 方法是"没有 this 的工具函数",以及 static 代码块在类加载时的执行机制。这是理解 JVM 类加载过程和单例模式的前置知识。


static 的哲学:从"你的"到"大家的"

周二下午,小崔在 Employee 类里定义了一个 companyName 变量。每创建一个员工,都重复存一遍"飞翔科技"这四个字。白歌看了一眼说:"小崔,公司名应该是所有员工共享的——加个 static 让它变成类级别的变量,所有对象只用存一份。"

小崔问道:"那 static 到底是什么?"

白歌在白板上画了两个圈:

static 修饰成员的含义:该成员不属于任何特定的实例对象,而是属于整个类。它在类加载时就已存在,不依赖于任何对象的创建。


static 修饰成员变量(类变量)

存储位置与生命周期

对比维度实例变量(非 static)类变量(static)
存储位置堆内存中的对象内部方法区(JDK 8:元空间 Metaspace)
内存份数每个对象各有一份全类只有一份
创建时机对象被 new 时类被加载时(早于任何对象创建)
销毁时机对象被 GC 回收时类被卸载时(通常 JVM 退出时才卸载)
访问方式对象.变量类名.变量(推荐)或 对象.变量(不推荐)
默认值有有
public class Employee {
    // 实例变量 —— 每个员工各不相同
    private long id;
    private String name;
    private double salary;

    // 类变量 —— 所有员工共享
    public static String companyName = "飞翔科技";
    private static int employeeCount = 0;  // 用 static 做全局计数器

    public Employee(long id, String name, double salary) {
        this.id = id;
        this.name = name;
        this.salary = salary;
        employeeCount++;  // 每次创建新员工,计数器 +1
    }

    public static int getEmployeeCount() {
        return employeeCount;
    }
}

// 使用
public class StaticDemo {
    public static void main(String[] args) {
        System.out.println(Employee.companyName);  // ✅ 推荐:通过类名访问

        Employee emp1 = new Employee(1001, "大翔", 50000);
        Employee emp2 = new Employee(1002, "白歌", 35000);

        System.out.println(emp1.companyName);       // 可以但不推荐
        System.out.println(Employee.companyName);    // ✅ 最佳实践

        // 修改静态变量——通过一个引用修改,对所有引用可见
        Employee.companyName = "飞翔科技集团(更名)";
        System.out.println(emp1.companyName);  // 输出:飞翔科技集团(更名)
        System.out.println(emp2.companyName);  // 输出:飞翔科技集团(更名)

        System.out.println("当前员工数:" + Employee.getEmployeeCount());  // 2
    }
}

static 修饰方法(类方法)

核心特性

特性说明
属于类通过 类名.方法名() 调用,无需创建对象
不能访问实例成员静态方法中没有 this,无法直接访问实例变量和实例方法
可以访问静态成员可以访问其他静态变量和静态方法
不能被重写静态方法可以被继承,但不能被重写(可被"隐藏")
工具类的首选如 Math、Arrays、Collections 中的方法
public class MathUtils {
    // 私有构造方法防止实例化(工具类惯用手法)
    private MathUtils() { }

    // 静态方法 —— 可以直接通过类名调用
    public static long factorial(int n) {
        if (n < 0) throw new IllegalArgumentException("n 不能为负数");
        long result = 1;
        for (int i = 2; i <= n; i++) {
            result *= i;
        }
        return result;
    }

    public static double circleArea(double radius) {
        return Math.PI * radius * radius;
    }
}

// 使用
long f5 = MathUtils.factorial(5);  // 120 —— 不需要 new MathUtils()

为什么静态方法不能访问实例成员?

public class Demo {
    private int instanceVar = 10;           // 实例变量
    private static int staticVar = 20;      // 静态变量

    public void instanceMethod() {
        System.out.println(instanceVar);    // ✅ 有 this,可以访问
        System.out.println(staticVar);      // ✅ 也可以访问静态变量
    }

    public static void staticMethod() {
        // System.out.println(instanceVar); // ❌ 没有 this,不知道是谁的 instanceVar
        System.out.println(staticVar);      // ✅ 静态变量是类级别的,当然可以访问
    }
}

根本原因:调用实例方法时,JVM 必须知道"是哪个对象调用的",因此会传入 this;调用静态方法时,可能根本没有对象存在(Demo.staticMethod()),没有 this,自然无法访问属于某个具体对象的实例成员。


static 代码块

static 代码块在类加载时执行,且仅执行一次。常用于复杂的静态变量初始化。

public class AppConfig {
    public static String DB_URL;
    public static String DB_USERNAME;
    public static String DB_PASSWORD;

    // static 代码块 —— 类加载时执行一次
    static {
        System.out.println("正在加载飞翔科技系统配置...");
        // 模拟读取配置文件
        DB_URL = "jdbc:mysql://10.0.0.1:3306/feixiang";
        DB_USERNAME = "feixiang_admin";
        DB_PASSWORD = "FX@2025#Secure";

        System.out.println("配置加载完成!");
        System.out.println("DB_URL = " + DB_URL);
    }
}

static 代码块 vs 普通代码块 vs 构造方法

代码块类型执行时机执行次数典型用途
static 代码块类加载时1 次加载配置、注册驱动、初始化静态集合
构造代码块 {}每次 new 对象时,在构造方法前执行每次 new 都执行所有构造方法的公共初始化代码
构造方法每次 new 对象时每次 new 都执行对象的特定初始化
public class InitOrderDemo {
    // 静态变量
    private static int staticVar = initStaticVar();

    private static int initStaticVar() {
        System.out.println("① 静态变量初始化");
        return 100;
    }

    // static 代码块
    static {
        System.out.println("② 静态代码块执行");
    }

    // 实例变量
    private int instanceVar = initInstanceVar();

    private int initInstanceVar() {
        System.out.println("③ 实例变量初始化");
        return 200;
    }

    // 构造代码块
    {
        System.out.println("④ 构造代码块执行");
    }

    // 构造方法
    public InitOrderDemo() {
        System.out.println("⑤ 构造方法执行");
    }

    public static void main(String[] args) {
        System.out.println("--- 开始创建第一个对象 ---");
        new InitOrderDemo();
        System.out.println("--- 开始创建第二个对象 ---");
        new InitOrderDemo();
    }
}
运行输出:
① 静态变量初始化
② 静态代码块执行
--- 开始创建第一个对象 ---
③ 实例变量初始化
④ 构造代码块执行
⑤ 构造方法执行
--- 开始创建第二个对象 ---
③ 实例变量初始化
④ 构造代码块执行
⑤ 构造方法执行

关键观察:静态初始化只执行一次(第 2 个对象时没有"①"和"②"),而实例初始化每次都执行。


静态导入(import static,JDK 5+)

静态导入允许直接使用其他类的静态成员,无需每次都写类名。

// 传统方式
public class WithoutStaticImport {
    public static void main(String[] args) {
        double result = Math.sqrt(Math.pow(3, 2) + Math.pow(4, 2));
        System.out.println("斜边长: " + result);
    }
}

// 静态导入方式
import static java.lang.Math.sqrt;
import static java.lang.Math.pow;
// 或者:import static java.lang.Math.*;

public class WithStaticImport {
    public static void main(String[] args) {
        double result = sqrt(pow(3, 2) + pow(4, 2));  // 省略 Math.
        System.out.println("斜边长: " + result);
    }
}
使用建议说明
✅ 推荐高频使用的静态常量或工具方法:import static java.lang.Math.PI
✅ 推荐测试框架断言:import static org.junit.Assert.*
❌ 不推荐滥用 * 导入,导致代码可读性下降——读者分不清方法是本地的还是导入的
❌ 不推荐导入多个类的同名静态方法——产生歧义

完整示例:飞翔科技全局配置与员工计数器

场景

大翔要求小崔实现一个系统全局配置类,并让 Employee 类具备自动计数能力。孔蓝负责验证计数逻辑在多线程场景下是否仍然准确。

/**
 * 飞翔科技 —— 系统配置类
 * 演示:static 变量、static 代码块、静态导入
 */
public class SysConfig {
    // ========== 静态配置项 ==========
    public static final String COMPANY_NAME = "飞翔科技";
    public static final String COMPANY_SHORT = "飞翔科技";
    public static final double MIN_SALARY = 3000.0;
    public static final double MAX_SALARY = 500000.0;

    public static String DB_URL;
    public static String DB_USER;
    public static String DB_PASS;

    // ========== static 代码块:系统启动时加载 ==========
    static {
        System.out.println(">>> 飞翔科技系统初始化 <<<");
        DB_URL = "jdbc:mysql://db.feixiang.net:3306/hr";
        DB_USER = "hr_admin";
        DB_PASS = "hr@2025";
        System.out.println("数据库配置加载完成...");
    }

    // 私有构造方法 —— 防止实例化
    private SysConfig() { }

    // 静态工具方法
    public static boolean isSalaryValid(double salary) {
        return salary >= MIN_SALARY && salary <= MAX_SALARY;
    }
}
/**
 * 飞翔科技 —— Employee 类(带 static 计数器)
 */
import static com.feixiang.SysConfig.COMPANY_NAME;
import static com.feixiang.SysConfig.isSalaryValid;

public class Employee {
    private long id;
    private String name;
    private double salary;

    // ========== static 计数器 ==========
    private static long nextId = 1000;     // 工号生成器
    private static int employeeCount = 0;  // 员工总数

    public Employee(String name, double salary) {
        if (!isSalaryValid(salary)) {     // 使用静态导入的方法
            throw new IllegalArgumentException(
                "薪资 " + salary + " 不合法,范围:[" + SysConfig.MIN_SALARY + ", " + SysConfig.MAX_SALARY + "]");
        }
        this.id = ++nextId;               // 自动生成工号
        this.name = name;
        this.salary = salary;
        employeeCount++;
    }

    public void showInfo() {
        System.out.printf("[%s] 工号:%d | 姓名:%s | 薪资:%.0f%n",
                COMPANY_NAME, id, name, salary);  // COMPANY_NAME 来自静态导入
    }

    // ========== 静态方法 ==========
    public static int getEmployeeCount() {
        return employeeCount;
    }

    public static String getCompanyInfo() {
        return COMPANY_NAME + " — 员工总数:" + employeeCount;
    }
}
/**
 * 测试类
 */
public class StaticTest {
    public static void main(String[] args) {
        System.out.println("===== " + Employee.getCompanyInfo() + " =====\n");

        // 创建员工(static 计数器自动递增)
        Employee emp1 = new Employee("大翔", 50000);
        Employee emp2 = new Employee("白歌", 35000);
        Employee emp3 = new Employee("小崔", 8000);
        Employee emp4 = new Employee("黄俪", 12000);
        Employee emp5 = new Employee("孔蓝", 15000);

        emp1.showInfo();
        emp2.showInfo();
        emp3.showInfo();
        emp4.showInfo();
        emp5.showInfo();

        System.out.println("\n" + Employee.getCompanyInfo());

        // 测试薪资校验
        System.out.println("\n--- 薪资校验 ---");
        System.out.println("5000 是否合法?" + isSalaryValid(5000));   // true
        System.out.println("2000 是否合法?" + isSalaryValid(2000));   // false

        // 尝试创建薪资不合法的员工
        try {
            new Employee("测试", 100);
        } catch (IllegalArgumentException e) {
            System.out.println("异常:" + e.getMessage());
            System.out.println("员工总数仍为:" + Employee.getEmployeeCount());  // 5(没有 +1)
        }
    }
}
运行输出:
>>> 飞翔科技系统初始化 <<<
数据库配置加载完成...
===== 飞翔科技 — 员工总数:0 =====

[飞翔科技] 工号:1001 | 姓名:大翔 | 薪资:50000
[飞翔科技] 工号:1002 | 姓名:白歌 | 薪资:35000
[飞翔科技] 工号:1003 | 姓名:小崔 | 薪资:8000
[飞翔科技] 工号:1004 | 姓名:黄俪 | 薪资:12000
[飞翔科技] 工号:1005 | 姓名:孔蓝 | 薪资:15000

飞翔科技 — 员工总数:5

--- 薪资校验 ---
5000 是否合法?true
2000 是否合法?false
异常:薪资 100.0 不合法,范围:[3000.0, 500000.0]
员工总数仍为:5

易错场景

反例一:静态方法中访问实例变量

// ❌ 错误:静态方法中直接使用实例变量
public class Employee {
    private String name;  // 实例变量

    public static void printName() {
        // ❌ 编译错误:无法从静态上下文中引用非静态变量 name
        // System.out.println(name);
        // 编译器问:name 是谁的 name?没有 this,无法确定
    }
}

纠正:

// ✅ 正确:如果确实需要,传入对象参数
public static void printName(Employee emp) {
    System.out.println(emp.name);  // 明确指定是哪个对象的 name
}

反例二:误用对象引用访问静态成员

// ❌ 不推荐:通过 null 引用访问静态方法(虽然语法上可以!)
Employee emp = null;
emp.getEmployeeCount();  // 竟然不抛 NullPointerException!
// 因为 getEmployeeCount() 是静态方法,编译器会直接替换为 Employee.getEmployeeCount()
// 这造成了"可以通过 null 调用方法"的假象,极度迷惑
// ✅ 正确:始终通过类名访问静态成员
Employee.getEmployeeCount();

反例三:静态变量导致的"状态污染"

// ❌ 隐患:单元测试中静态变量残留
public class UserService {
    private static int loginAttempts = 0;  // 全局登录尝试次数

    public void login(String username, String password) {
        loginAttempts++;
        // ...
    }
}

// 测试类
public class UserServiceTest {
    @Test
    public void test1() {
        new UserService().login("a", "p");
        // loginAttempts 现在是 1
    }

    @Test
    public void test2() {
        new UserService().login("b", "p");
        // loginAttempts 是 2!因为 test1 没有重置它!
        // 测试之间互相影响 —— 非常难排查
    }
}

纠正:测试框架(如 JUnit)的 @Before 或 @After 中手动重置静态变量,或避免使用可变静态变量。


面试考点

Q1:static 关键字可以修饰哪些内容?

① 成员变量:变成类变量,所有实例共享,存储在方法区/元空间; ② 成员方法:变成类方法,可通过类名直接调用,内部不能使用 this; ③ 代码块:类加载时执行一次,常用于复杂静态变量的初始化; ④ 内部类:静态内部类(static nested class),不持有外部类引用(详见内部类章节); ⑤ 导入:import static 静态导入,可直接使用其他类的静态成员。

Q2:为什么静态方法中不能使用 this 和 super?

this 和 super 都依赖于当前对象。静态方法属于类级别,调用时可能根本没有对象存在(如 ClassName.staticMethod())。JVM 不会为静态方法传入 this 引用,因此在静态方法中使用 this 或 super 会导致编译错误。

Q3:静态变量和实例变量的内存分配有什么区别?

静态变量存储在方法区(JDK 8 后为元空间 Metaspace),类加载时分配内存,类卸载时释放,全类只有一份。 实例变量存储在堆内存中,位于每个对象内部,随对象创建而分配,随对象被 GC 回收而释放,每个对象各有一份。 一个常见误解是"对象存储在堆中,静态变量当然也在堆中"——实际上,堆中的对象通过 Klass Pointer(类型指针)指向方法区中的类元数据来访问静态变量。

Q4:什么情况下应该使用静态导入?

① 高频使用的常量(如 Math.PI、TimeUnit.SECONDS); ② 测试断言方法(如 JUnit 的 assertEquals、Mockito 的 mock); ③ 静态工厂方法频繁调用时。 避免滥用:不要用 * 导入,不要导入多个同名的静态方法,不要让代码丢失"来源可追溯性"。

Q5:static 和 final 一起使用的含义是什么?

static final 表示全局常量。static 确保只有一份拷贝,final 确保不可修改。命名惯例为全大写+下划线(MAX_VALUE)。如果是基本类型或 String,编译期常量会在编译时内联到引用处,提高性能。


小崔的笔记:"static 把东西从'我的'变成'大家的'。静态变量存在方法区,所有对象共享同一份。静态方法没有 this,所以不能碰实例变量——就像公司公告不能写'今天张三请假',只能写'所有员工注意'。static 代码块在类加载时跑一次,非常适合初始化配置。"

上一页
package 与 import
下一页
this 关键字