static关键字

核心概念

static关键字用于定义类级别的成员和方法,它改变了变量和方法的生命周期和作用域。深入理解static的使用,是掌握Java面向对象编程的关键。


一、静态变量(类变量)

1.1 基本用法

public class StaticVariableDemo {
    // 类变量:所有实例共享
    public static int count = 0;

    // 实例变量:每个对象独立
    private int instanceCount = 0;

    public StaticVariableDemo() {
        count++;           // 所有实例共享
        instanceCount++;   // 每个实例独立
    }
}

// 使用
StaticVariableDemo obj1 = new StaticVariableDemo();
StaticVariableDemo obj2 = new StaticVariableDemo();

System.out.println(StaticVariableDemo.count);  // 2
System.out.println(obj1.count);               // 2

1.2 内存模型

public class StaticMemoryDemo {
    // 静态变量存储在方法区
    private static final int[] STATIC_ARRAY = new int[1000];

    // 实例变量存储在堆中
    private int[] instanceArray = new int[100];
}

二、静态方法

2.1 基本特征

public class StaticMethodDemo {
    // 静态方法:不依赖实例
    public static int add(int a, int b) {
        return a + b;
    }

    // 不能直接访问非静态成员
    public static void staticMethod() {
        // instanceMethod();  // ❌ 编译错误
        // name = "test";     // ❌ 编译错误
    }

    // 实例方法
    public void instanceMethod() {
        staticMethod();  // ✅ 可以调用静态方法
    }
}

// 使用
int result = StaticMethodDemo.add(1, 2);

2.2 工具类设计

// 典型的工具类设计
public final class MathUtils {
    private MathUtils() {
        // 防止实例化
        throw new AssertionError("Cannot be instantiated");
    }

    public static int max(int a, int b) {
        return a > b ? a : b;
    }

    public static double sqrt(double x) {
        return Math.sqrt(x);
    }
}

三、静态代码块

3.1 初始化顺序

public class StaticBlockDemo {
    // 静态变量
    private static int staticVar = initStaticVar();

    // 静态代码块
    static {
        System.out.println("静态代码块1");
        staticVar = 100;
    }

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

    // 实例代码块
    {
        System.out.println("实例代码块");
    }

    // 构造函数
    public StaticBlockDemo() {
        System.out.println("构造函数");
    }

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

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

// 初始化顺序
new StaticBlockDemo();

初始化顺序:

  1. 静态变量
  2. 静态代码块
  3. 实例变量
  4. 实例代码块
  5. 构造函数

3.2 单例模式示例

public class Singleton {
    // 私有静态实例
    private static Singleton instance;

    // 静态代码块初始化
    static {
        try {
            instance = new Singleton();
        } catch (Exception e) {
            throw new RuntimeException("Singleton init error");
        }
    }

    // 私有构造函数
    private Singleton() {}

    // 获取实例
    public static Singleton getInstance() {
        return instance;
    }
}

这段代码实现了 单例模式(Singleton Pattern),是用于确保类只有一个实例,并提供一个全局访问点的设计模式。

下面是对代码逐行的解释:

  1. private static Singleton instance;
  • 这行声明了一个 private 的静态变量 instance,用于存储单例的唯一实例。static 关键字确保该变量属于类,而不是类的实例,因此它在所有对象中共享。
  1. static { ... }
  • 这是一个 静态代码块(Static Block)。静态代码块在类加载时执行一次,通常用于初始化静态变量。
  • 在静态代码块中,实例 instance 被初始化为 new Singleton(),即创建了 Singleton 类的唯一实例。
  1. private Singleton() {}
  • 这是 私有构造函数,确保外部无法直接通过 new Singleton() 来创建 Singleton 类的对象。
  • 通过将构造函数设为 private,可以避免外部创建多个实例,从而保证只有一个实例。
  1. public static Singleton getInstance() { ... }
  • 这是 公共静态方法,用于返回唯一的实例。通过 getInstance() 方法,用户可以访问 instance,而不能直接访问 instance 变量。
  • 由于 instance 是静态变量,getInstance() 方法也是静态的。这样,用户无需创建 Singleton 对象,就可以直接通过 Singleton.getInstance() 获取实例。
  1. 错误处理 (try-catch):
  • 静态代码块中有一个 try-catch 语句,捕获实例化时可能发生的异常,并抛出一个 RuntimeException
  • 这种做法是为了在实例化过程中出现异常时,能够提供明确的错误信息。

总结:

  • 单例模式确保 Singleton 类在整个应用中只有一个实例。该实例是通过静态代码块在类加载时初始化的。
  • 通过私有构造函数,外部无法直接创建多个实例,确保了类的唯一性。
  • 通过公共静态方法 getInstance(),用户可以访问这个唯一实例。

这种实现方式是 懒汉式(Lazy Initialization),即只有在第一次调用 getInstance() 方法时才会创建实例。


四、内存泄漏风险

4.1 静态集合导致的内存泄漏

public class MemoryLeakDemo {
    // 静态集合可能导致内存泄漏
    private static final List staticList = new ArrayList<>();

    public void addToList(Object obj) {
        staticList.add(obj);
        // 对象永远不会被回收
    }
}

4.2 解决方案

public class SafeMemoryDemo {
    // 使用软引用或弱引用
    private static final List<WeakReference> safeList =
        new ArrayList<>();

    public void addToList(Object obj) {
        safeList.add(new WeakReference<>(obj));
    }
}

五、高频面试题

Q1: static方法能否访问非static方法?

答:

  • 不能直接访问
  • 需要先创建对象,通过对象调用

Q2: static代码块的执行时机?

答:

  • 类加载时执行
  • 仅执行一次
  • 早于实例初始化

Q3: 静态变量和实例变量有什么区别?

答:

  • 静态变量:类级别,所有实例共享
  • 实例变量:对象级别,每个实例独立

Q4: 为什么main方法是static的?

答:

  • 程序入口无需创建对象
  • JVM直接通过类名调用
  • 提高程序启动效率

Q5: 静态内部类和非静态内部类有什么区别?

答:

  • 静态内部类:不持有外部类引用
  • 非静态内部类:持有外部类引用
  • 静态内部类可以有静态成员

六、最佳实践

✅ 推荐做法

// 1. 工具类设计
public final class StringUtils {
    private StringUtils() {}

    public static boolean isEmpty(String str) {
        return str == null || str.trim().isEmpty();
    }
}

// 2. 单例模式
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

❌ 避免做法

// 1. 避免过度使用静态变量
public class BadDesign {
    private static Map<String, Object> globalCache;  // 不推荐
}

// 2. 滥用静态方法
public class MathHelper {
    public static void complexCalculation() {
        // 过于复杂的静态方法
    }
}

相关链接


记忆口诀

static修饰类级别,变量方法皆可栖
所有实例共享用,无需创建对象时
初始化有先后,静态块最先执
工具类单例伴,内存泄漏要当心

⬅️ final关键字 🏠 00-Java ➡️ super与this关键字