63. Java 类和对象 - static 关键字

toc

63. Java 类和对象 - static 关键字

了解类成员:static 关键字

在 Java 中,static 关键字用于修饰字段和方法,使其成为类级别的成员,而非对象级别的成员。也就是说,被声明为 static 的成员与类本身绑定,无论创建多少个对象,它们都只有一份拷贝。这种特性使得 static 成为实现全局共享数据、工具方法以及常量的重要手段。


1. 类变量(static 字段)

1.1 与实例变量的区别

  • 实例变量
    • 每个对象都有独立的实例变量副本,存储在各自的内存中。
    • 修改某个对象的实例变量只影响该对象,不会对其他对象产生影响。
  • 类变量(static 字段)
    • 整个类中只有一份存储空间,所有对象共享同一份数据。
    • 不管创建多少个该类的实例,类变量都只有一个,并且可以通过类名直接访问。

1.2 用法示例

下面的示例展示了如何使用 static 字段记录所有 Bicycle 对象的创建数量,同时为每个对象分配独一无二的 ID:

java 复制代码
public class Bicycle {
    // 实例变量,每个对象各自拥有
    private int cadence;
    private int gear;
    private int speed;
    private int id;  

    // 类变量:所有 Bicycle 实例共享,记录创建的数量
    private static int numberOfBicycles = 0;

    public Bicycle(int startCadence, int startSpeed, int startGear) {
        this.cadence = startCadence;
        this.speed = startSpeed;
        this.gear = startGear;

        // 每创建一个新对象,计数加 1,并将计数赋值给该对象的 id
        id = ++numberOfBicycles;
    }
    
    public int getID() {
        return id;
    }
}
java 复制代码
public static void main(String[] args) {
    Bicycle bike1 = new Bicycle(3, 100, 50);
    System.out.println(bike1.id);
    Bicycle bike2 = new Bicycle(2, 80, 40);
    System.out.println(bike2.id);
}

在这个示例中:

  • numberOfBicyclesstatic 字段,记录了创建的 Bicycle 数量,全类仅有一份,所有实例共享该数据。
  • 每个 Bicycle 对象都有自己的实例变量 id,用来区分不同对象。

1.3 访问类变量

  • 推荐方式 :使用 类名.类变量名 访问,例如:Bicycle.numberOfBicycles。这种方式能清晰地表明该变量是属于类的。
  • 不推荐方式 :通过对象引用访问,如 myBicycle.numberOfBicycles。这种方式容易让人误解为该变量是对象的专属属性,而非全局共享的。
java 复制代码
System.out.println( Bicycle.numberOfBicycles);
System.out.println( bike1.numberOfBicycles);
System.out.println( bike2.numberOfBicycles);

2. 类方法(static 方法)

2.1 与实例方法的区别

  • 实例方法
    • 属于对象,必须先创建对象后才能调用。
    • 调用方式:对象名.方法名()
  • 类方法(static 方法)
    • 属于类本身,可以直接通过 类名.方法名() 调用。
    • 不依赖任何具体的对象实例。

2.2 用法示例

假设我们想获取当前创建的 Bicycle 数量,可以通过以下 static 方法实现:

java 复制代码
public class Bicycle {
    // ...(其他成员同上)

    // 类方法:无需对象即可调用,用于获取创建的 Bicycle 数量
    public static int getNumberOfBicycles() {
        return numberOfBicycles;
    }
}

调用时只需使用:

java 复制代码
int total = Bicycle.getNumberOfBicycles();

这样既直观又方便,无需先实例化对象。

2.3 注意事项:不能直接访问实例成员

由于 static 方法不属于任何具体对象,因此在方法体内无法直接使用实例变量或 this 关键字。例如:

java 复制代码
public static void foo() {
    // 编译错误:不能使用 this 或直接访问实例变量
    // System.out.println(this.cadence);  
}

如果需要访问实例数据,必须通过已有对象引用来访问:

java 复制代码
public static void printBicycleSpeed(Bicycle bicycle) {
    System.out.println("Speed: " + bicycle.speed);
}

3. 常量(static final)

在 Java 中,使用 static final 修饰的变量被称为常量,其值在程序运行过程中不能改变。这些常量通常在类加载时就被初始化,并且全类共享。

3.1 示例

java 复制代码
public class MathConstants {
    public static final double PI = 3.141592653589793;
    public static final int MAX_VALUE = 100;
}
  • final 表示常量一经赋值就不能再修改。
  • static 表示常量是全类共享的,无需创建对象即可访问。
  • 常量命名通常使用全大写字母,并用下划线 _ 分隔单词,例如 MAX_SPEEDDEFAULT_CAPACITY

3.2 编译时常量

当常量的值在编译时已知(如上述 PI),编译器可能会将代码中所有对该常量的引用直接替换为实际值,这样在性能上会更高效。但这也意味着若常量值修改,所有引用该常量的类都需要重新编译。


4. 使用 static 的注意事项与最佳实践

  1. 数据共享与全局状态
    • 使用 static 字段可以方便地实现数据共享,例如计数器或全局配置信息。
    • 过度依赖 static 变量可能导致代码间的耦合度增加,从而使单元测试和维护变得困难。
  2. 工具方法与辅助函数
    • 很多工具类(如 java.lang.Math)中的方法都是 static 的,因为这些方法不依赖对象状态,只提供通用功能。
  3. 线程安全
    • 在多线程环境下使用 static 字段时要注意同步问题,因为多个线程可能同时访问和修改共享数据。
  4. 避免误用
    • 切记不要滥用 static。在设计时应仔细考虑某个方法或字段是否确实不依赖于对象状态,只有在确定其为全局共享的情况下再使用 static

5. 综合示例

以下示例整合了类变量、类方法、实例变量及常量的用法,展示了如何设计一个既能统计对象数量,又能提供工具方法的类:

java 复制代码
public class Bicycle {
    // 实例变量,每个对象独有
    private int cadence;
    private int gear;
    private int speed;
    private int id;  

    // 类变量:所有 Bicycle 实例共享
    private static int numberOfBicycles = 0;
    
    // 类常量:不变的最大速度限制
    public static final int MAX_SPEED = 50;

    public Bicycle(int startCadence, int startSpeed, int startGear) {
        this.cadence = startCadence;
        this.speed = startSpeed;
        this.gear = startGear;
        // 对象计数自增
        id = ++numberOfBicycles;
    }
    
    public int getID() {
        return id;
    }
    
    // 类方法:返回已创建的 Bicycle 对象总数
    public static int getNumberOfBicycles() {
        return numberOfBicycles;
    }
    
    // 工具方法:判断给定速度是否超过最大速度限制
    public static boolean isSpeedLegal(int speed) {
        return speed <= MAX_SPEED;
    }
    
    // 实例方法:调整当前对象的速度
    public void changeSpeed(int delta) {
        int newSpeed = speed + delta;
        if (isSpeedLegal(newSpeed)) {
            speed = newSpeed;
        } else {
            System.out.println("超出允许的最大速度!");
        }
    }
}

在这个综合示例中:

  • 使用 static 字段统计创建的 Bicycle 数量。
  • 通过 static 方法获取对象数量或判断速度是否合法。
  • 使用 static final 常量 MAX_SPEED 限制最大速度。
  • 实例方法 changeSpeed 则根据对象当前状态进行操作,并利用工具方法进行判断。
相关推荐
高兴达2 分钟前
Spring boot入门工程
java·spring boot·后端
伍哥的传说17 分钟前
鸿蒙系统(HarmonyOS)应用开发之手势锁屏密码锁(PatternLock)
前端·华为·前端框架·harmonyos·鸿蒙
yugi98783818 分钟前
前端跨域问题解决Access to XMLHttpRequest at xxx from has been blocked by CORS policy
前端
浪裡遊30 分钟前
Sass详解:功能特性、常用方法与最佳实践
开发语言·前端·javascript·css·vue.js·rust·sass
旧曲重听11 小时前
最快实现的前端灰度方案
前端·程序人生·状态模式
默默coding的程序猿1 小时前
3.前端和后端参数不一致,后端接不到数据的解决方案
java·前端·spring·ssm·springboot·idea·springcloud
夏梦春蝉2 小时前
ES6从入门到精通:常用知识点
前端·javascript·es6
归于尽2 小时前
useEffect玩转React Hooks生命周期
前端·react.js
G等你下课2 小时前
React useEffect 详解与运用
前端·react.js
我想说一句2 小时前
当饼干遇上代码:一场HTTP与Cookie的奇幻漂流 🍪🌊
前端·javascript