面向对象 09:static 关键字的使用——静态变量 / 静态方法 / 静态代码块 / 静态导入包 / 静态内部类

一、前言

记录时间 [2024-05-17]

系列文章简摘:
面向对象 04:三大特性之——封装,封装的含义和作用,以及在 Java 中的使用方式,附完整的测试案例代码
面向对象 05:三大特性之——继承,继承在 Java 中的相关使用,区分关键字 super 和 this,方法重写的注意点
面向对象 07:抽象类相关知识,抽象类的基本概念,使用方式,以及一些注意事项
面向对象 08:接口的相关知识,接口的基本特性、作用、使用方法等,以及普通类 / 抽象类 / 接口三者的区分

更多 Java 相关文章,请参考专栏哦。

本文是对 Java 中的关键字——static 的相关补充,主要包括静态变量、静态方法、静态代码块、静态导入包,以及静态内部类。


在 Java 中,static 关键字是一个非常重要的概念,它主要用于内存管理和访问权限的控制。下面详细介绍 static 关键字的几个关键应用场景和含义。


二、静态变量

静态变量(Static Variables)是 Java 中的一种特殊类型的变量。

1. 相关概念

特点

  • 类级别:静态变量,也称为类变量,使用 static 关键字进行修饰,属于类级别,而不是实例级别。无论创建类的多少个实例,所有实例共享同一个静态变量的副本,修改一个实例中的静态变量会影响其他实例。
  • 存储位置:在 Java 8 之后,静态变量存储在 Java 的永久代或元空间,而不是堆或栈中。这意味着静态变量即使在没有类实例的情况下也会存在,直到类被卸载。
  • 生命周期:静态变量的生命周期与类相同,从类加载到类被卸载期间一直存在。相比之下,实例变量的生命周期与对象实例相同,存在于从对象创建到对象被垃圾回收期间。
  • 访问方式:静态变量可以通过类名直接访问,不需要创建类的实例。例如,如果有一个类 Example,它有一个静态变量staticVar,可以通过 Example.staticVar 来访问它。

使用场景

  • 常量:静态变量常用于定义类的常量,如配置参数、数学常数等,这样可以在不创建对象的情况下访问这些值。
  • 计数器:如果需要追踪类的实例数量或其他与类相关的统计信息,可以使用静态变量作为计数器。
  • 单例模式:在实现单例设计模式时,静态变量常用来持有该类的唯一实例,确保全局访问点。
  • 全局设置:当需要为应用程序提供全局设置或配置时,静态变量可以作为一个方便的存储位置。

注意事项

静态变量初始化

静态变量的初始化发生在类加载阶段,且只会初始化一次。静态变量的初始化顺序遵循它们在代码中出现的顺序。

线程安全

由于静态变量是共享资源,当多个线程同时访问并修改静态变量时,需要注意同步问题,以避免数据不一致。


2. 案例分析

静态变量 / 非静态变量

静态变量和非静态变量(也称为实例变量)在 Java 中有着本质的区别,以下通过一个具体例子来说明这两种变量的不同之处。

假设我们有一个员工信息管理类 Employee,它包含员工的基本信息。

public class Employee {
    // 非静态变量(实例变量)
   	public int num;

    // 静态变量(类变量):所有员工共享的公司名称
    public static String companyName = "TechCorp";

}

通过测试类进行测试:

  • 其中,num 属性是非静态的,通过对象调用;
  • companyName 属性是静态的,通过类名调用。
public static void main(String[] args) {
    // 创建员工实例
    Employee emp1 = new Employee();

    // 调用静态变量
    // 修改公司名称,这是一个静态变量的修改,影响所有员工实例
    Employee.companyName = "NewTechCorp";

    // 调用非静态变量
    emp1.num = 100;

}

计数器

假设我们想跟踪一个类的实例被创建了多少次,可以使用静态变量作为计数器。

在这个例子中,instanceCount 是一个静态变量,每当创建一个新的 CounterClass 实例时,它的值就会增加。我们不需要实例化对象就能访问这个计数值,因为它通过类名直接访问。

class CounterClass {
    private static int instanceCount = 0;

    public CounterClass() {
        instanceCount++;
    }

    public static int getInstanceCount() {
        return instanceCount;
    }
}

// 使用示例
public class CounterTest {
    public static void main(String[] args) {
        CounterClass obj1 = new CounterClass();
        CounterClass obj2 = new CounterClass();
        System.out.println("Number of instances created: " + CounterClass.getInstanceCount()); // 输出:2
    }
}

3. 一些区别

静态变量和非静态变量主要存在以下区别。

存储位置与共享性

  • 静态变量:静态变量存储在方法区的静态区,所有类的实例共享这一份内存空间,因此当修改静态变量时,所有实例都会看到这个变化。
  • 非静态变量:非静态变量是每个实例独有的,存储在堆内存中,每个对象有自己独立的这两项数据。

生命周期

  • 静态变量:随着类的加载而创建,随着类的卸载而销毁,与任何对象实例的生命周期无关。
  • 非静态变量:随着对象的创建而存在,随着对象的销毁而消失,其生命周期与对象实例绑定。

访问方式

  • 静态变量:可以通过类名直接访问,无需创建类的实例,如 Employee.companyName
  • 非静态变量:必须通过类的实例来访问,如 emp1.num

作用域

  • 静态变量:属于类,作用于整个类及所有对象。
  • 非静态变量:属于对象,作用于对象实例内部。

三、静态方法

静态方法(Static Methods)是面向对象编程中的一种方法类型,使用 static 关键字进行修饰。

1. 相关概念

特点

  • 类级别而非对象级别:静态方法属于类本身,而不属于类的任何特定实例。不需要创建类的对象就可以调用静态方法。
  • 访问方式:静态方法通过类名直接调用,如 ClassName.staticMethodName(),而不是通过对象实例。
  • 内存中单一副本:静态方法在内存中只有一份实例,所有对该方法的调用共享这同一份代码。
  • 不能直接访问非静态成员:静态方法内部不能直接访问非静态属性(实例变量)或非静态方法,因为这些属于特定对象实例,而静态方法不依赖于任何实例。
  • 常用于工具方法或通用功能:静态方法通常用于执行不依赖于对象状态的操作,如数学运算、字符串处理或者与类状态有关的操作。

使用场景

静态方法主要适用于那些不依赖于对象状态、需要全局访问或执行逻辑相对独立的情况。

  • 工具类或辅助函数:静态方法非常适合用于创建工具类,其中包含不依赖于类实例的实用方法。例如,字符串处理、数学计算、日期格式化等。这类方法可以直接通过类名调用,无需实例化对象。
  • 常量或计算:当有一些计算逻辑或常量与类的实例无关,且不依赖于类的状态时,可以将其定义为静态方法或静态变量。例如,定义一个类来表示圆周率 PI,其中 PI 就是一个静态常量,而基于此计算圆的面积可以用静态方法实现。
  • 单例模式:在实现单例设计模式时,通常会使用一个静态方法来获取类的唯一实例。这样做的好处是可以保证系统中该类只有一个对象,并且可以全局访问。
  • 通用功能封装:如果有些功能或操作在多个类中通用,可以考虑将这些功能封装到一个类的静态方法中,便于复用。例如,日志记录、缓存管理等。
  • 系统初始化与配置:在程序启动或系统初始化时,可能需要执行一些一次性或配置性的操作,这些操作适合定义为静态方法,因为它们通常与任何特定对象实例无关。
  • 工厂方法:静态工厂方法是一种设计模式,用于代替构造函数来创建对象。它可以提供更多的灵活性,如返回子类实例、处理复杂对象创建逻辑等,而无需直接暴露构造函数。
  • 入口点方法:在某些框架或应用中,静态方法作为程序的入口点,如 Java 的 main 方法,它是程序开始执行的地方,不依赖于任何特定对象实例。

2. 案例分析

静态方法 / 非静态方法

静态方法与非静态方法(实例方法)在 Java 中有着明显的区别,下面通过一个具体的例子来详细说明这两种方法的不同之处。

假设我们有一个图书管理类 Book,它包含图书相关信息。

  • 静态方法:获取图书馆名称 getLibraryName()
  • 非静态方法:获取书籍详情 getBookDetails()
public class Book {
    // 非静态变量(实例变量):每本书都有独立的标题和作者
    private String title;
    private String author;

    // 静态变量(类变量):所有书籍共享的图书馆名称
    public static String libraryName = "City Central Library";

    // 构造方法
    public Book(String title, String author) {
        this.title = title;
        this.author = author;
    }

    // 非静态方法:获取书籍详情
    public String getBookDetails() {
        return "Title: " + title + ", Author: " + author;
    }

    // 静态方法:获取图书馆名称
    public static String getLibraryName() {
        return libraryName;
    }
}

通过测试类进行测试:

  • 访问静态方法:通过类名
  • 访问非静态方法:通过实例对象
public static void main(String[] args) {
    // 创建两本书的实例
    Book book1 = new Book("To Kill a Mockingbird", "Harper Lee");
    Book book2 = new Book("1984", "George Orwell");

    // 访问非静态方法
    System.out.println(book1.getBookDetails()); // 输出:Title: To Kill a Mockingbird, Author: Harper Lee
    System.out.println(book2.getBookDetails()); // 输出:Title: 1984, Author: George Orwell

    // 访问静态方法
    System.out.println(Book.getLibraryName()); // 输出:City Central Library

    // 修改静态变量
    Book.libraryName = "New Library";
    System.out.println(book1.getLibraryName()); // 输出:New Library
    System.out.println(book2.getLibraryName()); // 输出:New Library
}

计算圆的面积

假设我们要创建一个 Circle 类来计算圆的面积,其中 π 是一个常数,我们可以将其作为静态字段,而计算面积的方法可以设计为静态方法,因为计算过程并不依赖于圆的具体实例。

public class Circle {
    // 静态变量:π 的值,所有圆共用
    public static final double PI = 3.14159;

    // 静态方法:计算圆的面积
    public static double calculateArea(double radius) {
        // 面积公式 A = π * r^2
        return PI * Math.pow(radius, 2);
    }
}

// 主函数中调用静态方法
class Main {
    public static void main(String[] args) {

        // 给定圆的半径
        double radius = 5.0;
        double area = Circle.calculateArea(radius);
        System.out.println("The area of the circle with radius " + radius + " is: " + area);

    }
}

在这个例子中:

  • calculateArea 是一个静态方法,它接受圆的半径作为参数并返回圆的面积。
  • 调用这个方法时,我们不需要创建 Circle 类的实例,直接通过 Circle.calculateArea(radius) 即可完成计算。
  • 同时,PI 作为圆周率的值,被声明为一个静态常量,体现了静态变量的使用场景。

3. 一些区别

静态方法与非静态方法主要存在以下区别。

调用方式

  • 非静态方法需要通过类的实例来调用,如 book1.getBookDetails()
  • 静态方法可以直接通过类名调用,无需创建类的实例,如 Book.getLibraryName()

访问权限

  • 非静态方法可以访问实例变量和静态变量。
  • 静态方法只能访问静态变量,不能直接访问实例变量,因为静态方法不属于任何特定实例。

生命周期与存储

  • 非静态变量和非静态方法与对象实例绑定,每个对象实例有自己的副本,随对象创建而生,随对象销毁而亡。
  • 静态变量和静态方法属于类,由类加载器加载到内存,所有对象共享,仅在类首次加载时初始化,直到程序结束或类被卸载才释放。

内存分配

  • 非静态成员占用的空间随对象实例的数量成比例增长。
  • 静态成员在整个类的生命周期内只占用一份固定的内存空间。

四、静态代码块

1. 代码块 / 构造方法

静态代码块

静态代码块在 Java 中是一种特殊的代码结构,用于在类加载时执行初始化操作。

相关特点:

  • 执行时机:静态代码块在类被加载到内存时执行,且只执行一次。静态代码块中的代码会在任何对象实例化之前运行,也不需要创建类的实例。
  • 初始化静态资源:静态代码块常用于初始化类的静态成员变量,比如设置静态变量的初始值,或者执行与类相关的、只需一次的初始化配置。
  • 执行顺序:如果一个类中有多个静态代码块,它们将按照在类中出现的顺序依次执行。这对于需要按特定顺序进行初始化的场景非常有用。且静态代码块的执行优先于构造函数和非静态初始化块。

应用场景:

  • 资源初始化:比如初始化数据库连接池、读取配置文件、设置日志系统等,这些操作通常只需要在应用启动时进行一次。
  • 静态变量赋值:给类的静态成员变量赋予复杂的初始值,特别是那些需要执行一定逻辑才能确定的值。
  • 执行一次性任务:比如注册驱动、检查环境配置等。

代码应用:

在这个例子中,当 MyClass 被加载时,静态代码块会立即执行,设置 appName 并打印一条消息,同时调用 readVersionFromConfig 方法来初始化 version。之后,无论创建多少个 MyClass 的实例,静态代码块都不会再次执行。

public class MyClass {
    
    // 静态变量
    public static String appName;
    public static int version;

    // 静态代码块
    static {
        appName = "My Application";
        System.out.println("Static block executed.");
        
        // 假设这里还有其他复杂的初始化逻辑,比如从配置文件读取版本号
        version = readVersionFromConfig();
    }

    // 静态方法读取版本号
    private static int readVersionFromConfig() {
        
        // 这里模拟从配置文件读取版本号的逻辑
        return 1;
    }

    public static void main(String[] args) {
        System.out.println("Application Name: " + MyClass.appName);
        System.out.println("Version: " + MyClass.version);
    }
}

匿名代码块

匿名代码块(也称为实例代码块或初始化块)是在 Java 中位于类成员变量声明之后、构造器之前的花括号包围的代码块。它不是静态的,意味着每次创建类的新实例时都会执行一次,常用于初始化非静态成员变量。

匿名代码块相比于直接在构造器中初始化变量,提供了更灵活的初始化逻辑和优先级控制。

相关特点:

  • 执行时机:每当创建类的一个新对象时,匿名代码块会在对应的构造器之前执行。如果有多个匿名代码块,按照它们在类中出现的顺序执行。
  • 初始化顺序:匿名代码块的执行顺序优于构造器中的初始化代码,但晚于静态代码块。可以使用匿名代码块来设定实例变量的初始状态,即使这些状态依赖于构造器参数。
  • 灵活性:对于需要在不同构造器中执行相同或相似初始化逻辑,但逻辑较为复杂或不适合直接放入构造器时,匿名代码块提供了一个清晰的组织方式。
  • 实例相关:匿名代码块操作的是非静态成员,因此它与特定的对象实例绑定,允许根据实例的具体情况执行不同的初始化操作。

代码应用:

在这个例子中,当创建 Person 类的一个实例时,首先执行匿名代码块,设置 greeting 变量并打印消息。

public class Person {
    String name;
    int age;
    String greeting;

    // 匿名代码块
    {
        greeting = "Hello, ";
        System.out.println("匿名代码块执行了!");
    }

    public static void main(String[] args) {
        Person person = new Person();
    }
}

构造方法

构造方法(Constructor)主要用于初始化新创建的对象。构造方法的名字必须与它所在的类名完全相同,并且没有返回类型,甚至 void 也不需要。当创建一个类的新实例时,构造方法会被自动调用。

每当使用 new 关键字创建类的实例时,相应的构造方法会被自动执行。

例如,下面的例子展示了如何定义默认构造方法和带有参数的构造方法,以及它们如何在创建对象时被调用

public class Student {
    String name;
    int age;
    String major;

    // 默认构造方法
    public Student() {
        System.out.println("默认构造方法被调用");
    }

    // 参数化构造方法
    public Student(String name, int age, String major) {
        this.name = name;
        this.age = age;
        this.major = major;
        System.out.println("参数化构造方法被调用");
    }

    public static void main(String[] args) {
        // 使用默认构造方法创建对象
        Student student1 = new Student();

        // 使用参数化构造方法创建对象
        Student student2 = new Student("Alice", 20, "Computer Science");
    }
}

2. 三者的执行顺序

在实例化对象时,程序会按顺序执行:

  1. 静态代码块(只执行一次)
  2. 匿名代码块
  3. 构造方法

例如,我们在 User 类中同时编写静态代码块、匿名代码块,以及构造方法。

public class User {
    /*
        实例化对象时,执行顺序
        1. 静态代码块
        2. 匿名代码块
        3. 构造方法
        
        其中:静态代码块只执行一次
     */

    // 第一个执行
    static {
        System.out.println("静态代码块");
    }

    // 第二个执行
    {
        System.out.println("匿名代码块");
    }

    // 第三个执行
    public User() {
        System.out.println("构造方法");
    }

}

测试它们的执行顺序:

public static void main(String[] args) {
    System.out.println("User 类第一次实例化");
    User user1 = new User();

    System.out.println("User 类第二次实例化");
    User user2 = new User();

}

得到结果:

静态代码块			# 静态代码块最先执行,且只在第一次实例化时执行
User 类第一次实例化
匿名代码块
构造方法
User 类第二次实例化
匿名代码块
构造方法

五、静态导入包

静态导入包(Static Import)是 Java 语言中的一个特性,它允许直接访问另一个类中的静态成员(包括静态方法和静态变量),而无需显式地指定所属类名。

通过此,我们可以像使用本地方法一样直接调用静态方法,或者直接使用静态变量,而不需要类名作为前缀。

1. 基本语法

静态导入的语法是在 import 语句前加上关键字 static,有两种形式的静态导入。

导入单个静态成员

// 导入单个静态成员
import static 包名.类名.静态成员;

导入所有静态成员

// 导入所有静态成员
import static 包名.类名.*;

2. 使用示例

假设要频繁使用 java.lang.Math 类中的静态方法,如 Math.PIMath.sin(),一般情况下,我们会这样调用:

double result = Math.sin(Math.PI / 2);

使用静态导入后,可以直接这样写:

import static java.lang.Math.PI;
import static java.lang.Math.sin;

// ...

double result = sin(PI / 2);

或者,如果导入所有静态方法:

import static java.lang.Math.*;

// ...

double result = sin(PI / 2);

3. 注意事项

  • 静态导入虽然可以简化代码,提高可读性,但过度使用可能导致命名冲突和代码的可读性下降,特别是当导入了很多同名静态成员时。
  • 选择性地导入所需的静态成员,保持代码清晰,减少潜在的命名冲突。
  • 静态导入应该谨慎使用,确保它们确实提高了代码的清晰度和简洁性,而不是仅仅为了省略类名前缀。

六、静态内部类

静态内部类(Static Nested Class)是 Java 中的一种特殊类型的内部类,它使用 static 关键字进行修饰。

  • 当一个内部类被声明为静态时,它被称为静态内部类或嵌套类。
  • 静态内部类不依赖于外部类的实例,可以直接通过外部类名访问,就像访问静态变量一样。
  • 静态内部类可以有静态成员,包括静态变量和静态方法。

先做简单了解,在下篇内部类中进行补充。


七、总结

本文是对 Java 中的关键字——static 的相关补充,主要包括静态变量、静态方法、静态代码块、静态导入包,以及静态内部类。


一些参考资料

狂神说 Java 零基础:https://www.bilibili.com/video/BV12J41137hu/
TIOBE 编程语言走势: https://www.tiobe.com/tiobe-index/
Typora 官网:https://www.typoraio.cn/
Oracle 官网:https://www.oracle.com/
Notepad++ 下载地址:https://notepad-plus.en.softonic.com/
IDEA 官网:https://www.jetbrains.com.cn/idea/
Java 开发手册:https://developer.aliyun.com/ebook/394
Java 8 帮助文档:https://docs.oracle.com/javase/8/docs/api/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值