static在Java中到底怎么用?5分钟搞懂变量、方法、代码块与内部类

一、static的基本概念

static是Java中的一个修饰符,可用于修饰类的成员变量方法代码块嵌套类。被static修饰的成员属于类本身,而不是类的某个实例对象。这意味着静态成员在类加载时就会被初始化,并且所有该类的实例共享同一份静态成员。

二、static变量

1、静态变量(类变量)

  • 静态变量属于类,而不是某个特定的对象实例。所有实例共享同一个静态变量
java 复制代码
public class Counter {
    // 静态变量
    private static int count = 0;
    
    public Counter() {
        count++; // 每次创建实例时count增加
    }
    
    public static int getCount() {
        return count;
    }
}

// 使用示例
public class Main {
    public static void main(String[] args) {
        Counter c1 = new Counter();
        Counter c2 = new Counter();
        Counter c3 = new Counter();
        
        System.out.println("创建的实例数量: " + Counter.getCount()); // 输出: 3
    }
}

2、静态变量的初始化

  • JVM加载类时,会执行类初始化(Class Initialization),此时静态变量会被显式赋值(若未显式赋值则使用默认值)
    • 默认值规则:数值类型为0,布尔类型为false,引用类型为null
    • 显式赋值:在定义中直接赋值,或在静态代码块中赋值(二者执行顺序按代码编写顺序)
java 复制代码
public class InitializationExample {
    // 声明时初始化
    static int a = 10;

    // 静态代码块初始化
    static int b;
    static {
        b = 20;
    }

    // 默认赋值
    static int c;

    public static void main(String[] args) {
        System.out.println("a=" + a);  // 输出10
        System.out.println("b=" + b);  // 输出20
        System.out.println("c=" + c);  // 输出0
    }
}

3、静态变量 vs 实例变量

特性 静态变量 实例变量
内存分配 类加载时分配,在方法区 对象创建时分配,在堆内存
生命周期 与类相同 与对象实例相同
访问方式 类名.变量名 或 对象.变量名 只能通过对象.变量名
共享性 所有实例共享 每个实例独有

4、静态变量的线程安全性

  • 静态变量被所有实例共享,多线程环境下修改可能导致数据不一致(需通过synchronizedLockAtomicXXX类保证原子性)
java 复制代码
public class ThreadSafeCounter {
    private static int count = 0;
    private static final Object lock = new Object();
    // 线程不安全的递增
    public static void unsafeIncrement() {
        count++;
    }
    // 线程安全的递增
    public static void safeIncrement() {
        synchronized (lock) {
            count++;
        }
    }
    
    // 使用AtomicInteger实现线程安全
    private static AtomicInteger atomicCount = new AtomicInteger(0);
    public static void atomicIncrement() {
        atomicCount.incrementAndGet();
    }
}

三、static方法

1、静态方法

  • 静态方法属于类,而不是类的实例
  • 可以通过类名直接调用,无需创建实例
java 复制代码
public class MathUtils {
    // 静态变量
    private static final double PI = 3.14159;
    
    // 静态方法
    public static double calculateCircleArea(double radius) {
        return PI * radius * radius;
    }
    
    // 另一个静态方法,可以调用其他静态方法
    public static double calculateCircleCircumference(double radius) {
        return 2 * PI * radius;
    }
}

// 使用示例
public class Main {
    public static void main(String[] args) {
        double area = MathUtils.calculateCircleArea(5.0);
        double circumference = MathUtils.calculateCircleCircumference(5.0);
        
        System.out.println("面积: " + area);
        System.out.println("周长: " + circumference);
    }
}

2、静态方法的使用限制

  1. 不能使用this和super关键字:因为静态方法不依赖于任何实例
  2. 不能直接访问实例变量和实例方法:只能直接访问静态成员
  3. 不能被重写:静态方法可以被子类"隐藏",但不能被重写

代码示例:重写 vs. 隐藏

  • 隐藏: 针对静态方法、静态变量和实例变量
  • 发生在编译时,基于引用的声明类型(等号左边的类型)来决定调用哪个方法或字段
  • 这就是静态绑定或早期绑定
java 复制代码
class Animal {
    // 这是一个静态方法,将被"隐藏"
    public static void staticMethod() {
        System.out.println("Animal: Static Method");
    }
    
    // 这是一个实例方法,将被"重写"
    public void instanceMethod() {
        System.out.println("Animal: Instance Method");
    }
}

class Dog extends Animal {
    // 隐藏父类的静态方法 (使用 @Override 注解会报错,因为它不是重写)
    public static void staticMethod() {
        System.out.println("Dog: Static Method");
    }
    
    // 重写父类的实例方法 (使用 @Override 注解是正确且推荐的)
    @Override
    public void instanceMethod() {
        System.out.println("Dog: Instance Method");
    }
}

public class Test {
    public static void main(String[] args) {
        // 关键:声明类型为 Animal,实际对象为 Dog
        Animal myAnimal = new Dog();
        
        // 调用静态方法 - 隐藏 (看引用类型 Animal)
        myAnimal.staticMethod(); // 输出: Animal: Static Method
        
        // 调用实例方法 - 重写 (看对象类型 Dog)
        myAnimal.instanceMethod(); // 输出: Dog: Instance Method
        
        // 通过类名调用静态方法(正确方式)
        Animal.staticMethod(); // 输出: Animal: Static Method
        Dog.staticMethod();    // 输出: Dog: Static Method
    }
}

四、static代码块

  • 静态代码块在类加载时执行,且只执行一次,常用于初始化静态变量或执行只需进行一次的操作
java 复制代码
public class DatabaseConnection {
    private static Connection connection;
    
    // 静态代码块
    static {
        try {
            // 初始化数据库连接
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "password");
            System.out.println("数据库连接已建立");
        } catch (SQLException e) {
            System.out.println("数据库连接失败: " + e.getMessage());
        }
    }
    
    public static Connection getConnection() {
        return connection;
    }
}

五、static嵌套类

1、静态内部类

  • static嵌套类(静态内部类)是声明为static的内部类
    • 不依赖于外部类的实例
    • 可以访问外部类的static成员
    • 不能直接访问外部类的非static成员
    • 可以包含static和非static成员
java 复制代码
public class OuterClass {
    private static String staticField = "静态字段";
    private String instanceField = "实例字段";
    
    // 静态嵌套类
    public static class StaticNestedClass {
        public void print() {
            System.out.println(staticField); // 可以访问外部类的静态成员
            // System.out.println(instanceField); // 错误:不能访问外部类的实例成员
        }
    }
    
    // 内部类(非静态)
    public class InnerClass {
        public void print() {
            System.out.println(staticField); // 可以访问静态成员
            System.out.println(instanceField); // 也可以访问实例成员
        }
    }
}

// 使用示例
public class Main {
    public static void main(String[] args) {
        // 依赖外部类,需要先创建外部类,再创建内部类实例
        OuterClass outer = new OuterClass();
        OuterClass.InnerClass inner = outer.new InnerClass();
        inner.print();


        // 不需要外部类实例,直接创建静态嵌套类实例
        OuterClass.StaticNestedClass nested = new OuterClass.StaticNestedClass();
        nested.print();
    }
}

2、常见用途(Builder模式)

示例:自定义一个带有 Builder 模式的类

java 复制代码
public class User {
    // 成员变量
    private String name;
    private Integer age;
    private String email;
    
    // 私有构造方法,只能通过 Builder 构建
    private User(String name, Integer age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }

    public static UserBuilder builder() {
        return new UserBuilder();
    }
    
    // 静态内部类 Builder
    public static class UserBuilder {
        private String name;
        private Integer age;
        private String email;

        private UserBuilder() {
        }
        
        // 设置参数的方法,返回 Builder 本身,支持链式调用
        public UserBuilder name(final String name) {
            this.name = name;
            return this;
        }
        public UserBuilder age(final Integer age) {
            this.age = age;
            return this;
        }
        public UserBuilder email(final String email) {
            this.email = email;
            return this;
        }
        
        // 构建 User 对象
        public User build() {
            return new User(this.name, this.age, this.email);
        }
    }
}

使用方式:

java 复制代码
public class Main {
    public static void main(String[] args) {
        User user = User.builder()
                .name("xuchang")
                .age(18)
                .email("xuchang@gmail.com")
                .build();

        System.out.println(user);
    }
}

六、静态导入

  • Java 5引入了static导入功能,允许直接使用其他类中定义的static成员而不需要类名前缀
java 复制代码
// 导入Math类的所有static成员
import static java.lang.Math.*;

public class StaticImportExample {
    public static void main(String[] args) {
        // 直接使用Math类的static方法,无需Math.前缀
        double result = sin(PI / 2) + cos(0);
        System.out.println("计算结果: " + result);  // 输出2.0
    }
}
  • 也可以只导入特定的static成员
java 复制代码
// 只导入Math类的sqrt和PI
import static java.lang.Math.sqrt;
import static java.lang.Math.PI;

public class SelectiveStaticImport {
    public static void main(String[] args) {
        double area = PI * sqrt(2) * sqrt(2);  // 计算半径为√2的圆的面积
        System.out.println("面积: " + area);  // 输出约6.283185307179586
    }
}
相关推荐
他日若遂凌云志8 小时前
深度剖析 Fantasy 框架的多协议网络通信(一):TCP 可靠传输与连接管控
后端
开心就好20258 小时前
Fiddler 抓不到包怎么办?从排查到替代方案的全流程指南
后端
南北是北北8 小时前
TextureView中的surfaceTexture的作用
前端·面试
知其然亦知其所以然8 小时前
面试官最爱问的坑:MySQL 中 FLOAT 和 DOUBLE 你真懂吗?
后端·mysql·面试
雨中散步撒哈拉8 小时前
12、做中学 | 初一上期 Golang函数 包 异常
开发语言·后端·golang
AAA修煤气灶刘哥8 小时前
从全表扫描到 0.1 秒查询:数据库索引吃透这篇,面试不慌
java·数据库·后端
南北是北北9 小时前
BufferQueue的环形队列是什么设计的
前端·面试
南北是北北9 小时前
Surface中的BufferQueue
前端·面试
笃行3509 小时前
打造智能写作工作流:n8n + 蓝耘MaaS平台完整实战指南
后端