提交 158dfb03 编写于 作者: 沉默王二's avatar 沉默王二 💬

结构调整

上级 92395634
......@@ -43,72 +43,66 @@
- [什么是 Java](docs/overview/what-is-java.md)
- [Java 发展简史](docs/overview/java-history.md)
- [Java 为什么如此流行](docs/overview/why-java-popular.md)
- [环境准备:Intellij IDEA](docs/overview/idea.md)
- [第一个 Java 程序:Hello World](docs/overview/hello-world.md)
- [Java 的优势](docs/overview/java-advantage.md)
- [JDK 和 JRE 有什么区别](docs/overview/jdk-jre.md)
- [JVM 是什么](docs/overview/jvm.md)
- [安装集成开发环境 Intellij IDEA](docs/overview/idea.md)
- [第一个 Java 程序:Hello World](docs/overview/hello-world.md)
### **Java 基础语法**
- [一网打尽 Java 的那些关键字](docs/core-grammar/java-keywords.md)
- [Java 数据类型有哪些](docs/core-grammar/java-data-type.md)
- [流程控制语句有哪些?图解版](docs/control/java-control.md)
- [Java 运算符有哪些?](docs/core-grammar/java-operator.md)
- [Java 注释:程序的注解](docs/overview/javadoc.md)
- [技术大佬的必备素质:命名优雅](docs/core-grammar/java-naming.md)
- [基本数据类型](docs/basic-grammar/basic-data-type.md)
- [流程控制](docs/basic-grammar/flow-control.md)
- [运算符](docs/basic-grammar/operator.md)
- [注释:代码的最强辅助](docs/basic-grammar/javadoc.md)
### **面向对象**
- [对象和类的相爱相杀](docs/object-class/java-object-class.md)
- [Java 变量类型有哪些](docs/core-grammar/java-var.md)
- [方法:我负责程序的行为](docs/object-class/java-method.md)
- [构造方法:对象初始化的必经之路](docs/object-class/java-construct.md)
- [代码初始化块:让我先走一步](docs/object-class/code-init.md)
- [抽象类:子类复用的基石](docs/object-class/java-abstract.md)
- [接口:抽象的另外一种表现形式](docs/object-class/java-interface.md)
### **关键字详解**
- [什么是对象?什么是类](docs/oo/object-class.md)
- [变量](docs/oo/var.md)
- [方法](docs/oo/method.md)
- [构造方法](docs/oo/construct.md)
- [代码初始化块](docs/oo/code-init.md)
- [抽象类](docs/oo/java-abstract.md)
- [接口](docs/oo/interface.md)
- [static 关键字](docs/oo/static.md)
- [this 和 super 关键字](docs/oo/this-super.md)
- [final 关键字](docs/oo/final.md)
- [instanceof 关键字](docs/oo/instanceof.md)
- [学妹必须学会的 static 关键字](docs/keywords/java-static.md)
- [学弟必须掌握的 this 和 super ](docs/keywords/java-this.md)
- [再见了,我的 final 关键字](docs/keywords/java-final.md)
- [判断对象的类型:instanceof 关键字](docs/keywords/java-instanceof.md)
### **字符串**
- [从源码的角度来看字符串的不可变性](docs/string/source.md)
- [学弟学妹都必须掌握的字符串常量池](docs/string/constant-pool.md)
- [深入浅出之美团技术团队解析过的 String.intern](docs/string/intern.md)
- [String 为什么是不可变的](docs/string/immutable.md)
- [字符串常量池](docs/string/constant-pool.md)
- [深入浅出 String.intern](docs/string/intern.md)
- [如何比较两个字符串是否相等](docs/string/equals.md)
- [如何拼接字符串](docs/string/join.md)
- [如何拆分字符串](docs/string/split.md)
### **数组**
- [最重要的数据结构之一](docs/array/gailan.md)
- [数组的专用工具类:java.util.Arrays](docs/array/arrays.md)
- [打印数组最优雅的方式:deepToString](docs/array/print.md)
### **泛型**
- [晦涩难懂的泛型](docs/generic/generic.md)
- [Java 不能实现真正泛型的原因是什么?](docs/generic/true-generic.md)
### **注解**
- [撸个注解有什么难的](docs/annotation/annotation.md)
### **枚举**
- [单例的最佳实现方式——枚举](docs/enum/enum.md)
- [数组](docs/array/array.md)
- [打印数组](docs/array/print.md)
### **集合框架**
### **反射**
- [初识集合框架](docs/collection/gailan.md)
- [时间复杂度](docs/collection/big-o.md)
- [ArrayList](docs/collection/arraylist.md)
- [LinkedList](docs/collection/linkedlist.md)
- [ArrayList 重拳出击,把 LinkedList 干翻在地](docs/collection/list-war-1.md)
- [被 ArrayList 锤了一拳后,LinkedList 很不服气](docs/collection/list-war-2.md)
- [海康威视一面:Iterator与Iterable有什么区别?](docs/collection/iterator-iterable.md)
- [为什么阿里巴巴强制不要在 foreach 里执行删除操作](docs/collection/fail-fast.md)
- [HashMap 的 hash 原理](docs/collection/hash.md)
- [HashMap 的扩容机制](docs/collection/hashmap-resize.md)
- [HashMap 的加载因子为什么是 0.75](docs/collection/hashmap-loadfactor.md)
- [为什么 HashMap 是线程不安全的?](docs/collection/hashmap-thread-nosafe.md)
- [HashMap 精选面试题(背诵版)](docs/collection/hashmap-interview.md)
- [深入理解 Java 的反射](docs/fanshe/fanshe.md)
### **异常**
### **异常处理**
- [异常处理机制](docs/exception/gailan.md)
- [try-catch-finally](docs/exception/try-catch-finally.md)
......@@ -116,32 +110,34 @@
- [try-with-resouces](docs/exception/try-with-resouces.md)
- [异常最佳实践](docs/exception/shijian.md)
### **常用工具类**
### **补充**
- [数组工具类:Arrays](docs/common-tool/arrays.md)
- [集合工具类:Collections](docs/common-tool/collections.md)
- [Java程序在编译期发生了什么](docs/overview/what-happen-when-javac.md)
- [必知必会的 Unicode:躲开锟斤拷](docs/core-points/unicode.md)
- [面试会考,Java 数据类型缓存池](docs/core-points/int-cache.md)
- [傻傻分不清:方法重载和方法重写](docs/core-points/override-overload.md)
- [Java 表示:我只有值传递,没有引用传递](docs/core-points/pass-by-value.md)
- [面试经典题目:浅拷贝与深拷贝有什么区别](docs/core-points/deep-copy.md)
- [自动拆箱与自动装箱,好玩](docs/core-points/box.md)
- [为什么重写 equals 时必须重写 hashCode 方法](docs/core-points/equals-hashcode.md)
### **加餐**
### **集合框架**
- [Java 中常用的 48 个关键字](docs/basic-extra-meal/48-keywords.md)
- [Java 命名约定](docs/basic-extra-meal/java-naming.md)
- [Java 默认的编码方式 Unicode](docs/basic-extra-meal/java-unicode.md)
- [new Integer(18) 与 Integer.valueOf(18) 有什么区别](docs/basic-extra-meal/int-cache.md)
- [自动拆箱与自动装箱](docs/basic-extra-meal/box.md)
- [方法重载和方法重写](docs/basic-extra-meal/override-overload.md)
- [Java 到底是值传递还是引用传递](docs/basic-extra-meal/pass-by-value.md)
- [浅拷贝与深拷贝](docs/basic-extra-meal/deep-copy.md)
- [为什么重写 equals 时必须重写 hashCode 方法](docs/basic-extra-meal/equals-hashcode.md)
- [注解](docs/basic-extra-meal/annotation.md)
- [枚举](docs/basic-extra-meal/enum.md)
- [深入理解 Java 中的反射](docs/basic-extra-meal/fanshe.md)
- [泛型](docs/basic-extra-meal/generic.md)
- [Java 不能实现真正泛型的原因是什么?](docs/basic-extra-meal/true-generic.md)
- [Java程序在编译期发生了什么](docs/basic-extra-meal/what-happen-when-javac.md)
- [初探集合框架](docs/collection/gailan.md)
- [时间复杂度](docs/collection/big-o.md)
- [ArrayList](docs/collection/arraylist.md)
- [LinkedList](docs/collection/linkedlist.md)
- [ArrayList 重拳出击,把 LinkedList 干翻在地](docs/collection/list-war-1.md)
- [被 ArrayList 锤了一拳后,LinkedList 很不服气](docs/collection/list-war-2.md)
- [HashMap 的 hash 方法原理是什么](docs/collection/hash.md)
- [HashMap 的扩容机制](docs/collection/hashmap-resize.md)
- [HashMap 的加载因子为什么是 0.75](docs/collection/hashmap-loadfactor.md)
- [为什么 HashMap 是线程不安全的?](docs/collection/hashmap-thread-nosafe.md)
- [HashMap 精选面试题(背诵版)](docs/collection/hashmap-interview.md)
## Java 进阶
### **Java 虚拟机**
- [JVM 是什么?](docs/jvm/what-is-jvm.md)
# :paw_prints: 联系作者
......
- **Java 概述**
**Java 概述**
- [什么是 Java](docs/overview/what-is-java.md)
- [Java 发展简史](docs/overview/java-history.md)
- [Java 为什么如此流行](docs/overview/why-java-popular.md)
- [环境准备:Intellij IDEA](docs/overview/idea.md)
- [第一个 Java 程序:Hello World](docs/overview/hello-world.md)
- [Java 的优势](docs/overview/java-advantage.md)
- [JDK 和 JRE 有什么区别](docs/overview/jdk-jre.md)
- [JVM 是什么](docs/overview/jvm.md)
- [安装集成开发环境 Intellij IDEA](docs/overview/idea.md)
- [第一个 Java 程序:Hello World](docs/overview/hello-world.md)
- **Java 基础语法**
- [一网打尽 Java 的那些关键字](docs/core-grammar/java-keywords.md)
- [Java 数据类型有哪些](docs/core-grammar/java-data-type.md)
- [流程控制语句有哪些?图解版](docs/control/java-control.md)
- [Java 运算符有哪些?](docs/core-grammar/java-operator.md)
- [Java 注释:程序的注解](docs/overview/javadoc.md)
- [技术大佬的必备素质:命名优雅](docs/core-grammar/java-naming.md)
**Java 基础语法**
- **面向对象**
- [基本数据类型](docs/basic-grammar/basic-data-type.md)
- [流程控制](docs/basic-grammar/flow-control.md)
- [运算符](docs/basic-grammar/operator.md)
- [注释:代码的最强辅助](docs/basic-grammar/javadoc.md)
- [对象和类的相爱相杀](docs/object-class/java-object-class.md)
- [Java 变量类型有哪些](docs/core-grammar/java-var.md)
- [方法:我负责程序的行为](docs/object-class/java-method.md)
- [构造方法:对象初始化的必经之路](docs/object-class/java-construct.md)
- [代码初始化块:让我先走一步](docs/object-class/code-init.md)
- [抽象类:子类复用的基石](docs/object-class/java-abstract.md)
- [接口:抽象的另外一种表现形式](docs/object-class/java-interface.md)
**面向对象**
- **关键字详解**
- [什么是对象?什么是类](docs/oo/object-class.md)
- [变量](docs/oo/var.md)
- [方法](docs/oo/method.md)
- [构造方法](docs/oo/construct.md)
- [代码初始化块](docs/oo/code-init.md)
- [抽象类](docs/oo/java-abstract.md)
- [接口](docs/oo/interface.md)
- [static 关键字](docs/oo/static.md)
- [this 和 super 关键字](docs/oo/this-super.md)
- [final 关键字](docs/oo/final.md)
- [instanceof 关键字](docs/oo/instanceof.md)
- [学妹必须学会的 static 关键字](docs/keywords/java-static.md)
- [学弟必须掌握的 this 和 super ](docs/keywords/java-this.md)
- [再见了,我的 final 关键字](docs/keywords/java-final.md)
- [判断对象的类型:instanceof 关键字](docs/keywords/java-instanceof.md)
- **字符串**
**字符串**
- [从源码的角度来看字符串的不可变性](docs/string/source.md)
- [学弟学妹都必须掌握的字符串常量池](docs/string/constant-pool.md)
- [深入浅出之美团技术团队解析过的 String.intern](docs/string/intern.md)
- [String 为什么是不可变的](docs/string/immutable.md)
- [字符串常量池](docs/string/constant-pool.md)
- [深入浅出 String.intern](docs/string/intern.md)
- [如何比较两个字符串是否相等](docs/string/equals.md)
- [如何拼接字符串](docs/string/join.md)
- [如何拆分字符串](docs/string/split.md)
- **数组**
- [最重要的数据结构之一](docs/array/gailan.md)
- [数组的专用工具类:java.util.Arrays](docs/array/arrays.md)
- [打印数组最优雅的方式:deepToString](docs/array/print.md)
- **泛型**
- [晦涩难懂的泛型](docs/generic/generic.md)
- [Java 不能实现真正泛型的原因是什么?](docs/generic/true-generic.md)
- **注解**
- [撸个注解有什么难的](docs/annotation/annotation.md)
**数组**
- **枚举**
- [数组](docs/array/array.md)
- [打印数组](docs/array/print.md)
- [单例的最佳实现方式——枚举](docs/enum/enum.md)
**集合框架**
- [初识集合框架](docs/collection/gailan.md)
- [时间复杂度](docs/collection/big-o.md)
- [ArrayList](docs/collection/arraylist.md)
- [LinkedList](docs/collection/linkedlist.md)
- [ArrayList 重拳出击,把 LinkedList 干翻在地](docs/collection/list-war-1.md)
- [被 ArrayList 锤了一拳后,LinkedList 很不服气](docs/collection/list-war-2.md)
- [海康威视一面:Iterator与Iterable有什么区别?](docs/collection/iterator-iterable.md)
- [为什么阿里巴巴强制不要在 foreach 里执行删除操作](docs/collection/fail-fast.md)
- [HashMap 的 hash 原理](docs/collection/hash.md)
- [HashMap 的扩容机制](docs/collection/hashmap-resize.md)
- [HashMap 的加载因子为什么是 0.75](docs/collection/hashmap-loadfactor.md)
- [为什么 HashMap 是线程不安全的?](docs/collection/hashmap-thread-nosafe.md)
- [HashMap 精选面试题(背诵版)](docs/collection/hashmap-interview.md)
- **反射**
- [深入理解 Java 的反射](docs/fanshe/fanshe.md)
- **异常**
**异常处理**
- [异常处理机制](docs/exception/gailan.md)
- [try-catch-finally](docs/exception/try-catch-finally.md)
......@@ -75,31 +69,33 @@
- [try-with-resouces](docs/exception/try-with-resouces.md)
- [异常最佳实践](docs/exception/shijian.md)
**常用工具类**
- **补充**
- [数组工具类:Arrays](docs/common-tool/arrays.md)
- [集合工具类:Collections](docs/common-tool/collections.md)
- [Java程序在编译期发生了什么](docs/overview/what-happen-when-javac.md)
- [必知必会的 Unicode:躲开锟斤拷](docs/core-points/unicode.md)
- [面试会考,Java 数据类型缓存池](docs/core-points/int-cache.md)
- [傻傻分不清:方法重载和方法重写](docs/core-points/override-overload.md)
- [Java 表示:我只有值传递,没有引用传递](docs/core-points/pass-by-value.md)
- [面试经典题目:浅拷贝与深拷贝有什么区别](docs/core-points/deep-copy.md)
- [自动拆箱与自动装箱,好玩](docs/core-points/box.md)
- [为什么重写 equals 时必须重写 hashCode 方法](docs/core-points/equals-hashcode.md)
**加餐**
- **集合框架**
- [Java 中常用的 48 个关键字](docs/basic-extra-meal/48-keywords.md)
- [Java 命名约定](docs/basic-extra-meal/java-naming.md)
- [Java 默认的编码方式 Unicode](docs/basic-extra-meal/java-unicode.md)
- [new Integer(18) 与 Integer.valueOf(18) 有什么区别](docs/basic-extra-meal/int-cache.md)
- [自动拆箱与自动装箱](docs/basic-extra-meal/box.md)
- [方法重载和方法重写](docs/basic-extra-meal/override-overload.md)
- [Java 到底是值传递还是引用传递](docs/basic-extra-meal/pass-by-value.md)
- [浅拷贝与深拷贝](docs/basic-extra-meal/deep-copy.md)
- [为什么重写 equals 时必须重写 hashCode 方法](docs/basic-extra-meal/equals-hashcode.md)
- [注解](docs/basic-extra-meal/annotation.md)
- [枚举](docs/basic-extra-meal/enum.md)
- [深入理解 Java 中的反射](docs/basic-extra-meal/fanshe.md)
- [泛型](docs/basic-extra-meal/generic.md)
- [Java 不能实现真正泛型的原因是什么?](docs/basic-extra-meal/true-generic.md)
- [Java程序在编译期发生了什么](docs/basic-extra-meal/what-happen-when-javac.md)
- [初探集合框架](docs/collection/gailan.md)
- [时间复杂度](docs/collection/big-o.md)
- [ArrayList](docs/collection/arraylist.md)
- [LinkedList](docs/collection/linkedlist.md)
- [ArrayList 重拳出击,把 LinkedList 干翻在地](docs/collection/list-war-1.md)
- [被 ArrayList 锤了一拳后,LinkedList 很不服气](docs/collection/list-war-2.md)
- [HashMap 的 hash 方法原理是什么](docs/collection/hash.md)
- [HashMap 的扩容机制](docs/collection/hashmap-resize.md)
- [HashMap 的加载因子为什么是 0.75](docs/collection/hashmap-loadfactor.md)
- [为什么 HashMap 是线程不安全的?](docs/collection/hashmap-thread-nosafe.md)
- [HashMap 精选面试题(背诵版)](docs/collection/hashmap-interview.md)
**Java 虚拟机**
- [JVM 是什么?](docs/jvm/what-is-jvm.md)
- **其他:**
......
## 数组概览
## 数组
“哥,我看你之前的文章里提到,ArrayList 的内部是用数组实现的,我就对数组非常感兴趣,想深入地了解一下,今天终于到这个环节了,好期待呀!”三妹的语气里显得很兴奋。
......
## 关键字
## Java 中常用的 48 个关键字
“二哥,就我之前学过的这些 Java 代码中,有 public、static、void、main 等等,它们应该都是关键字吧?”三妹的脸上泛着甜甜的笑容,我想她在学习 Java 方面已经变得越来越自信了。
......
## 撸个注解有什么难的
## 注解
“二哥,这节讲注解吗?”三妹问。
......
## 深入理解 Java 的反射
## 深入理解 Java 的反射
“二哥,什么是反射呀?”三妹开门见山地问。
......
## 面试会考,Java 数据类型缓存池
## new Integer(18) 与 Integer.valueOf(18) 有什么区别
“三妹,今天我们来补一个小的知识点:Java 数据类型缓存池。”我喝了一口枸杞泡的茶后对三妹说,“考你一个问题哈:`new Integer(18) 与 Integer.valueOf(18) ` 的区别是什么?”
......
## 不可不知的 Unicode
## Java 默认的编码方式 Unicode
“二哥,[上一篇](https://mp.weixin.qq.com/s/twim3w_dp5ctCigjLGIbFw)文章中提到了 Unicode,说 Java 中的
char 类型之所以占 2 个字节,是因为 Java 使用的是 Unicode 字符集而不是 ASCII 字符集,我有点迷,想了解一下,能细致给我说说吗?”
......
## 值传递与引用传递
## Java 到底是值传递还是引用传递
“哥,说说 Java 到底是值传递还是引用传递吧?”三妹一脸的困惑,看得出来她被这个问题折磨得不轻。
......
## 数据类型
## 基本数据类型
“二哥,[上一节](https://mp.weixin.qq.com/s/IgBpLGn0L1HZymgI4hWGVA)提到了 Java 变量的数据类型,是不是指定了类型就限定了变量的取值范围啊?”三妹吸了一口麦香可可奶茶后对我说。
......
## 流程控制语句
## 流程控制
“二哥,流程控制语句都有哪些呢?”三妹的脸上泛着甜甜的笑容,她开始对接下来要学习的内容充满期待了,这正是我感到欣慰的地方。
......
## Java 中的注释
## 注释:代码的最强辅助
“二哥,Java 中的注释好像真没什么可讲的,我已经提前预习了,不过是单行注释,多行注释,还有文档注释。”三妹的脸上泛着甜甜的笑容,她竟然提前预习了接下来要学习的知识,有一种“士别三日,当刮目相看”的感觉。
......
## Java 运算符
## 运算符
“二哥,让我盲猜一下哈,运算符是不是指的就是加减乘除啊?”三妹的脸上泛着甜甜的笑容,我想她一定对提出的问题很有自信。
......
## 为什么阿里巴巴强制不要在 foreach 里执行删除操作
那天,小二去阿里面试,面试官老王一上来就甩给了他一道面试题:为什么阿里的 Java 开发手册里会强制不要在 foreach 里进行元素的删除操作?小二听完就面露喜色,因为两年前,也就是 2021 年,他在《Java 程序员进阶之路》专栏上的第 63 篇看到过这题😆。
*PS:star 这种事,只能求,不求没效果,铁子们,《Java 程序员进阶之路》在 GitHub 上已经收获了 417 枚星标,小伙伴们赶紧去点点了,冲 500 star!*
>https://github.com/itwanger/toBeBetterJavaer
-----
为了镇楼,先搬一段英文来解释一下 fail-fast。
>In systems design, a fail-fast system is one which immediately reports at its interface any condition that is likely to indicate a failure. Fail-fast systems are usually designed to stop normal operation rather than attempt to continue a possibly flawed process. Such designs often check the system's state at several points in an operation, so any failures can be detected early. The responsibility of a fail-fast module is detecting errors, then letting the next-highest level of the system handle them.
这段话的大致意思就是,fail-fast 是一种通用的系统设计思想,一旦检测到可能会发生错误,就立马抛出异常,程序将不再往下执行。
```java
public void test(Wanger wanger) {
if (wanger == null) {
throw new RuntimeException("wanger 不能为空");
}
System.out.println(wanger.toString());
}
```
一旦检测到 wanger 为 null,就立马抛出异常,让调用者来决定这种情况下该怎么处理,下一步 `wanger.toString()` 就不会执行了——避免更严重的错误出现。
很多时候,我们会把 fail-fast 归类为 Java 集合框架的一种错误检测机制,但其实 fail-fast 并不是 Java 集合框架特有的机制。
之所以我们把 fail-fast 放在集合框架篇里介绍,是因为问题比较容易再现。
```java
List<String> list = new ArrayList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("一个文章真特么有趣的程序员");
for (String str : list) {
if ("沉默王二".equals(str)) {
list.remove(str);
}
}
System.out.println(list);
```
这段代码看起来没有任何问题,但运行起来就报错了。
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/collection/fail-fast-01.png)
根据错误的堆栈信息,我们可以定位到 ArrayList 的第 901 行代码。
```java
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
```
也就是说,remove 的时候触发执行了 `checkForComodification` 方法,该方法对 modCount 和 expectedModCount 进行了比较,发现两者不等,就抛出了 `ConcurrentModificationException` 异常。
为什么会执行 `checkForComodification` 方法呢?
是因为 for-each 本质上是个语法糖,底层是通过[迭代器 Iterator](戳链接🔗,详细了解下) 配合 while 循环实现的,来看一下反编译后的字节码。
```java
List<String> list = new ArrayList();
list.add("沉默王二");
list.add("沉默王三");
list.add("一个文章真特么有趣的程序员");
Iterator var2 = list.iterator();
while(var2.hasNext()) {
String str = (String)var2.next();
if ("沉默王二".equals(str)) {
list.remove(str);
}
}
System.out.println(list);
```
来看一下 ArrayList 的 iterator 方法吧:
```java
public Iterator<E> iterator() {
return new Itr();
}
```
内部类 Itr 实现了 Iterator 接口。
```java
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
}
```
也就是说 `new Itr()` 的时候 expectedModCount 被赋值为 modCount,而 modCount 是 List 的一个成员变量,表示集合被修改的次数。由于 list 此前执行了 3 次 add 方法。
- add 方法调用 ensureCapacityInternal 方法
- ensureCapacityInternal 方法调用 ensureExplicitCapacity 方法
- ensureExplicitCapacity 方法中会执行 `modCount++`
所以 modCount 的值在经过三次 add 后为 3,于是 `new Itr()` 后 expectedModCount 的值也为 3。
执行第一次循环时,发现“沉默王二”等于 str,于是执行 `list.remove(str)`
- remove 方法调用 fastRemove 方法
- fastRemove 方法中会执行 `modCount++`
```java
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
```
modCount 的值变成了 4。
执行第二次循环时,会执行 Itr 的 next 方法(`String str = (String) var3.next();`),next 方法就会调用 `checkForComodification` 方法,此时 expectedModCount 为 3,modCount 为 4,就只好抛出 ConcurrentModificationException 异常了。
那其实在阿里巴巴的 Java 开发手册里也提到了,不要在 for-each 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式。
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/collection/fail-fast-02.png)
那原因其实就是我们上面分析的这些,出于 fail-fast 保护机制。
**那该如何正确地删除元素呢**
**1)remove 后 break**
```java
List<String> list = new ArrayList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("一个文章真特么有趣的程序员");
for (String str : list) {
if ("沉默王二".equals(str)) {
list.remove(str);
break;
}
}
```
break 后循环就不再遍历了,意味着 Iterator 的 next 方法不再执行了,也就意味着 `checkForComodification` 方法不再执行了,所以异常也就不会抛出了。
但是呢,当 List 中有重复元素要删除的时候,break 就不合适了。
**2)for 循环**
```java
List<String> list = new ArrayList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("一个文章真特么有趣的程序员");
for (int i = 0, n = list.size(); i < n; i++) {
String str = list.get(i);
if ("沉默王二".equals(str)) {
list.remove(str);
}
}
```
for 循环虽然可以避开 fail-fast 保护机制,也就说 remove 元素后不再抛出异常;但是呢,这段程序在原则上是有问题的。为什么呢?
第一次循环的时候,i 为 0,`list.size()` 为 3,当执行完 remove 方法后,i 为 1,`list.size()` 却变成了 2,因为 list 的大小在 remove 后发生了变化,也就意味着“沉默王三”这个元素被跳过了。能明白吗?
remove 之前 `list.get(1)` 为“沉默王三”;但 remove 之后 `list.get(1)` 变成了“一个文章真特么有趣的程序员”,而 `list.get(0)` 变成了“沉默王三”。
**3)使用 Iterator**
```java
List<String> list = new ArrayList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("一个文章真特么有趣的程序员");
Iterator<String> itr = list.iterator();
while (itr.hasNext()) {
String str = itr.next();
if ("沉默王二".equals(str)) {
itr.remove();
}
}
```
为什么使用 Iterator 的 remove 方法就可以避开 fail-fast 保护机制呢?看一下 remove 的源码就明白了。
```java
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
```
删除完会执行 `expectedModCount = modCount`,保证了 expectedModCount 与 modCount 的同步。
-----
简单地总结一下,fail-fast 是一种保护机制,可以通过 for-each 循环删除集合的元素的方式验证这种保护机制。
那也就是说,for-each 本质上是一种语法糖,遍历集合时很方面,但并不适合拿来操作集合中的元素(增删)。
\ No newline at end of file
## 初集合框架
## 初集合框架
眼瞅着三妹的王者荣耀杀得正嗨,我趁机喊到:“别打了,三妹,我们来一起学习 Java 的集合框架吧。”
......
## 海康威视一面:Iterator与Iterable有什么区别?
那天,小二去海康威视面试,面试官老王一上来就甩给了他一道面试题:请问 Iterator与Iterable有什么区别?小二差点笑出声,因为一年前,也就是 2021 年,他在《Java 程序员进阶之路》专栏上的第 62 篇看到过这题😆。
*PS:星标这种事,只能求,不求没效果,come on。《Java 程序员进阶之路》在 GitHub 上已经收获了 408 枚星标,小伙伴们赶紧去点点了,冲 500!*
>https://github.com/itwanger/toBeBetterJavaer
-----
在 Java 中,我们对 List 进行遍历的时候,主要有这么三种方式。
第一种:for 循环。
```java
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + ",");
}
```
第二种:迭代器。
```java
Iterator it = list.iterator();
while (it.hasNext()) {
System.out.print(it.next() + ",");
}
```
第三种:for-each。
```java
for (String str : list) {
System.out.print(str + ",");
}
```
第一种我们略过,第二种用的是 Iterator,第三种看起来是 for-each,其实背后也是 Iterator,看一下反编译后的代码就明白了。
```java
Iterator var3 = list.iterator();
while(var3.hasNext()) {
String str = (String)var3.next();
System.out.print(str + ",");
}
```
for-each 只不过是个语法糖,让我们在遍历 List 的时候代码更简洁明了。
Iterator 是个接口,JDK 1.2 的时候就有了,用来改进 Enumeration:
- 允许删除元素(增加了 remove 方法)
- 优化了方法名(Enumeration 中是 hasMoreElements 和 nextElement,不简洁)
来看一下 Iterator 的源码:
```java
public interface Iterator<E> {
// 判断集合中是否存在下一个对象
boolean hasNext();
// 返回集合中的下一个对象,并将访问指针移动一位
E next();
// 删除集合中调用next()方法返回的对象
default void remove() {
throw new UnsupportedOperationException("remove");
}
}
```
JDK 1.8 时,Iterable 接口中新增了 forEach 方法:
```java
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
```
它对 Iterable 的每个元素执行给定操作,具体指定的操作需要自己写Consumer接口通过accept方法回调出来。
```java
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
list.forEach(integer -> System.out.println(integer));
```
写得更浅显易懂点,就是:
```java
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
list.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});
```
如果我们仔细观察ArrayList 或者 LinkedList 的“户口本”就会发现,并没有直接找到 Iterator 的影子。
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/collection/iterator-iterable-01.png)
反而找到了 Iterable!
```java
public interface Iterable<T> {
Iterator<T> iterator();
}
```
也就是说,List 的关系图谱中并没有直接使用 Iterator,而是使用 Iterable 做了过渡。
回头再来看一下第二种遍历 List 的方式。
```java
Iterator it = list.iterator();
while (it.hasNext()) {
}
```
发现刚好呼应上了。拿 ArrayList 来说吧,它重写了 Iterable 接口的 iterator 方法:
```java
public Iterator<E> iterator() {
return new Itr();
}
```
返回的对象 Itr 是个内部类,实现了 Iterator 接口,并且按照自己的方式重写了 hasNext、next、remove 等方法。
```java
private class Itr implements Iterator<E> {
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
Object[] elementData = ArrayList.this.elementData;
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
```
那可能有些小伙伴会问:为什么不直接将 Iterator 中的核心方法 hasNext、next 放到 Iterable 接口中呢?直接像下面这样使用不是更方便?
```java
Iterable it = list.iterator();
while (it.hasNext()) {
}
```
从英文单词的后缀语法上来看,(Iterable)able 表示这个 List 是支持迭代的,而 (Iterator)tor 表示这个 List 是如何迭代的。
支持迭代与具体怎么迭代显然不能混在一起,否则就乱的一笔。还是各司其职的好。
想一下,如果把 Iterator 和 Iterable 合并,for-each 这种遍历 List 的方式是不是就不好办了?
原则上,只要一个 List 实现了 Iterable 接口,那么它就可以使用 for-each 这种方式来遍历,那具体该怎么遍历,还是要看它自己是怎么实现 Iterator 接口的。
Map 就没办法直接使用 for-each,因为 Map 没有实现 Iterable 接口,只有通过 `map.entrySet()``map.keySet()``map.values()` 这种返回一个 Collection 的方式才能 使用 for-each。
如果我们仔细研究 LinkedList 的源码就会发现,LinkedList 并没有直接重写 Iterable 接口的 iterator 方法,而是由它的父类 AbstractSequentialList 来完成。
```java
public Iterator<E> iterator() {
return listIterator();
}
```
LinkedList 重写了 listIterator 方法:
```java
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
```
这里我们发现了一个新的迭代器 ListIterator,它继承了 Iterator 接口,在遍历List 时可以从任意下标开始遍历,而且支持双向遍历。
```java
public interface ListIterator<E> extends Iterator<E> {
boolean hasNext();
E next();
boolean hasPrevious();
E previous();
}
```
我们知道,集合(Collection)不仅有 List,还有 Map 和 Set,那 Iterator 不仅支持 List,还支持 Set,但 ListIterator 就只支持 List。
那可能有些小伙伴会问:为什么不直接让 List 实现 Iterator 接口,而是要用内部类来实现呢?
这是因为有些 List 可能会有多种遍历方式,比如说 LinkedList,除了支持正序的遍历方式,还支持逆序的遍历方式——DescendingIterator:
```java
private class DescendingIterator implements Iterator<E> {
private final ListItr itr = new ListItr(size());
public boolean hasNext() {
return itr.hasPrevious();
}
public E next() {
return itr.previous();
}
public void remove() {
itr.remove();
}
}
```
可以看得到,DescendingIterator 刚好利用了 ListIterator 向前遍历的方式。可以通过以下的方式来使用:
```java
Iterator it = list.descendingIterator();
while (it.hasNext()) {
}
```
-----
好了,关于Iterator与Iterable我们就先聊这么多,总结两点:
- 学会深入思考,一点点抽丝剥茧,多想想为什么这样实现,很多问题没有自己想象中的那么复杂。
- 遇到疑惑不放弃,这是提升自己最好的机会,遇到某个疑难的点,解决的过程中会挖掘出很多相关的东西。
\ No newline at end of file
## 数组专用工具类
## 数组工具类:Arrays
“哥,数组专用工具类是专门用来操作数组的吗?比如说创建数组、数组排序、数组检索等等。”三妹的提问其实已经把答案说了出来。
......
## 集合工具类:Collections
Collections 是 JDK 提供的一个工具类,位于 java.util 包下,提供了一系列的静态方法,方便我们对集合进行各种骚操作,算是集合框架的一个大管家。
还记得我们前面讲过的 [Arrays 工具类](https://mp.weixin.qq.com/s/9dYmKXEErZbyPJ_GxwWYug)吗?可以回去温习下。
Collections 的用法很简单,在 Intellij IDEA 中敲完 `Collections.` 之后就可以看到它提供的方法了,大致看一下方法名和参数就能知道这个方法是干嘛的。
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/common-tool/collections-01.png)
为了节省大家的学习时间,我将这些方法做了一些分类,并列举了一些简单的例子。
### 01、排序操作
- `reverse(List list)`:反转顺序
- `shuffle(List list)`:洗牌,将顺序打乱
- `sort(List list)`:自然升序
- `sort(List list, Comparator c)`:按照自定义的比较器排序
- `swap(List list, int i, int j)`:将 i 和 j 位置的元素交换位置
来看例子:
```java
List<String> list = new ArrayList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("沉默王四");
list.add("沉默王五");
list.add("沉默王六");
System.out.println("原始顺序:" + list);
// 反转
Collections.reverse(list);
System.out.println("反转后:" + list);
// 洗牌
Collections.shuffle(list);
System.out.println("洗牌后:" + list);
// 自然升序
Collections.sort(list);
System.out.println("自然升序后:" + list);
// 交换
Collections.swap(list, 2,4);
System.out.println("交换后:" + list);
```
输出后:
```
原始顺序:[沉默王二, 沉默王三, 沉默王四, 沉默王五, 沉默王六]
反转后:[沉默王六, 沉默王五, 沉默王四, 沉默王三, 沉默王二]
洗牌后:[沉默王五, 沉默王二, 沉默王六, 沉默王三, 沉默王四]
自然升序后:[沉默王三, 沉默王二, 沉默王五, 沉默王六, 沉默王四]
交换后:[沉默王三, 沉默王二, 沉默王四, 沉默王六, 沉默王五]
```
### 02、查找操作
- `binarySearch(List list, Object key)`:二分查找法,前提是 List 已经排序过了
- `max(Collection coll)`:返回最大元素
- `max(Collection coll, Comparator comp)`:根据自定义比较器,返回最大元素
- `min(Collection coll)`:返回最小元素
- `min(Collection coll, Comparator comp)`:根据自定义比较器,返回最小元素
- `fill(List list, Object obj)`:使用指定对象填充
- `frequency(Collection c, Object o)`:返回指定对象出现的次数
来看例子:
```java
System.out.println("最大元素:" + Collections.max(list));
System.out.println("最小元素:" + Collections.min(list));
System.out.println("出现的次数:" + Collections.frequency(list, "沉默王二"));
// 没有排序直接调用二分查找,结果是不确定的
System.out.println("排序前的二分查找结果:" + Collections.binarySearch(list, "沉默王二"));
Collections.sort(list);
// 排序后,查找结果和预期一致
System.out.println("排序后的二分查找结果:" + Collections.binarySearch(list, "沉默王二"));
Collections.fill(list, "沉默王八");
System.out.println("填充后的结果:" + list);
```
输出后:
```
原始顺序:[沉默王二, 沉默王三, 沉默王四, 沉默王五, 沉默王六]
最大元素:沉默王四
最小元素:沉默王三
出现的次数:1
排序前的二分查找结果:0
排序后的二分查找结果:1
填充后的结果:[沉默王八, 沉默王八, 沉默王八, 沉默王八, 沉默王八]
```
### 03、同步控制
[HashMap 是线程不安全](https://mp.weixin.qq.com/s/qk_neCdzM3aB6pVWVTHhNw)的,这个我们前面讲到了。那其实 ArrayList 也是线程不安全的,没法在多线程环境下使用,那 Collections 工具类中提供了多个 synchronizedXxx 方法,这些方法会返回一个同步的对象,从而解决多线程中访问集合时的安全问题。
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/common-tool/collections-02.png)
使用起来也非常的简单:
```java
SynchronizedList synchronizedList = Collections.synchronizedList(list);
```
看一眼 SynchronizedList 的源码就明白了,不过是在方法里面使用 synchronized 关键字加了一层锁而已。
```java
static class SynchronizedList<E>
extends SynchronizedCollection<E>
implements List<E> {
private static final long serialVersionUID = -7754090372962971524L;
final List<E> list;
SynchronizedList(List<E> list) {
super(list);
this.list = list;
}
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}
}
```
那这样的话,其实效率和那些直接在方法上加 synchronized 关键字的 Vector、Hashtable 差不多(JDK 1.0 时期就有了),而这些集合类基本上已经废弃了,几乎不怎么用。
```java
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
public synchronized E remove(int index) {
modCount++;
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
int numMoved = elementCount - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--elementCount] = null; // Let gc do its work
return oldValue;
}
}
```
正确的做法是使用并发包下的 CopyOnWriteArrayList、ConcurrentHashMap。这些我们放到并发编程时再讲。
### 04、不可变集合
- `emptyXxx()`:制造一个空的不可变集合
- `singletonXxx()`:制造一个只有一个元素的不可变集合
- `unmodifiableXxx()`:为指定集合制作一个不可变集合
举个例子:
```java
List emptyList = Collections.emptyList();
emptyList.add("非空");
System.out.println(emptyList);
```
这段代码在执行的时候就抛出错误了。
```
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
at com.itwanger.s64.Demo.main(Demo.java:61)
```
这是因为 `Collections.emptyList()` 会返回一个 Collections 的内部类 EmptyList,而 EmptyList 并没有重写父类 AbstractList 的 `add(int index, E element)` 方法,所以执行的时候就抛出了不支持该操作的 UnsupportedOperationException 了。
这是从分析 add 方法源码得出的原因。除此之外,emptyList 方法是 final 的,返回的 EMPTY_LIST 也是 final 的,种种迹象表明 emptyList 返回的就是不可变对象,没法进行增伤改查。
```java
public static final <T> List<T> emptyList() {
return (List<T>) EMPTY_LIST;
}
public static final List EMPTY_LIST = new EmptyList<>();
```
### 05、其他
还有两个方法比较常用:
- `addAll(Collection<? super T> c, T... elements)`,往集合中添加元素
- `disjoint(Collection<?> c1, Collection<?> c2)`,判断两个集合是否没有交集
举个例子:
```java
List<String> allList = new ArrayList<>();
Collections.addAll(allList, "沉默王九","沉默王十","沉默王二");
System.out.println("addAll 后:" + allList);
System.out.println("是否没有交集:" + (Collections.disjoint(list, allList) ? "是" : "否"));
```
输出后:
```
原始顺序:[沉默王二, 沉默王三, 沉默王四, 沉默王五, 沉默王六]
addAll 后:[沉默王九, 沉默王十, 沉默王二]
是否没有交集:否
```
整体上,Collections 工具类作为集合框架的大管家,提供了一些非常便利的方法供我们调用,也非常容易掌握,没什么难点,看看方法的注释就能大致明白干嘛的。
不过,工具就放在那里,用是一回事,为什么要这么用就是另外一回事了。能不能提高自己的编码水平,很大程度上取决于你到底有没有去钻一钻源码,看这些设计 JDK 的大师们是如何写代码的,学会一招半式,在工作当中还是能很快脱颖而出的。
恐怕 JDK 的设计者是这个世界上最好的老师了,文档写得不能再详细了,代码写得不能再优雅了,基本上都达到了性能上的极致。
可能有人会说,工具类没什么鸟用,不过是调用下方法而已,但这就大错特错了:如果要你来写,你能写出来 Collections 这样一个工具类吗?
这才是高手要思考的一个问题。
## 每个程序员都应该了解的 Java 虚拟机
## JVM 是什么?
......
## Java 构造方法
## 构造方法
我对三妹说,“[上一节](https://mp.weixin.qq.com/s/L4jAgQPurGZPvWu8ECtBpA)学了 Java 中的方法,接着学构造方法的话,难度就小很多了。”
......
## instanceof
## instanceof 关键字
instanceof 操作符的用法其实很简单:
instanceof 关键字的用法其实很简单:
```java
(object) instanceof (type)
......
## Java 方法
## 方法
“二哥,[上一节](https://mp.weixin.qq.com/s/UExby8GP3kSacCXliQw8pQ)学了对象和类,这一节我们学什么呢?”三妹满是期待的问我。
......
## 对象和
## 什么是对象?什么是
“二哥,我那天在图书馆复习[上一节](https://mp.weixin.qq.com/s/WzMEOEdzI0fFwBQ4s0S-0g)你讲的内容,刚好碰见一个学长,他问我有没有‘对象’,我说还没有啊。结果你猜他说什么,‘要不要我给你 new 一个啊?’我当时就懵了,new 是啥意思啊,二哥?”三妹满是疑惑的问我。
......
## Java 变量
## 变量
“二哥,听说 Java 变量在以后的日子里经常用,能不能提前给我透露透露?”三妹咪了一口麦香可可奶茶后对我说。
......
## 编写 Hello World 程序
## 第一个 Java 程序:Hello World
可以通过 Intellij IDEA 来编写代码,也可以使用在线编辑器来完成。
......
## 安装 Intellij IDEA
## 安装集成开发环境 Intellij IDEA
IntelliJ IDEA 简称 IDEA,是业界公认为最好的 Java 集成开发工具,尤其是在代码自动提示、代码重构、代码版本管理、单元测试、代码分析等方面有着亮眼的发挥。
......
## Java 为什么如此流行?
## Java 的优势
尽管 Java 已经 25 岁了,但仍然“宝刀未老”。在 Stack Overflow 2019 年流行编程语言调查报告中,Java 位居第 5 位,有 41% 的受调开发者认为 Java 仍然是一门受欢迎的编程语言。
......
## Java 程序在编译期发生了什么
## Java程序在编译期发生了什么
“二哥,看了上一篇 [Hello World](https://mp.weixin.qq.com/s/191I_2CVOxVuyfLVtb4jhg) 的程序后,我很好奇,它是怎么在 Run 面板里打印出‘三妹,少看手机少打游戏,好好学,美美哒’呢?”三妹咪了一口麦香可可奶茶后对我说。
......
## Java 是什么
## 什么是 Java?
“二哥,到底什么是 Java?给我说说呗。”
......
## 字符串源码分析
## String 为什么是不可变的
我正坐在沙发上津津有味地读刘欣大佬的《码农翻身》——Java 帝国这一章,门铃响了。起身打开门一看,是三妹,她从学校回来了。
......
## intern
## 深入浅出 String.intern
“哥,你发给我的那篇文章我看了,结果直接把我给看得不想学 Java 了!”三妹气冲冲地说。
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册