static.md 13.9 KB
Newer Older
沉默王二's avatar
沉默王二 已提交
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
---
title: 一文彻底搞懂 Java static 关键字:静态变量、静态方法、静态代码块、静态内部类
shortTitle: 一文彻底搞懂Java static关键字
description: Java程序员进阶之路,小白的零基础Java教程,一文彻底搞懂 Java static 关键字:静态变量、静态方法、静态代码块、静态内部类
category:
  - Java 核心
tag:
  - 面向对象编程
head:
  - - meta
    - name: keywords
      content: Java,Java SE,Java 基础,Java 教程,Java 程序员进阶之路,Java 入门,Java static,java 静态变量,java 静态方法,java 静态代码块,java 静态内部类
---

“哥,你牙龈肿痛轻点没?周一的《教妹学 Java》(Java 程序员进阶之路的前身)你都没有更新,偷懒了呀!”三妹关心地问我。

“今天周四了,吃了三天的药,疼痛已经减轻不少,咱妈还给我打了电话,让我买点牛黄解毒片下下火。”我面带着微笑对三妹说,“学习可不能落下,今天我们来学 Java 中 `static` 关键字吧。”

“static 是 Java 中比较难以理解的一个关键字,也是各大公司的面试官最喜欢问到的一个知识点之一。”我喝了一口咖啡继续说道。

“既然是面试重点,那我可得好好学习下。”三妹连忙说。

“static 关键字的作用可以用一句话来描述:‘**方便在没有创建对象的情况下进行调用**,包括变量和方法’。也就是说,只要类被加载了,就可以通过类名进行访问。”我扶了扶沉重眼镜,继续说到,“static 可以用来修饰类的成员变量,以及成员方法。我们一个个来看。”

## 01、静态变量

“如果在声明变量的时候使用了 static 关键字,那么这个变量就被称为静态变量。静态变量只在类加载的时候获取一次内存空间,这使得静态变量很节省内存空间。”家里的暖气有点足,我跑去开了一点窗户后继续说道。

“来考虑这样一个 Student 类。”话音刚落,我就在键盘上噼里啪啦一阵敲。

```java
public class Student {
    String name;
    int age;
    String school = "郑州大学";
}
```

这段代码敲完后,我对三妹说:“假设郑州大学录取了一万名新生,那么在创建一万个 Student 对象的时候,所有的字段(name、age 和 school)都会获取到一块内存。学生的姓名和年纪不尽相同,但都属于郑州大学,如果每创建一个对象,school 这个字段都要占用一块内存的话,就很浪费,对吧?三妹。”

“因此,最好将 school 这个字段设置为 static,这样就只会占用一块内存,而不是一万块。”

安静的房子里又响起了一阵噼里啪啦的键盘声。

```java
public class Student {
    String name;
    int age;
    static String school = "郑州大学";

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

    public static void main(String[] args) {
        Student s1 = new Student("沉默王二", 18);
        Student s2 = new Student("沉默王三", 16);
    }
}
```

“瞧,三妹。s1 和 s2 这两个引用变量存放在栈区(stack),沉默王二+18 这个对象和沉默王三+16 这个对象存放在堆区(heap),school 这个静态变量存放在静态区。”

“等等,哥,栈、堆、静态区?”三妹的脸上塞满了疑惑。

“哦哦,别担心,三妹,画幅图你就全明白了。”说完我就打开 draw.io 这个网址,认真地画起了图。

![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/keywords/19-01.png)

“现在,是不是一下子就明白了?”看着这幅漂亮的手绘图,我心里有点小开心。

“哇,哥,惊艳了呀!”三妹也不忘拍马屁,给我了一个大大的赞。

“好了,三妹,我们来看下面这串代码。”

```java
public class Counter {
    int count = 0;

    Counter() {
        count++;
        System.out.println(count);
    }

    public static void main(String args[]) {
        Counter c1 = new Counter();
        Counter c2 = new Counter();
        Counter c3 = new Counter();
    }
}
```

“我们创建一个成员变量 count,并且在构造函数中让它自增。因为成员变量会在创建对象的时候获取内存,因此每一个对象都会有一个 count 的副本, count 的值并不会随着对象的增多而递增。”

我在侃侃而谈,而三妹似乎有些不太明白。

![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/keywords/19-02.png)


“没关系,三妹,你先盲猜一下,这段代码输出的结果是什么?”

“按照你的逻辑,应该输出三个 1?是这样吗?”三妹眨眨眼,有点不太自信地回答。

“哎呀,不错哟。”

我在 IDEA 中点了一下运行按钮,程序跑了起来。

```
1
1
1
```

“每创建一个 Counter 对象,count 的值就从 0 自增到 1。三妹,想一下,如果 count 是静态的呢?”

“我不知道啊。”

“嗯,来看下面这段代码。”

```java
public class StaticCounter {
    static int count = 0;

    StaticCounter() {
        count++;
        System.out.println(count);
    }

    public static void main(String args[]) {
        StaticCounter c1 = new StaticCounter();
        StaticCounter c2 = new StaticCounter();
        StaticCounter c3 = new StaticCounter();
    }
}
```

“来看一下输出结果。”

```
1
2
3
```

“简单解释一下哈,由于静态变量只会获取一次内存空间,所以任何对象对它的修改都会得到保留,所以每创建一个对象,count 的值就会加 1,所以最终的结果是 3,明白了吧?三妹。这就是静态变量和成员变量之间的差别。”

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

![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/keywords/19-03.png)


## 02、静态方法

“说完静态变量,我们来说静态方法。”说完,我准备点一支华子来抽,三妹阻止了我,她指一指烟盒上的「吸烟有害身体健康」,我笑了。

“好吧。”我只好喝了一口咖啡继续说,“如果方法上加了 static 关键字,那么它就是一个静态方法。”

“静态方法有以下这些特征。”

- 静态方法属于这个类而不是这个类的对象;
- 调用静态方法的时候不需要创建这个类的对象;
- 静态方法可以访问静态变量。

“来,继续上代码”

```java
public class StaticMethodStudent {
    String name;
    int age;
    static String school = "郑州大学";

    public StaticMethodStudent(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    static void change() {
        school = "河南大学";
    }
    
    void out() {
        System.out.println(name + " " + age + " " + school);
    }

    public static void main(String[] args) {
        StaticMethodStudent.change();
        
        StaticMethodStudent s1 = new StaticMethodStudent("沉默王二", 18);
        StaticMethodStudent s2 = new StaticMethodStudent("沉默王三", 16);
        
        s1.out();
        s2.out();
    }
}
```

“仔细听,三妹。`change()` 方法就是一个静态方法,所以它可以直接访问静态变量 school,把它的值更改为河南大学;并且,可以通过类名直接调用 `change()` 方法,就像 ` StaticMethodStudent.change()` 这样。”

“来看一下程序的输出结果吧。”

```
沉默王二 18 河南大学
沉默王三 16 河南大学
```

“需要注意的是,静态方法不能访问非静态变量和调用非静态方法。你看,三妹,我稍微改动一下代码,编译器就会报错。”

“先是在静态方法中访问非静态变量,编译器不允许。”

![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/keywords/19-04.png)

“然后在静态方法中访问非静态方法,编译器同样不允许。”

![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/keywords/19-05.png)

“关于静态方法的使用,这下清楚了吧,三妹?”

看着三妹点点头,我欣慰地笑了。

“哥,我想到了一个问题,为什么 main 方法是静态的啊?”没想到,三妹串联知识点的功力还是不错的。

“如果 main 方法不是静态的,就意味着 Java 虚拟机在执行的时候需要先创建一个对象才能调用 main 方法,而 main 方法作为程序的入口,创建一个额外的对象显得非常多余。”我不假思索的回答令三妹感到非常的钦佩。

“java.lang.Math 类的几乎所有方法都是静态的,可以直接通过类名来调用,不需要创建类的对象。”

![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/keywords/19-06.png)

## 03、静态代码块

“三妹,站起来活动一下,我的脖子都有点僵硬了。”

我们一起走到窗户边,映入眼帘的是从天而降的雪花。三妹和我都高兴坏了,迫不及待地打开窗口,伸出手去触摸雪花的温度,那种稍纵即逝的冰凉,真的舒服极了。

“北国风光,千里冰封,万里雪飘。望长城内外,惟余莽莽;大河上下,顿失滔滔。山舞银蛇,原驰蜡象,欲与天公试比高。须晴日,看红装素裹,分外妖娆。。。。。。”三妹竟然情不自禁地朗诵起了《沁园春·雪》。

确实令人欣喜,这是 2020 年洛阳的第一场雪,的确令人感到开心。

片刻之后。

“除了静态变量和静态方法,static 关键字还有一个重要的作用。”我心情愉悦地对三妹说,“用一个 static 关键字,外加一个大括号括起来的代码被称为静态代码块。”

“就像下面这串代码。”

```java
public class StaticBlock {
    static {
        System.out.println("静态代码块");
    }

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

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


“来看一下程序的输出结果吧。”

```
静态代码块
main 方法
```

“二哥,既然静态代码块先于 `main()` 方法执行,那没有 `main()` 方法的 Java 类能执行成功吗?”三妹的脑回路越来越令我敬佩了。

“Java 1.6 是可以的,但 Java 7 开始就无法执行了。”我胸有成竹地回答到。

```java
public class StaticBlockNoMain {
    static {
        System.out.println("静态代码块,没有 main");
    }
}
```

“在命令行中执行 `java StaticBlockNoMain` 的时候,会抛出 NoClassDefFoundError 的错误。”

![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/keywords/19-07.png)

“三妹,来看下面这个例子。”

```java
public class StaticBlockDemo {
    public static List<String> writes = new ArrayList<>();

    static {
        writes.add("沉默王二");
        writes.add("沉默王三");
        writes.add("沉默王四");

        System.out.println("第一块");
    }

    static {
        writes.add("沉默王五");
        writes.add("沉默王六");

        System.out.println("第二块");
    }
}
```

“writes 是一个静态的 ArrayList,所以不太可能在声明的时候完成初始化,因此需要在静态代码块中完成初始化。”

“静态代码块在初始集合的时候,真的非常有用。在实际的项目开发中,通常使用静态代码块来加载配置文件到内存当中。”

## 04、静态内部类

“三妹啊,除了以上只写,static 还有一个不太常用的功能——静态内部类。”

“Java 允许我们在一个类中声明一个内部类,它提供了一种令人信服的方式,允许我们只在一个地方使用一些变量,使代码更具有条理性和可读性。”

“常见的内部类有四种,成员内部类、局部内部类、匿名内部类和静态内部类,限于篇幅原因,前三种不在我们本次的讨论范围之内,以后有机会再细说。”

“来看下面这个例子。”三妹有点走神,我敲了敲她的脑袋后继续说。

```java
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 类的唯一性。不过,创建单例更优雅的一种方式是使用枚举,以后再讲给你听。”

“需要注意的是。第一,静态内部类不能访问外部类的所有成员变量;第二,静态内部类可以访问外部类的所有静态变量,包括私有静态变量。第三,外部类不能声明为 static。”

“三妹,你看,在 Singleton 类上加 static 后,编译器就提示错误了。”

![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/keywords/19-08.png)

三妹点了点头,所有所思。

----

最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)

关注二哥的原创公众号 **沉默王二**,回复**111** 即可免费领取。

![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/xingbiaogongzhonghao.png)