Java static keyword

Java static keyword

静态变量

静态变量简介

在类中创建变量的使用static关键字修饰,那么这个变量就是静态变量。

静态变量的特点:

  • 静态变量属于类,而不属于类实例对象。
  • 静态变量只在类加载的时候获取一次内存空间,这使得静态变量很节省空间;
  • 静态变量可以通过类名直接访问,也可以通过类实例对象访问。
  • 静态变量可以直接赋值,也可以在静态代码块中赋值。
  • 静态变量一旦赋值后,就可以在类的任意方法中访问。

使用静态静态变量的好处就是可以节省内存空间,假设某大学录取了一万名新生,那么在创建一万个 Student 对象的时候,
所有的字段(name、age 和 school)都会获取到一块内存。学生的姓名和年纪不尽相同,但都属于这个大学,
如果每创建一个对象,school 这个字段都要占用一块内存的话,就很浪费,因此,最好将 school 这个字段设置为 static,这样就只会占用一块内存,而不是一万块。

package com.basic.keywords.static_;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Student {
    private int age;
    private String name;
    private static String school = "加里敦大学";

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getSchool() {
        return this.school;
    }

    @Override
    public String toString() {
        return String.format("Student(age=%s,name=%s,school=%s)", this.age, this.name, this.school);
    }

    public static void main(String[] args) {
        Student s1 = new Student("Tom", 18);
        Student s2 = new Student("Jory", 19);
        System.out.println(String.format("%s\n%s", s1.toString(), s2.toString()));
    }
}

注意

  • lombook的@Getter、@Setter、@ToString、@Data等注解,会忽略静态变量,如果需要使用静态变量,需要单独编写get方法;

静态变量在jvm中的存储位置如下图所示:

s1和s2两个引用变量存放在栈中(stack),s1和s2指向的对象存放在堆中(heap),school变量存放在方法区(静态区)中(method area)。

s1和s2共享同一个school变量,即s1和s2的school变量指向同一个内存地址。

静态变量的特性

由于静态变量只会获取一次内存空间,所以任何对象对它的修改都会得到保留。

举例说明:
类中定义一个静态变量,然后创建三个对象,分别修改静态变量的值,然后打印静态变量的值。

package com.basic.keywords.static_;

public class StudentCounter {
    private static int count = 0;

    public StudentCounter() {
        count++;
        System.out.println(String.format("count=%s", count));
    }

    public int getCount() {
        return this.count;
    }

    public static void main(String[] args) {
        StudentCounter s1 = new StudentCounter();
        StudentCounter s2 = new StudentCounter();
        StudentCounter s3 = new StudentCounter();
    }

}

运行结果:

count=1
count=2
count=3

由于静态变量只会获取一次内存空间,所以任何对象对它的修改都会得到保留,所以每创建一个对象,count 的值就会加 1,所以最终的结果是 3。这就是静态变量和成员变量之间的差别。

另外,需要注意的是,由于静态变量属于一个类,所以不要通过对象引用来访问,而应该直接通过类名来访问,否则编译器会发出警告。

静态方法

如果方法上加了 static 关键字,那么它就是一个静态方法。
静态方法的特点:

  • 静态方法属于类,而不属于类实例对象。
  • 调用静态方法的时候不需要创建这个类的对象;
  • 静态方法只能访问静态变量和静态方法,不能访问成员变量和成员方法。

实例代码如下:

package com.basic.keywords.static_;

import lombok.*;

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class staticMethodStudent {
    private String name;
    private int age;
    private static String school = "加里敦大学";

    public static void changeSchool(String newSchool) {
        school = newSchool;
    }
    @Override
    public String toString() {
        return String.format("Student(age=%s,name=%s,school=%s)",this.age,this.name,this.school);
    }

    public static void main(String[] args) {
        staticMethodStudent s1 = new staticMethodStudent("Tom", 20);
        System.out.println(String.format("s1: %s",s1));
        staticMethodStudent.changeSchool("社会大学");
        staticMethodStudent s2 = new staticMethodStudent("Jory", 20);
        System.out.println(String.format("s2: %s",s2));
    }

}

运行结果:

s1: Student(age=20,name=Tom,school=加里敦大学)
s2: Student(age=20,name=Jory,school=社会大学)

从运行结果可以看出,s2 的 school 都是社会大学,这是因为静态方法 changeSchool 是属于类的,另外,需要注意的是,由于静态方法属于一个类,所以不要通过对象引用来访问,而应该直接通过类名来访问,否则编译器会发出警告。

另外,main 方法也是静态方法,所以可以直接通过类名来访问。如果 main 方法不是静态的,就意味着 Java 虚拟机在执行的时候需要先创建一个对象才能调用 main 方法,而 main 方法作为程序的入口,创建一个额外的对象显得非常多余。

静态代码块

用一个 static 关键字,外加一个大括号括起来的代码被称为静态代码块。

静态代码块是在类加载的时候执行的,并且只会执行一次。

静态代码块的作用是:

  • 初始化静态变量
  • 执行一些静态的初始化操作

实例代码如下:

package com.basic.keywords.static_;

public class staticBlock {
    static {
        System.out.println("static block");
    }
    public static void main(String[] args) {
        System.out.println("main 方法");
    }
}

运行结果:

static block
main 方法

从运行结果可以看出,静态代码块是在类加载的时候执行的,并且只会执行一次。

静态代码块通常用来初始化一些静态变量,它会优先于 main() 方法执行。

静态代码块适合在无法在声明静态变量时完成初始化操作,比如List、Map等。在实际的项目开发中,通常使用静态代码块来加载配置文件到内存当中。

静态代码块的执行顺序是:

  1. 静态代码块
  2. 构造代码块
  3. 构造函数

实例代码如下:

package com.basic.keywords.static_;

import java.util.ArrayList;
import java.util.List;

public class staticBlock {
    public static List<String> writes = new ArrayList<>();

    public staticBlock(){}
    public List<String> getWrites(){
        return this.writes;
    }

    static {
        writes.add("Tom");
        writes.add("Jory");
        writes.add("Suxin");
        System.out.println("第一块");
    }

    static {
        writes.add("Tom2");
        writes.add("Jory2");
        writes.add("Suxin2");
        System.out.println("第二块");
    }
    public static void main(String[] args) {
        staticBlock s1 = new staticBlock();
        System.out.println(s1.getWrites());
    }
}

执行结果:

第一块
第二块
构造函数
[Tom, Jory, Suxin, Tom2, Jory2, Suxin2]

静态内部类

除了以上,static 还有一个不太常用的功能——静态内部类。静态内部类是指在一个类中定义一个静态的内部类,这个内部类可以直接访问外部类的静态成员,但是不能访问外部类的非静态成员。

静态内部类的特点:

  • 静态内部类不能访问外部类的所有成员变量;
  • 静态内部类可以访问外部类的所有静态变量,包括私有静态变量。
  • 外部类不能声明为 static。
public class Singleton {
    private Singleton() {}

    private static class SingletonHolder {
        public static final Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

第一次加载 Singleton 类时并不会初始化 instance,只有第一次调用 getInstance() 方法时 Java 虚拟机才开始加载 SingletonHolder 并初始化 instance,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。

静态内部类练习案例,访问静态内部类的静态变量不会触发构造函数的执行。

public class staticClass {
    private staticClass(){
        System.out.println("staticClass constructor");
    }

    private static staticClass getInstance(){
        return staticClassInner.instance;
    }
    public static class staticClassInner{

        public static final staticClass instance = new staticClass();
        public staticClassInner(){
            System.out.println("staticClassInner constructor");
        }
    }
    public static void main(String[] args) {
        staticClass staticClass = new staticClass();
        staticClass.getInstance();
    }
}

运行结果:

staticClass constructor
staticClass constructor

从运行结果可以看出,静态内部类的静态变量不会触发构造函数的执行。

getInstance()方法返回的是staticClassInner.instance(一个static final字段)。

staticClassInner.instance是一个静态字段,它的值是在类加载时初始化的。 因此,staticClassInner.instance的值是在类加载时初始化的,而不是在getInstance()方法被调用时初始化的。

根据Java类加载的规则,静态内部类是一个独立的类,它会在第一次被主动使用时加载。主动使用包括:

  • 创建类的实例
  • 访问类的静态变量(非final)或静态方法

注意:在Java中,如果一个静态变量是final的,并且它的值是编译时常量,那么它在编译时就会被内联,不会导致类的初始化。但是,如果静态变量是final的,但它的值不是编译时常量(比如通过new创建对象),那么访问这个静态变量会导致类的初始化。

在这个例子中,public static final staticClass instance = new staticClass(); 这个静态变量是final的,但它的值是通过new来创建的,所以它属于运行时常量。因此,在第一次访问这个静态变量时,会触发静态内部类staticClassInner的初始化。

然而,在getInstance()方法中,我们访问了staticClassInner.instance,这会导致静态内部类staticClassInner被加载和初始化。在类初始化的过程中,会执行静态变量的赋值,即new staticClass(),所以会再次调用staticClass的构造方法(输出"staticClass constructor")。但是,静态内部类staticClassInner的构造方法并没有被调用,因为我们没有创建它的实例。

静态内部类的构造函数仅在显式创建内部类对象(使用new)时执行。访问其静态字段或方法只会初始化类(处理静态成员),不会实例化对象,因此构造函数不会运行。

参考链接:

  1. https://javabetter.cn/oo/static.html
posted @ 2025-06-15 23:23  joudys  阅读(39)  评论(0)    收藏  举报