Java Static关键字详解
本文讲解Java的static关键字。它修饰的成员属于类,被所有对象共享。这些静态成员不依赖于对象实例,可直接通过类名进行访问。
我们来详细、系统地讲解一下Java中的static关键字。
static是Java中一个非常重要的关键字,它用于修饰类的成员,包括变量、方法、代码块和内部类。static的核心思想是:被static修饰的成员属于类本身,而不是类的某个具体实例(对象)。
为了更好地理解,我们可以把一个类(Class)想象成一张“蓝图”,而对象(Object/Instance)是根据这张蓝图建造出来的“房子”。
- 非静态成员(实例成员):像是每栋房子里各自的家具、电器。每栋房子都有自己的电视、冰箱,它们是独立的。
- 静态成员(类成员):像是这个小区(由同一张蓝图建造的所有房子)共享的设施,比如小区的游泳池或大门。它不属于任何一栋具体的房子,而是所有房子共享的。
static可以修饰什么?
static主要用于修-饰以下四种结构:
- 静态变量 (Static Variables / Class Variables)
- 静态方法 (Static Methods / Class Methods)
- 静态代码块 (Static Initialization Blocks)
- 静态内部类 (Static Nested Classes)
下面我们逐一详细解释。
1. 静态变量 (Static Variables)
当一个变量被声明为static时,它就不再是对象的属性,而是类的属性。这意味着:
- 内存中只有一份:无论这个类创建了多少个对象,静态变量在内存中都只有一个副本。
- 所有对象共享:所有该类的对象共享这一个静态变量。任何一个对象修改了它的值,其他所有对象看到的值都会改变。
- 生命周期长:它随着类的加载而创建,随着类的卸载而销毁。它的生命周期比任何对象的生命周期都长。
- 访问方式:推荐使用
类名.静态变量名的方式访问,虽然也可以通过对象.静态变量名访问,但前者更能体现其“类级别”的特性,也更清晰。
示例: 统计创建了多少个学生对象。
java
class Student {
String name; // 实例变量,每个学生都有自己的名字
int age; // 实例变量,每个学生都有自己的年龄
static int studentCount = 0; // 静态变量,所有学生对象共享
public Student(String name, int age) {
this.name = name;
this.age = age;
studentCount++; // 每创建一个学生对象,计数器加1
}
public void showInfo() {
System.out.println("Name: " + name + ", Age: " + age);
}
}
public class Main {
public static void main(String[] args) {
System.out.println("Initial student count: " + Student.studentCount); // 推荐访问方式
Student s1 = new Student("Alice", 18);
System.out.println("After creating s1, student count: " + Student.studentCount);
Student s2 = new Student("Bob", 19);
System.out.println("After creating s2, student count: " + Student.studentCount);
// 不推荐,但也可以通过对象访问
System.out.println("Accessing via s1: " + s1.studentCount);
}
}
输出:
plaintext
Initial student count: 0
After creating s1, student count: 1
After creating s2, student count: 2
Accessing via s1: 2
2. 静态方法 (Static Methods)
当一个方法被声明为static时,它成为一个类方法。
- 不依赖于对象:调用静态方法不需要创建类的实例,可以直接通过
类名.方法名()来调用。 - 无法访问实例成员:由于静态方法不与任何特定对象关联,它不能直接访问类中的非静态(实例)变量和非静态(实例)方法。因为它不知道你想访问哪个对象的实例成员。
- 不能使用
this或super:this关键字代表当前对象,super代表父类对象。既然静态方法不属于任何对象,自然也就不能使用this和super。 - 可以访问静态成员:静态方法可以直接访问本类的其他静态变量和调用其他静态方法。
示例: 创建一个数学工具类。
java
class MathUtils {
// 这是一个静态方法,因为它执行的操作不依赖于任何对象的状态
public static int add(int a, int b) {
return a + b;
}
// 这是一个实例方法,假设它需要访问某个对象的状态
public double PI = 3.14;
public double circleArea(double radius) {
return PI * radius * radius; // 访问了实例变量 PI
}
public static void tryAccessInstanceMember() {
// 下面这行会编译错误!
// System.out.println(PI); // 错误: 无法从静态上下文中引用非静态 变量 PI
// circleArea(1.0); // 错误: 无法从静态上下文中引用非静态 方法 circleArea(double)
}
}
public class Main {
public static void main(String[] args) {
// 直接通过类名调用静态方法
int sum = MathUtils.add(5, 10);
System.out.println("Sum: " + sum);
// 要调用实例方法,必须先创建对象
MathUtils myUtil = new MathUtils();
double area = myUtil.circleArea(2.0);
System.out.println("Area: " + area);
}
}
最经典的静态方法例子就是 java.lang.Math 类,里面所有的方法如 Math.sqrt(), Math.max() 都是静态的。
3. 静态代码块 (Static Initialization Blocks)
静态代码块是一段用static { ... }包裹起来的代码。
- 执行时机:它在类被加载到JVM时执行,并且只执行一次。
- 执行顺序:它的执行时机早于任何对象的创建,也早于任何静态方法的调用。如果一个类有多个静态代码块,它们会按照在代码中出现的顺序依次执行。
- 主要用途:通常用于对静态变量进行复杂的初始化。比如,从配置文件读取信息来初始化静态变量。
示例:
java
class Config {
public static String DB_URL;
public static int MAX_CONNECTIONS;
// 静态代码块
static {
System.out.println("Static block is executing...");
// 模拟从配置文件加载配置
DB_URL = "jdbc:mysql://localhost:3306/mydb";
MAX_CONNECTIONS = 10;
}
// 构造函数
public Config() {
System.out.println("Constructor is called.");
}
}
public class Main {
public static void main(String[] args) {
System.out.println("Main method started.");
System.out.println("DB URL: " + Config.DB_URL); // 第一次访问静态成员,会触发类的加载
System.out.println("Max Connections: " + Config.MAX_CONNECTIONS);
System.out.println("--------------------");
new Config(); // 创建对象
}
}
输出:
plaintext
Main method started.
Static block is executing...
DB URL: jdbc:mysql://localhost:3306/mydb
Max Connections: 10
--------------------
Constructor is called.
注意,静态代码块在main方法中第一次访问Config.DB_URL时执行,且只执行了一次。
4. 静态内部类 (Static Nested Classes)
一个类可以定义在另一个类的内部,这被称为内部类。如果用static修饰,它就变成了静态内部类。
- 与外部类的关系:静态内部类不持有对外部类实例的引用。它就像一个普通的顶级类,只是恰好被“包装”在另一个类的命名空间里。
- 实例化:创建静态内部类的实例时,不需要先创建外部类的实例。
- 访问限制:它只能访问外部类的静态成员,不能访问外部类的实例成员。
示例: 经典的Builder设计模式就常用静态内部类实现。
java
class Computer {
// 实例变量
private final String CPU;
private final String RAM;
private final String storage;
private Computer(Builder builder) {
this.CPU = builder.CPU;
this.RAM = builder.RAM;
this.storage = builder.storage;
}
@Override
public String toString() {
return "Computer [CPU=" + CPU + ", RAM=" + RAM + ", storage=" + storage + "]";
}
// 静态内部类 Builder
public static class Builder {
private String CPU;
private String RAM;
private String storage;
public Builder setCPU(String CPU) {
this.CPU = CPU;
return this;
}
public Builder setRAM(String RAM) {
this.RAM = RAM;
return this;
}
public Builder setStorage(String storage) {
this.storage = storage;
return this;
}
public Computer build() {
return new Computer(this);
}
}
}
public class Main {
public static void main(String[] args) {
// 创建静态内部类的实例,并用它来构建外部类对象
Computer gamingPC = new Computer.Builder()
.setCPU("Intel i9")
.setRAM("32GB")
.setStorage("1TB SSD")
.build();
System.out.println(gamingPC);
}
}
为什么 main 方法是 public static void?
public:因为它需要被JVM从任何地方调用。static:因为程序启动时,还没有创建任何对象。JVM需要一个入口点来执行代码,而不需要先创建一个类的实例。static方法正好满足这个要求。void:因为它执行完毕后,不需要返回任何值给JVM。
总结 (Key Takeaways)
| 特性 | 静态成员 (static) |
实例成员 (非static) |
|---|---|---|
| 归属 | 属于类 | 属于对象 (实例) |
| 内存 | 在方法区/元空间中,只有一份 | 在堆内存中,每个对象都有一份 |
| 生命周期 | 随类的加载而生,随类的卸载而灭 | 随对象的创建而生,随对象的销毁而灭 |
| 访问方式 | 类名.成员 (推荐) |
对象引用.成员 |
| 方法内访问 | static方法只能访问static成员 |
实例方法既可以访问static成员,也可以访问实例成员 |
this关键字 |
static方法中不能使用this |
实例方法中可以使用this |
| 常见用途 | 工具类方法、常量(static final)、共享计数器、单例模式 |
描述对象自身状态和行为的属性和方法 |
右滑查看面试常问