当你希望某个成员(变量或方法)不属于任何一个具体的对象实例,而是属于这个类本身时,就应该使用 static。

下面我们从几个方面来详细解释 static 的使用场景和注意事项。
静态变量
静态变量(也称为类变量)是被所有该类的对象实例共享的。
什么时候用?
-
共享状态:当一个类的所有实例都需要共享同一个数据时。
-
经典例子:在一个游戏中,玩家类
Player有一个属性是totalPlayerCount(总玩家数),每当创建一个新玩家,这个总数就应该加一,这个totalPlayerCount就应该被所有Player对象共享,而不是每个玩家都有自己的“总玩家数”,所以它应该被声明为static。
(图片来源网络,侵删) -
代码示例:
public class Player { // 每个玩家自己的名字(非静态,属于对象) private String name; // 所有玩家共享的总数(静态,属于类) private static int totalPlayerCount = 0; public Player(String name) { this.name = name; totalPlayerCount++; // 创建新玩家时,总数增加 } public static int getTotalPlayerCount() { return totalPlayerCount; } } // 使用 Player p1 = new Player("Alice"); Player p2 = new Player("Bob"); System.out.println(Player.getTotalPlayerCount()); // 输出: 2
-
-
常量定义:Java 中定义常量的标准方式就是使用
public static final。- 例子:
Math.PI,System.out。 - 代码示例:
public class MathConstants { // PI 是一个固定不变的值,所有实例共享,所以用 static final public static final double PI = 3.14159; }
- 例子:
如何访问?
- 通过类名访问(推荐):
Player.totalPlayerCount - 通过对象名访问(不推荐,容易引起混淆):
p1.totalPlayerCount
静态方法
静态方法(也称为类方法)在不创建任何对象实例的情况下就可以被调用。
什么时候用?
-
工具方法:当一个方法不依赖于任何对象的状态(即不访问任何非静态的成员变量)时,它非常适合被声明为静态。
-
例子:
Math.sqrt(),Collections.sort(),Arrays.toString(),这些方法只是执行一些计算或操作,它们不关心是哪个Math对象调用了它们,因为Math根本没有可改变的状态。 -
代码示例:
public class ArrayUtils { // 这个方法不依赖于任何对象实例的状态,所以是静态的 public static int findMax(int[] array) { if (array == null || array.length == 0) { throw new IllegalArgumentException("Array cannot be empty"); } int max = array[0]; for (int i = 1; i < array.length; i++) { if (array[i] > max) { max = array[i]; } } return max; } } // 使用,无需创建 ArrayUtils 对象 int[] numbers = {1, 5, 3, 9, 2}; int maxNumber = ArrayUtils.findMax(numbers); System.out.println("Max number is: " + maxNumber); // 输出: Max number is: 9
-
-
工厂方法:用于创建和返回该类的一个新实例。
-
例子:
BigInteger.valueOf(long val)。 -
代码示例:
public class User { private String username; private User(String username) { this.username = username; } // 静态工厂方法,创建 User 实例 public static User create(String username) { return new User(username); } } // 使用 User user = User.create("john_doe");
-
-
访问静态变量:当一个方法只需要操作静态变量时,它也应该是静态的。
静态方法的限制?
静态方法不能直接访问:
- 非静态成员变量(实例变量)
- 非静态成员方法(实例方法)
因为它没有 this 引用。this 代表当前对象的引用,而静态方法不属于任何一个对象。
静态代码块
静态代码块在类被加载到 JVM 时执行,并且只执行一次,通常用于静态变量的初始化。
什么时候用?
当初始化一个静态变量需要执行复杂的逻辑(如读取配置文件、建立数据库连接池等)时,不能直接在声明时赋值,这时就需要静态代码块。
-
例子:加载驱动程序、初始化日志系统。
-
代码示例:
public class DatabaseConfig { private static String dbUrl; private static String username; private static String password; // 静态代码块,在类加载时执行 static { try { // 模拟从配置文件中读取数据 Properties props = new Properties(); props.load(DatabaseConfig.class.getResourceAsStream("/db.properties")); dbUrl = props.getProperty("db.url"); username = props.getProperty("db.username"); password = props.getProperty("db.password"); System.out.println("Database configuration loaded successfully."); } catch (IOException e) { System.err.println("Failed to load database configuration."); e.printStackTrace(); } } // 私有构造函数,防止外部实例化 private DatabaseConfig() {} public static String getDbUrl() { return dbUrl; } }
静态内部类
静态内部类与外部类关联,但不持有外部类的引用,它更像是一个普通的嵌套类。
什么时候用?
当一个内部类不需要访问外部类的实例成员(非静态成员)时,就应该使用静态内部类,这可以避免内存泄漏和不必要的依赖。
-
例子:
HashMap中的Node类就是一个静态内部类。 -
代码示例:
public class OuterClass { private int outerInstanceVar = 10; // 静态内部类 static class StaticInnerClass { // 可以访问外部类的静态成员 private static int outerStaticVar = 20; public void display() { // System.out.println(outerInstanceVar); // 编译错误!不能访问非静态成员 System.out.println("Outer static var: " + outerStaticVar); } } }
总结表格
| 成员类型 | 何时使用 | 关键特点 | 访问方式 |
|---|---|---|---|
| 静态变量 | 数据需要被所有实例共享,或定义为常量 (public static final) |
属于类,不属于对象,所有实例共享一份,修改会影响所有实例。 | ClassName.variableName |
| 静态方法 | 方法逻辑不依赖于任何对象的状态(不访问实例成员),或作为工具方法、工厂方法。 | 不需要创建对象即可调用,没有 this 引用。 |
ClassName.methodName() |
| 静态代码块 | 在类加载时执行一次,用于初始化静态变量。 | 只在类首次加载到 JVM 时执行。 | 自动执行 |
| 静态内部类 | 内部类不需要访问外部类的实例成员。 | 不持有外部类的 this 引用,可以独立存在。 |
OuterClass.InnerClass |
一个重要的反面教材:单例模式
很多人会误用 static 来实现单例模式,虽然它能工作,但这不是最佳实践。
-
不好的方式(饿汉式):
public class Singleton { // 静态变量,在类加载时就初始化了 private static final Singleton INSTANCE = new Singleton(); // 私有构造函数,防止外部 new private Singleton() {} public static Singleton getInstance() { return INSTANCE; } }- 缺点:如果这个单例对象很大,而且程序可能一直用不到它,那么它会在类加载时就占用内存,造成浪费。
-
更好的方式(懒汉式,双重检查锁): 这种方式延迟了对象的创建,只有在第一次调用
getInstance()时才创建,更符合现代应用的需求。
核心思想
记住这个核心原则:static 将成员从“对象级别的”提升到了“类级别的”,当你问自己“这个东西是属于每一个具体的对象,还是属于这个概念性的‘类’?”时,答案就能帮你决定是否使用 static。
