从 01 开始 从 01 开始
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • RSS
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金等
  • 职场规划
  • 关于离职
  • 杂谈
  • 📖 读书

    • 读书工具
    • 读书笔记
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档

晓林

程序猿,自由职业者,博主,英语爱好者,健身达人
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • RSS
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金等
  • 职场规划
  • 关于离职
  • 杂谈
  • 📖 读书

    • 读书工具
    • 读书笔记
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档
  • JavaSE

    • 我的 Java 学习路线
    • 安装 Java
    • Java数据类型

    • Java 多版本配置
    • 面向对象

      • 枚举类
      • static 关键字
        • 静态字段 demo
        • 静态方法 demo
        • 静态方法的作用
        • 关于实例方法和静态方法之间的关系
        • 关于访问性
        • static 代码块
        • 接口的静态字段
        • 一些关于 static 的面试题
        • 面试题 5
        • 小结
        • 参考
        • 前言
        • 静态字段demo
        • 静态方法demo
        • 静态方法的作用
        • 关于实例方法和静态方法之间的关系
        • 关于访问性
        • static代码块
        • 接口的静态字段
        • 一些关于static的面试题
        • 面试题5
        • 小结
        • 参考
      • classpath:JVM 查找类的机制
      • 包:Java 组织类的方式
      • jar 包
      • class 版本
      • 抽象类
      • 接口
      • 访问性修饰符
      • 非访问性修饰符
      • 内部类
    • Java核心类

    • IO

    • Java与时间

    • 异常处理

    • 哈希和加密算法

    • Java8新特性

    • 网络编程

  • JavaSenior

  • JavaEE

  • JavaWeb

  • Spring

  • 主流框架

  • SpringMVC

  • SpringBoot

  • Java
  • JavaSE
  • 面向对象
2022-12-04
目录

static 关键字

# static 关键字

在一个 class 中定义的字段,我们称之为实例字段,它的特点是:每个实例都有独立的字段,各个实例的同名字段互不影响。

但有时候,我们想让一个类的所有实例共享数据,就要使用静态变量(static variable),也称为类变量(class variable)。

例如,我们想知道这个类的对象有多少个,可以使用类变量存储;当每次新创建对象的时候,都给该变量自增。这种变量,如果存储在实例字段里是很难做到的。

静态变量将变量值存储在一个公共的内存地址,它当且仅当在类初次加载时会被初始化。因为它是公共的地址,所以如果某一个对象修改了静态变量的值,那么同一个类的所有对象都会受到影响。

静态方法:Java 还支持静态方法,无须创建类的实例就可以调用静态方法(static method)。

总的来说,static 的作用:方便在没有创建对象的情况下来进行调用(方法/变量)。

# 静态字段 demo

我们定义一个 Person 类,里面定义一个静态字段。

public class StaticField {
  public static void main(String[] args) {
    Person ming = new Person("xiao ming", 0);
    Person hong = new Person("xiao hong", 0);

    ming.number = 88;
    System.out.println(hong.number);  //88

    hong.number = 99;
    System.out.println(ming.number);  //99
  }
}

class Person{
  public String name;
  public int age;

  // 定义静态字段number:
  public static int number;

  public Person(String name, int age){
    this.name = name;
    this.age = age;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

编译和运行:

> javac StaticField.java -encoding utf8
> java StaticField
88
99
1
2
3
4

对于静态字段,无论修改哪个实例的静态字段,效果都是一样的:所有实例的静态字段都被修改了,原因是静态字段并不属于实例:可以把静态字段理解为描述 class 本身的字段(非实例字段)。

        ┌──────────────────┐
ming ──→│Person instance   │
        ├──────────────────┤
        │name = "Xiao Ming"│
        │age = 12          │
        │number ───────────┼──┐     ┌─────────────┐
        └──────────────────┘  │     │Person class │
                              │     ├─────────────┤
                              ├───→ │number = 99  │
        ┌──────────────────┐  │     └─────────────┘
hong ──→│Person instance   │  │
        ├──────────────────┤  │
        │name = "Xiao Hong"│  │
        │age = 15          │  │
        │number ───────────┼──┘
        └──────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

虽然实例可以访问静态字段,但是它们指向的其实都是 Person class的静态字段。所以,所有实例共享一个静态字段。

但是,不推荐用 实例变量.静态字段 去访问静态字段,因为在 Java 程序中,实例对象并没有静态字段。在代码中,实例对象能访问静态字段,只是因为编译器可以根据实例类型自动转换为 类名.静态字段 来访问静态对象。

推荐用 类名.静态字段 的方式访问静态变量,这样提高了可读性,别人一看可以用类名的方式访问,就知道这是一个静态变量或方法了 :

Person.number = 100;  //recommend
System.out.println(Person.number);
1
2

# 静态方法 demo

有静态字段,就有静态方法。用 static 修饰的方法称为静态方法。

调用实例方法必须通过一个实例变量,而调用静态方法则不需要实例变量,通过类名就可以调用。静态方法类似其它编程语言的函数。例如:

// static method
public class Main {
    public static void main(String[] args) {
        Person.setNumber(99);
        System.out.println(Person.number);
    }
}

class Person {
    public static int number;

    public static void setNumber(int value) {
        number = value;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

因为静态方法属于 class 而不属于实例,因此,静态方法内部,无法访问 this 变量,也无法访问实例字段,它只能访问静态字段。

通过实例变量也可以调用静态方法,但这只是编译器自动帮我们把实例改写成类名而已。

# 静态方法的作用

静态方法经常用于工具类。例如:

  • Arrays.sort()
  • Math.random()

静态方法也经常用于辅助方法。注意到 Java 程序的入口 main() 也是静态方法。为什么 main 方法必须是 static 的,现在就很清楚了。因为程序在执行 main 方法的时候没有创建任何对象,因此只有通过类名来访问

# 关于实例方法和静态方法之间的关系

  • 实例方法可以调用实例方法和静态方法,以及访问实例数据域或者静态数据域。
  • 静态方法中不允许使用 this 或是 super 关键字,可以继承,不能重写、没有多态。
  • 静态方法可以调用静态方法以及访问静态数据域。
  • 然而,静态方法不能调用实例方法或者访问实例数据域,因为静态方法和静态数据域不属于某个特定的对象。而非静态成员方法/变量都是必须依赖具体的对象才能够被调用。静态成员和实例成员的关系总结在下表中。
实例方法 实例数据域 静态方法 静态数据域
实例方法 √ 调用 √ 访问 √ 调用 √ 访问
静态方法 × 调用 × 访问 √ 调用 √ 访问

# 关于访问性

注意:static 关键字不会影响到变量或者方法的作用域,在 Java 中能够影响到访问权限的只有 private、public、protected(包括默认的包访问权限)这几个关键字。例如我们尝试访问 private 和 static 修饰的字段:

public class StaticAccess {
  public static void main(String[] args) {
    System.out.println(Person.name);
    System.out.println(Person.age);  
  }
}

class Person{
  public static String name = "zhangsan";
  private static int age = 10;
}
1
2
3
4
5
6
7
8
9
10
11

编译会报错:

> javac StaticAccess.java  
StaticAccess.java:4: 错误: age 在 Person 中是 private 访问控制
    System.out.println(Person.age);
                             ^
1 个错误
1
2
3
4
5

# static 代码块

static 关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static 块可以置于类中的任何地方,类中可以有多个 static 块。在类初次被加载的时候,会按照 static 块的顺序来执行每个 static 块,并且只会执行一次。

public class StaticCode {

  static{
    int num = 1;
    System.out.println("static code execute!");
  }
  public static void main(String[] args) {
    StaticCode t = new StaticCode();
    System.out.println("Static main execute!");
    StaticCode t2 = new StaticCode();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
$ javac .\StaticCode.java

$ java StaticCode 
static code execute!
Static main execute!
1
2
3
4
5

static 代码块有什么用?举个离子:

class Person{
    private Date birthDate;
   
    public Person(Date birthDate) {
        this.birthDate = birthDate;
    }
   
    boolean isBornBoomer() {
        Date startDate = Date.valueOf("1946");
        Date endDate = Date.valueOf("1964");
        return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

isBornBoomer 是用来这个人是否是 1946-1964 年出生的,而每次 isBornBoomer 被调用的时候,都会生成 startDate 和 birthDate 两个对象,造成了空间浪费,如果改成这样效率会更好:

class Person{
    private Date birthDate;
    private static Date startDate,endDate;
    static{
        startDate = Date.valueOf("1946");
        endDate = Date.valueOf("1964");
    }
   
    public Person(Date birthDate) {
        this.birthDate = birthDate;
    }
   
    boolean isBornBoomer() {
        return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

因此,很多时候会将一些只需要进行一次的初始化操作都放在 static 代码块中进行。

# 接口的静态字段

因为 interface 是一个纯抽象类,所以它不能定义实例字段。但是,interface 是可以有静态字段的,并且静态字段必须为 final 类型:

public interface Person {
    public static final int MALE = 1;
    public static final int FEMALE = 2;
}
1
2
3
4

实际上,因为 interface 的字段只能是 public static final 类型,所以我们可以把这些修饰符都去掉,上述代码可以简写为:

public interface Person {
    // 编译器会自动加上public statc final:
    int MALE = 1;
    int FEMALE = 2;
}
1
2
3
4
5

编译器会自动把该字段变为 public static final 类型。

# 一些关于 static 的面试题

static 关键字是很多朋友在编写代码和阅读代码时碰到的比较难以理解的一个关键字,也是各大公司的面试官喜欢在面试时问到的知识点之一

# 面试题 1

public class StaticInterview1 {

  static{
    System.out.println("test static 1");
  }
  public static void main(String[] args) {
  
  }

  static{
    System.out.println("test static 2");
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

输出是什么?

分析:虽然在 main 方法中没有任何语句,但是还是会输出,原因上面已经讲述过了。另外,static 块可以出现类中的任何地方(只要不是方法内部,记住,任何方法内部都不行),并且执行是按照 static 块的顺序执行的。

test static 1
test static 2
1
2

# 面试题 2

public class StaticInterview2 {
  static int value = 33;
  public static void main(String[] args) {
    new StaticInterview2().printValue();;
  }

  private void printValue(){
    int value = 3;
    System.out.println(this.value);
  }
}
1
2
3
4
5
6
7
8
9
10
11

这里面主要考察对 this 和 static 的理解。this 代表什么?this 代表当前对象,那么通过 new StaticInterview2() 来调用 printValue 的话,当前对象就是通过 new StaticInterview2() 生成的对象。而 static 变量是被对象所享有的,因此在 printValue 中的 this.value 的值毫无疑问是 33。在 printValue 方法内部的 value 是局部变量,根本不可能与 this 关联,所以输出结果是 33。

另外,static 不允许用来修饰局部变量(在 C/C++ 中 static 是可以作用域局部变量的),不要问为什么,问就是 Java 语法的规定。

# 面试题 3

public class StaticInterview1 {
  public static void main(String[] args) {
    new Test();
  }
}


class Test extends Base{

  static{
      System.out.println("test static");
  }
 
  public Test(){
      System.out.println("test constructor");
  }
 
  public static void main(String[] args) {
      new Test();
  }
}

class Base{
 
  static{
      System.out.println("base static");
  }
 
  public Base(){
      System.out.println("base constructor");
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

输出结果是什么?请先不要看答案,尝试自己分析下。

分析:

  1. 在执行开始,先要寻找到 main 方法,因为 main 方法是程序的入口。然后加载 Test 类,而在加载 Test 类的时候发现 Test 类继承自 Base 类,因此会转去先加载 Base 类,在加载 Base 类的时候,发现有 static 块,便执行了 static 块,输出 base static
  2. 在 Base 类加载完成之后,便继续加载 Test 类,然后发现 Test 类中也有 static 块,便执行 static 块,输出 test static
  3. 在加载完所需的类之后,便开始执行 main 方法。在 main 方法中执行 new Test()的时候会先调用父类的构造器,输出 base constructor
  4. 然后再调用自身的构造器,输出 test constructor

因此,输出结果如下:

base static
test static
base constructor
test constructor
1
2
3
4

# 面试题 4

public class StaticInterview4 {
  Person person = new Person("Test");
  static {
    System.out.println("test static");
  }

  public StaticInterview4() {
    System.out.println("test constructor");
  }

  public static void main(String[] args) {
    new MyClass();
  }
}

class Person {
  static {
    System.out.println("person static");
  }

  public Person(String str) {
    System.out.println("person " + str);
  }
}

class MyClass extends StaticInterview4 {
  Person person = new Person("MyClass");
  static {
    System.out.println("myclass static");
  }

  public MyClass() {
    System.out.println("myclass constructor");
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

请写出输出结果

分析:类似地,我们还是来想一下这段代码的具体执行过程。

  • 首先加载 StaticInterview4 类,因此会执行 StaticInterview4 类中的 static 块,输出“test static”
  • 接着执行 new MyClass(),而 MyClass 类还没有被加载,因此需要加载 MyClass 类。在加载 MyClass 类的时候,发现 MyClass 类继承自 StaticInterview4 类,但是由于 StaticInterview4 类已经被加载了,所以只需要加载 MyClass 类,那么就会执行 MyClass 类的中的 static 块,输出“myclass static”
  • 在加载完之后,就通过构造器来生成对象。而在生成 MyClass 对象的时候,必须先初始化父类 StaticInterview4 的成员变量,因此会执行 StaticInterview4 中的 Person person = new Person("Test"),而 Person 类还没有被加载过,因此会先加载 Person 类并执行 Person 类中的 static 块,输出 “person static”
  • 接着执行父类 StaticInterview4 的构造器,完成了父类的初始化,输出 test constructor
  • 然后就来初始化自身了,因此会接着执行 MyClass 中的 Person person = new Person("MyClass"),输出 Person MyClass,
  • 最后执行 MyClass 的构造器,输出 myclass constructor
test static
myclass static
person static
person Test
test constructor
person MyClass
myclass constructor
1
2
3
4
5
6
7

# 面试题 5

public class Test{
    private static Test tester = new Test();
    private static int count1;
    private static int count2 = 2;
    public Test(){
        count1++;
        count2++;
        System.out.println("count1: " + count1);
        System.out.println("count2: " + count2);
    }

    public static Test getTester(){
        return tester;
    }
    public static void main(String[] args) {
        Test.getTester();
        System.out.println("count1: " + count1);
        System.out.println("count2: " + count2);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

JAVA 类首次装入时,开始加载静态变量和静态块,也就是说会首先为静态区域分配内存空间,此时 tester、count1、count2 都已经分配空间,其中 tester 为一个引用空间,count1、count2 为默认值 0。

第二步开始执行 private static Test tester = new Test() 这段代码,调用构造器打印出 count1、count2 分别为 1 和 1 。

然后依次执行一下代码 private static int count1; private static int count2 = 2; 此时,count2 被重置为 2,因此如果此时再次打印的话 count1、count2 的值应该为 1 和 2。

count1: 1
count2: 1
count1: 1
count2: 2
1
2
3
4

# 小结

  • static 比较难以理解,也是面试常问的,常用来和 Java 的继承结合起来出题。但只要稍微静下心来自己动手实践下,也可以掌握
  • 静态字段属于所有实例“共享”的字段,实际上是属于 class的字段;
  • 调用静态方法不需要实例,无法访问 this,但可以访问静态字段和其他静态方法,静态方法常用于工具类和辅助方法。
  • static 代码块常用于初始化,在类加载的时候会被执行(且只会被执行一次)
  • 接口可以有静态字段,其相当于一个常量

Java 程序运行的时候,会做什么

  1. 找到 main 方法
  2. 加载 main 方法所在的类,但如果其有父类,则先加载父类,类似构造方法链一样,有个加载链
  3. 每加载一个类,就先执行其 static 代码块
  4. 执行 main 方法
  5. 再创建一个类的对象的时候,如果其有父类,则先初始化父类的成员变量,再调用父类的构造方法,创建父类的对象
  6. 创建完父类对象后,在初始化自身的成员变量,再调用自己的构造方法创建对象(构造方法链)

# 参考

  • 静态字段和静态方法 - 廖雪峰的官方网站 (opens new window)
  • 《Java 语言程序设计-基础篇》9.7 节
  • static 关键字详解 - 沦为旧友 - 博客园 (opens new window)
  • Java 中的 static 关键字解析 - Matrix 海子 - 博客园 (opens new window)
  • 构造器是静态方法吗?-向上攀爬的程序员的博客-CSDN (opens new window)
  • Java 小知识玩出大花样! (opens new window)

在一个class中定义的字段,我们称之为实例字段。实例字段的特点是,每个实例都有独立的字段,各个实例的同名字段互不影响。

但有时候,我们想让一个类的所有实例共享数据,就要使用静态变量(static variable),也称为类变量(class variable)。

# 前言

静态变量将变量值存储在一个公共的内存地址,它当且仅当在类初次加载时会被初始化。因为它是公共的地址,所以如果某一个对象修改了静态变量的值,那么同一个类的所有对象都会受到影响。

静态方法:Java还支持静态方法,无须创建类的实例就可以调用静态方法(static method)。

总的来说,static的作用:方便在没有创建对象的情况下来进行调用(方法/变量)。 ‍

# 静态字段demo

我们定义一个Person类,里面定义一个静态字段。

public class StaticField {
  public static void main(String[] args) {
    Person ming = new Person("xiao ming", 0);
    Person hong = new Person("xiao hong", 0);

    ming.number = 88;
    System.out.println(hong.number);  //88

    hong.number = 99;
    System.out.println(ming.number);  //99
  }
}

class Person{
  public String name;
  public int age;

  // 定义静态字段number:
  public static int number;

  public Person(String name, int age){
    this.name = name;
    this.age = age;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

编译和运行:

> javac StaticField.java -encoding utf8
> java StaticField
88
99
1
2
3
4

对于静态字段,无论修改哪个实例的静态字段,效果都是一样的:所有实例的静态字段都被修改了,原因是静态字段并不属于实例:可以把静态字段理解为描述class本身的字段(非实例字段)。

        ┌──────────────────┐
ming ──→│Person instance   │
        ├──────────────────┤
        │name = "Xiao Ming"│
        │age = 12          │
        │number ───────────┼──┐     ┌─────────────┐
        └──────────────────┘  │     │Person class │
                              │     ├─────────────┤
                              ├───→ │number = 99  │
        ┌──────────────────┐  │     └─────────────┘
hong ──→│Person instance   │  │
        ├──────────────────┤  │
        │name = "Xiao Hong"│  │
        │age = 15          │  │
        │number ───────────┼──┘
        └──────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

虽然实例可以访问静态字段,但是它们指向的其实都是Person class的静态字段。所以,所有实例共享一个静态字段。

但是,不推荐用实例变量.静态字段去访问静态字段,因为在Java程序中,实例对象并没有静态字段。在代码中,实例对象能访问静态字段,只是因为编译器可以根据实例类型自动转换为类名.静态字段来访问静态对象。

推荐用 类名.静态字段 的方式访问静态变量,这样提高了可读性,别人一看可以用类名的方式访问,就知道这是一个静态变量或方法了 :

Person.number = 100;  //recommend
System.out.println(Person.number);
1
2

# 静态方法demo

有静态字段,就有静态方法。用static修饰的方法称为静态方法。

调用实例方法必须通过一个实例变量,而调用静态方法则不需要实例变量,通过类名就可以调用。静态方法类似其它编程语言的函数。例如:

// static method
public class Main {
    public static void main(String[] args) {
        Person.setNumber(99);
        System.out.println(Person.number);
    }
}

class Person {
    public static int number;

    public static void setNumber(int value) {
        number = value;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

因为静态方法属于class而不属于实例,因此,静态方法内部,无法访问this变量,也无法访问实例字段,它只能访问静态字段。

通过实例变量也可以调用静态方法,但这只是编译器自动帮我们把实例改写成类名而已。 ‍

# 静态方法的作用

静态方法经常用于工具类。例如:

  • Arrays.sort()
  • Math.random()

静态方法也经常用于辅助方法。注意到Java程序的入口main()也是静态方法。为什么main方法必须是static的,现在就很清楚了。因为程序在执行main方法的时候没有创建任何对象,因此只有通过类名来访问 ‍

# 关于实例方法和静态方法之间的关系

  • 实例方法可以调用实例方法和静态方法,以及访问实例数据域或者静态数据域。
  • 静态方法中不允许使用this或是super关键字,可以继承,不能重写、没有多态。
  • 静态方法可以调用静态方法以及访问静态数据域。
  • 然而,静态方法不能调用实例方法或者访问实例数据域,因为静态方法和静态数据域不属于某个特定的对象。而非静态成员方法/变量都是必须依赖具体的对象才能够被调用。静态成员和实例成员的关系总结在下表中。
实例方法 实例数据域 静态方法 静态数据域
实例方法 √调用 √访问 √调用 √访问
静态方法 ×调用 ×访问 √调用 √访问

# 关于访问性

‍ 注意:static关键字不会影响到变量或者方法的作用域,在Java中能够影响到访问权限的只有private、public、protected(包括默认的包访问权限)这几个关键字。例如我们尝试访问private 和 static修饰的字段:

public class StaticAccess {
  public static void main(String[] args) {
    System.out.println(Person.name);
    System.out.println(Person.age);  
  }
}

class Person{
  public static String name = "zhangsan";
  private static int age = 10;
}
1
2
3
4
5
6
7
8
9
10
11

编译会报错:

> javac StaticAccess.java  
StaticAccess.java:4: 错误: age 在 Person 中是 private 访问控制
    System.out.println(Person.age);
                             ^
1 个错误
1
2
3
4
5

‍

# static代码块

static关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。

public class StaticCode {

  static{
    int num = 1;
    System.out.println("static code execute!");
  }
  public static void main(String[] args) {
    StaticCode t = new StaticCode();
    System.out.println("Static main execute!");
    StaticCode t2 = new StaticCode();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

‍

$ javac .\StaticCode.java

$ java StaticCode 
static code execute!
Static main execute!
1
2
3
4
5

‍ static代码块有什么用?举个离子:

class Person{
    private Date birthDate;
   
    public Person(Date birthDate) {
        this.birthDate = birthDate;
    }
   
    boolean isBornBoomer() {
        Date startDate = Date.valueOf("1946");
        Date endDate = Date.valueOf("1964");
        return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

isBornBoomer是用来这个人是否是1946-1964年出生的,而每次isBornBoomer被调用的时候,都会生成startDate和birthDate两个对象,造成了空间浪费,如果改成这样效率会更好:

class Person{
    private Date birthDate;
    private static Date startDate,endDate;
    static{
        startDate = Date.valueOf("1946");
        endDate = Date.valueOf("1964");
    }
   
    public Person(Date birthDate) {
        this.birthDate = birthDate;
    }
   
    boolean isBornBoomer() {
        return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

因此,很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。

# 接口的静态字段

因为interface是一个纯抽象类,所以它不能定义实例字段。但是,interface是可以有静态字段的,并且静态字段必须为final类型:

public interface Person {
    public static final int MALE = 1;
    public static final int FEMALE = 2;
}
1
2
3
4

实际上,因为interface的字段只能是public static final类型,所以我们可以把这些修饰符都去掉,上述代码可以简写为:

public interface Person {
    // 编译器会自动加上public statc final:
    int MALE = 1;
    int FEMALE = 2;
}
1
2
3
4
5

编译器会自动把该字段变为public static final类型。 ‍

# 一些关于static的面试题

static关键字是很多朋友在编写代码和阅读代码时碰到的比较难以理解的一个关键字,也是各大公司的面试官喜欢在面试时问到的知识点之一

# 面试题1

public class StaticInterview1 {

  static{
    System.out.println("test static 1");
  }
  public static void main(String[] args) {
  
  }

  static{
    System.out.println("test static 2");
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

输出是什么? ‍ 分析:虽然在main方法中没有任何语句,但是还是会输出,原因上面已经讲述过了。另外,static块可以出现类中的任何地方(只要不是方法内部,记住,任何方法内部都不行),并且执行是按照static块的顺序执行的。

test static 1
test static 2
1
2

‍

# 面试题2

public class StaticInterview2 {
  static int value = 33;
  public static void main(String[] args) {
    new StaticInterview2().printValue();;
  }

  private void printValue(){
    int value = 3;
    System.out.println(this.value);
  }
}
1
2
3
4
5
6
7
8
9
10
11

‍ 这里面主要考察对this和static的理解。this代表什么?this代表当前对象,那么通过new StaticInterview2()来调用printValue的话,当前对象就是通过new StaticInterview2()生成的对象。而static变量是被对象所享有的,因此在printValue中的this.value的值毫无疑问是33。在printValue方法内部的value是局部变量,根本不可能与this关联,所以输出结果是33。

另外,static不允许用来修饰局部变量(在C/C++中static是可以作用域局部变量的),不要问为什么,这是Java语法的规定。

# 面试题3

public class StaticInterview1 {
  public static void main(String[] args) {
    new Test();
  }
}


class Test extends Base{

  static{
      System.out.println("test static");
  }
 
  public Test(){
      System.out.println("test constructor");
  }
 
  public static void main(String[] args) {
      new Test();
  }
}

class Base{
 
  static{
      System.out.println("base static");
  }
 
  public Base(){
      System.out.println("base constructor");
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

输出结果是什么?请先不要看答案,尝试自己分析下。

分析:

  1. 在执行开始,先要寻找到main方法,因为main方法是程序的入口。然后加载Test类,而在加载Test类的时候发现Test类继承自Base类,因此会转去先加载Base类,在加载Base类的时候,发现有static块,便执行了static块,输出base static
  2. 在Base类加载完成之后,便继续加载Test类,然后发现Test类中也有static块,便执行static块,输出test static
  3. 在加载完所需的类之后,便开始执行main方法。在main方法中执行new Test()的时候会先调用父类的构造器,输出base constructor
  4. 然后再调用自身的构造器,输出test constructor

因此,输出结果如下:

base static
test static
base constructor
test constructor
1
2
3
4

# 面试题4

public class StaticInterview4 {
  Person person = new Person("Test");
  static {
    System.out.println("test static");
  }

  public StaticInterview4() {
    System.out.println("test constructor");
  }

  public static void main(String[] args) {
    new MyClass();
  }
}

class Person {
  static {
    System.out.println("person static");
  }

  public Person(String str) {
    System.out.println("person " + str);
  }
}

class MyClass extends StaticInterview4 {
  Person person = new Person("MyClass");
  static {
    System.out.println("myclass static");
  }

  public MyClass() {
    System.out.println("myclass constructor");
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

请写出输出结果

分析:类似地,我们还是来想一下这段代码的具体执行过程。

  • 首先加载StaticInterview4类,因此会执行StaticInterview4类中的static块,输出“test static”
  • 接着执行new MyClass(),而MyClass类还没有被加载,因此需要加载MyClass类。在加载MyClass类的时候,发现MyClass类继承自StaticInterview4类,但是由于StaticInterview4类已经被加载了,所以只需要加载MyClass类,那么就会执行MyClass类的中的static块,输出“myclass static”
  • 在加载完之后,就通过构造器来生成对象。而在生成MyClass对象的时候,必须先初始化父类StaticInterview4的成员变量,因此会执行StaticInterview4中的Person person = new Person("Test"),而Person类还没有被加载过,因此会先加载Person类并执行Person类中的static块,输出 “person static”
  • 接着执行父类StaticInterview4的构造器,完成了父类的初始化,输出test constructor
  • 然后就来初始化自身了,因此会接着执行MyClass中的Person person = new Person("MyClass"),输出Person MyClass,
  • 最后执行MyClass的构造器,输出myclass constructor
test static
myclass static
person static
person Test
test constructor
person MyClass
myclass constructor
1
2
3
4
5
6
7

# 面试题5

public class Test{
    private static Test tester = new Test();
    private static int count1;
    private static int count2 = 2;
    public Test(){
        count1++;
        count2++;
        System.out.println("count1: " + count1);
        System.out.println("count2: " + count2);
    }

    public static Test getTester(){
        return tester;
    }
    public static void main(String[] args) {
        Test.getTester();
        System.out.println("count1: " + count1);
        System.out.println("count2: " + count2);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

JAVA 类首次装入时,开始加载静态变量和静态块,也就是说会首先为静态区域分配内存空间,此时 tester、count1、count2 都已经分配空间,其中 tester 为一个引用空间,count1、count2 为默认值 0。

第二步开始执行 private static Test tester = new Test() 这段代码,调用构造器打印出 count1、count2 分别为 1 和 1 。

然后依次执行一下代码 private static int count1; private static int count2 = 2; 此时,count2 被重置为 2,因此如果此时再次打印的话 count1、count2 的值应该为 1 和 2。 ‍

count1: 1
count2: 1
count1: 1
count2: 2
1
2
3
4

# 小结

  • static比较难以理解,也是面试常问的,常用来和Java的继承结合起来出题。但只要稍微静下心来自己动手实践下,也可以掌握
  • 静态字段属于所有实例“共享”的字段,实际上是属于class的字段;
  • 调用静态方法不需要实例,无法访问this,但可以访问静态字段和其他静态方法,静态方法常用于工具类和辅助方法。
  • static代码块常用于初始化,在类加载的时候会被执行(且只会被执行一次)
  • 接口可以有静态字段,其相当于一个常量

Java程序运行的时候,会做什么

  1. 找到main方法
  2. 加载main方法所在的类,但如果其有父类,则先加载父类,类似构造方法链一样,有个加载链
  3. 每加载一个类,就先执行其static代码块
  4. 执行main方法
  5. 再创建一个类的对象的时候,如果其有父类,则先初始化父类的成员变量,再调用父类的构造方法,创建父类的对象
  6. 创建完父类对象后,在初始化自身的成员变量,再调用自己的构造方法创建对象(构造方法链) ‍ 本文涉及代码:LearnJava: 学习Java相关技术实践用的项目 - Gitee.com (opens new window) ‍

# 参考

  • 静态字段和静态方法 - 廖雪峰的官方网站 (opens new window)
  • 《Java语言程序设计-基础篇》9.7节
  • static关键字详解 - 沦为旧友 - 博客园 (opens new window)
  • Java中的static关键字解析 - Matrix海子 - 博客园 (opens new window)
  • 构造器是静态方法吗?-向上攀爬的程序员的博客-CSDN (opens new window)
  • Java 小知识玩出大花样! (opens new window)

‍

上次更新: 2025/5/5 17:15:09
枚举类
classpath:JVM 查找类的机制

← 枚举类 classpath:JVM 查找类的机制→

最近更新
01
2025 年 4 月记
04-30
02
山西大同 “订婚强奸案” 将会给整个社会带来的影响有多严重? - 知乎 转载
04-26
03
一个小技巧,让电子书阅读体验翻倍!
04-18
更多文章>
Theme by Vdoing | Copyright © 2022-2025 | 粤 ICP 备 2022067627 号 -1 | 粤公网安备 44011302003646 号 | 点击查看十年之约
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式