static 是 Java 中一个非常核心和重要的关键字,它主要用于内存管理和方便调用,当一个成员(变量、方法、代码块或内部类)被声明为 static 时,它就不再属于类的某个实例(对象),而是属于类本身。

这意味着,无论你创建了这个类的多少个对象,static 成员在内存中只有一份副本,所有对象共享这个副本。
下面我们从几个方面来详细阐述 static 的作用。
静态变量 (Static Variables / Class Variables)
静态变量也叫类变量,它与实例变量的最大区别在于:
- 生命周期:静态变量随着类的加载而创建,随着类的卸载而销毁,它的生命周期与类相同,而不是与对象。
- 内存存储:存储在方法区(或称为静态存储区),而不是堆内存中。
- 共享性:所有该类的对象共享同一个静态变量,一个对象对静态变量的修改,会影响到所有其他对象。
语法:
public class Student {
// 实例变量,每个对象都有自己独立的副本
private String name;
// 静态变量,所有对象共享这一个副本
private static int studentCount = 0;
public Student(String name) {
this.name = name;
// 每创建一个学生,总人数就加1
studentCount++;
}
public static int getStudentCount() {
return studentCount;
}
}
使用示例:
public class Main {
public static void main(String[] args) {
System.out.println("初始学生人数: " + Student.getStudentCount()); // 输出: 0
Student s1 = new Student("张三");
System.out.println("创建张三后学生人数: " + Student.getStudentCount()); // 输出: 1
Student s2 = new Student("李四");
System.out.println("创建李四后学生人数: " + Student.getStudentCount()); // 输出: 2
// 可以通过类名直接访问,也可以通过对象访问(但不推荐)
System.out.println("通过对象s1访问: " + s1.getStudentCount()); // 输出: 2
}
}
常见用途:
- 记录某个类的实例总数(如上面的
studentCount)。 - 全局配置信息(如数据库连接URL、API密钥等)。
- 常量(虽然更推荐使用
public static final)。
静态方法 (Static Methods / Class Methods)
静态方法也叫类方法,它与实例方法的区别在于:
- 操作对象:静态方法不能直接访问实例变量和实例方法,因为它不依赖于任何具体的对象实例,它只能直接访问静态变量和静态方法。
- 调用方式:可以通过类名直接调用,也可以通过对象名调用(但推荐使用类名调用,以示区分)。
- 隐式参数:静态方法没有
this引参数,因为它不操作任何对象。
语法:
public class MathUtils {
// 静态方法,不依赖于任何对象
public static int add(int a, int b) {
return a + b;
}
// 实例方法
public void printMessage() {
System.out.println("This is an instance method.");
}
}
使用示例:
public class Main {
public static void main(String[] args) {
// 通过类名直接调用,非常方便
int sum = MathUtils.add(10, 20);
System.out.println("计算结果: " + sum); // 输出: 30
// 也可以通过对象调用,但不推荐
MathUtils utils = new MathUtils();
int sum2 = utils.add(5, 5);
System.out.println("计算结果: " + sum2); // 输出: 10
// 错误示例:静态方法不能访问非静态成员
// MathUtils.printMessage(); // 编译错误!
}
}
常见用途:
- 工具类:如
java.lang.Math中的Math.sqrt(),Math.max()等,这些方法不操作任何状态,只是提供一些通用的功能。 - 工厂方法:用于创建对象。
- 主方法:
public static void main(String[] args)是程序的入口,它必须是静态的,因为JVM在加载类后需要立即执行它,此时还没有创建任何对象。
静态代码块 (Static Initialization Block)
静态代码块是在类加载时(ClassLoader 加载类并对其链接和初始化时)执行的一段代码。
- 执行时机:只执行一次,在创建第一个对象或访问静态成员之前执行。
- 执行顺序:按照在代码中出现的顺序执行。
- 用途:通常用于初始化静态变量,特别是当初始化过程比较复杂(如读取配置文件、建立数据库连接池等)时。
语法:
public class DatabaseConnection {
private static Connection connection;
// 静态代码块
static {
System.out.println("正在初始化数据库连接池...");
try {
// 模拟加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 模拟建立连接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "password");
System.out.println("数据库连接池初始化成功!");
} catch (Exception e) {
System.err.println("数据库连接初始化失败!");
e.printStackTrace();
}
}
public static Connection getConnection() {
return connection;
}
}
使用示例:
public class Main {
public static void main(String[] args) {
System.out.println("Main方法开始执行。");
// 第一次访问静态成员,触发类加载和静态代码块执行
DatabaseConnection.getConnection();
System.out.println("-------------------------------");
// 第二次访问静态成员,静态代码块不会再执行
DatabaseConnection.getConnection();
}
}
输出:
正在初始化数据库连接池...
数据库连接池初始化成功!
Main方法开始执行。
-------------------------------
静态内部类 (Static Nested Classes)
内部类是定义在另一个类内部的类,如果内部类被声明为 static,它就变成了静态内部类。
- 与外部类的关系:静态内部类不持有对外部类实例的隐式引用(即没有
Outer.this),它更像是一个“附在外部类上的普通类”。 - 创建方式:可以直接通过
new Outer.Inner()创建,不需要先创建外部类的对象。 - 访问权限:它可以访问外部类的所有静态成员(变量和方法),但不能直接访问外部类的实例成员。
语法:
public class OuterClass {
private static int staticOuterVar = 10;
private int instanceOuterVar = 20;
// 静态内部类
public static class StaticInnerClass {
public void display() {
// 可以访问外部类的静态成员
System.out.println("外部类的静态变量: " + staticOuterVar);
// 不能直接访问外部类的实例成员,编译会报错
// System.out.println("外部类的实例变量: " + instanceOuterVar); // 错误!
}
}
}
使用示例:
public class Main {
public static void main(String[] args) {
// 直接创建静态内部类的实例,无需外部类的实例
OuterClass.StaticInnerClass inner = new OuterClass.StaticInnerClass();
inner.display(); // 输出: 外部类的静态变量: 10
}
}
常见用途:
- 当一个类只被另一个类使用,并且不想让它被外部访问时。
- 当内部类不需要访问外部类的实例成员时,使用静态内部类可以避免内存泄漏的风险(因为不持有外部类引用)。
静态导入 (Static Import - import static)
这是一个语法糖,可以让你无需通过类名前缀来直接访问静态成员。
语法:
// 静态导入 Math 类的所有静态成员
import static java.lang.Math.*;
public class Main {
public static void main(String[] args) {
// 不再需要 Math.PI 和 Math.sqrt()
double radius = 10;
double area = PI * radius * radius;
double root = sqrt(area);
System.out.println("面积: " + area);
System.out.println("平方根: " + root);
}
}
注意: 过度使用静态导入可能会降低代码的可读性,因为它模糊了静态成员的来源,通常只用于被广泛使用的工具类(如 Math)。
总结表格
| 作用 | 关键点 | 内存位置 | 生命周期 | 访问方式 |
|---|---|---|---|---|
| 静态变量 | 属于类,所有对象共享 | 方法区 | 与类相同 | ClassName.var 或 obj.var (不推荐) |
| 静态方法 | 不能访问实例成员,没有 this |
方法区 | 与类相同 | ClassName.method() 或 obj.method() (不推荐) |
| 静态代码块 | 类加载时执行一次 | 方法区 | 执行即结束 | JVM自动调用 |
| 静态内部类 | 不持有外部类引用 | 与普通类相同 | 与类相同 | new Outer.Inner() |
| 静态导入 | 简化静态成员的调用 | - | - | import static |
核心思想
static 的核心思想:“类级别”而非“实例级别”,当你希望某个成员被类的所有实例共享,或者在不创建对象的情况下就能访问某个功能时,就应该使用 static。
