From fb2058393c67945eed7be4751c82a1d22732a2d7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=86=AF=E5=BA=86?=
Date: Wed, 26 Aug 2020 09:22:38 +0800
Subject: [PATCH 01/84] =?UTF-8?q?=E7=9F=AB=E6=AD=A3=E5=85=B3=E4=BA=8E?=
=?UTF-8?q?=E8=8C=83=E5=9E=8B=E4=BB=8B=E7=BB=8D=E4=B8=ADJava=E5=AF=B9?=
=?UTF-8?q?=E8=B1=A1=E5=86=99=E6=B3=95=EF=BC=8C=E5=85=B6=E4=B8=ADList?=
=?UTF-8?q?=E6=98=AF=E9=94=99=E8=AF=AF=E7=9A=84=EF=BC=8C=E9=9B=86=E5=90=88?=
=?UTF-8?q?=E4=B8=AD=E4=B8=8D=E5=8F=AF=E4=BB=A5=E6=94=BE=E5=9F=BA=E6=9C=AC?=
=?UTF-8?q?=E7=B1=BB=E5=9E=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/basics/java-basic/type-erasue.md | 25 ++++++++++++-------------
1 file changed, 12 insertions(+), 13 deletions(-)
diff --git a/docs/basics/java-basic/type-erasue.md b/docs/basics/java-basic/type-erasue.md
index f1f40ea4..50fea639 100644
--- a/docs/basics/java-basic/type-erasue.md
+++ b/docs/basics/java-basic/type-erasue.md
@@ -4,11 +4,11 @@
通常情况下,一个编译器处理泛型有两种方式:
-1\.`Code specialization`。在实例化一个泛型类或泛型方法时都产生一份新的目标代码(字节码or二进制代码)。例如,针对一个泛型`list`,可能需要 针对`string`,`integer`,`float`产生三份目标代码。
+1\.`Code specialization`。在实例化一个泛型类或泛型方法时都产生一份新的目标代码(字节码or二进制代码)。例如,针对一个泛型List`,可能需要 针对`String`,`Integer`,`Float`产生三份目标代码。
2\.`Code sharing`。对每个泛型类只生成唯一的一份目标代码;该泛型类的所有实例都映射到这份目标代码上,在需要的时候执行类型检查和类型转换。
-**C++**中的模板(`template`)是典型的`Code specialization`实现。**C++**编译器会为每一个泛型类实例生成一份执行代码。执行代码中`integer list`和`string list`是两种不同的类型。这样会导致**代码膨胀(code bloat)**。 **C#**里面泛型无论在程序源码中、编译后的`IL`中(Intermediate Language,中间语言,这时候泛型是一个占位符)或是运行期的CLR中都是切实存在的,`List`与`List`就是两个不同的类型,它们在系统运行期生成,有自己的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型被称为`真实泛型`。 **Java**语言中的泛型则不一样,它只在程序源码中存在,在编译后的字节码文件中,就已经被替换为原来的原生类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此对于运行期的Java语言来说,`ArrayList`与`ArrayList`就是同一个类。所以说泛型技术实际上是Java语言的一颗语法糖,Java语言中的泛型实现方法称为**类型擦除**,基于这种方法实现的泛型被称为`伪泛型`。
+**C++**中的模板(`template`)是典型的`Code specialization`实现。**C++**编译器会为每一个泛型类实例生成一份执行代码。执行代码中`Integer List`和`String List`是两种不同的类型。这样会导致**代码膨胀(code bloat)**。 **C#**里面泛型无论在程序源码中、编译后的`IL`中(Intermediate Language,中间语言,这时候泛型是一个占位符)或是运行期的CLR中都是切实存在的,`List`与`List`就是两个不同的类型,它们在系统运行期生成,有自己的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型被称为`真实泛型`。 **Java**语言中的泛型则不一样,它只在程序源码中存在,在编译后的字节码文件中,就已经被替换为原来的原生类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此对于运行期的Java语言来说,`ArrayList`与`ArrayList`就是同一个类。所以说泛型技术实际上是Java语言的一颗语法糖,Java语言中的泛型实现方法称为**类型擦除**,基于这种方法实现的泛型被称为`伪泛型`。
`C++`和`C#`是使用`Code specialization`的处理机制,前面提到,他有一个缺点,那就是**会导致代码膨胀**。另外一个弊端是在引用类型系统中,浪费空间,因为引用类型集合中元素本质上都是一个指针。没必要为每个类型都产生一份执行代码。而这也是Java编译器中采用`Code sharing`方式处理泛型的主要原因。
@@ -35,7 +35,7 @@
System.out.println(map.get("name"));
System.out.println(map.get("age"));
}
-
+
**反编译后的code 1:**
@@ -46,7 +46,7 @@
System.out.println((String) map.get("name"));
System.out.println((String) map.get("age"));
}
-
+
我们发现泛型都不见了,程序又变回了Java泛型出现之前的写法,泛型类型都变回了原生类型,
@@ -73,7 +73,7 @@
return this.value - that.value;
}
}
-
+
**反编译后的code 2:**
@@ -102,7 +102,6 @@
}
private byte value;
}
-
* * *
@@ -120,7 +119,7 @@
return w;
}
}
-
+
**反编译后的code 3:**
@@ -142,7 +141,7 @@
return w;
}
}
-
+
第2个泛型类`Comparable `擦除后 A被替换为最左边界`Object`。`Comparable`的类型参数`NumericValue`被擦除掉,但是这直 接导致`NumericValue`没有实现接口`Comparable的compareTo(Object that)`方法,于是编译器充当好人,添加了一个**桥接方法**。 第3个示例中限定了类型参数的边界`>A`,A必须为`Comparable`的子类,按照类型擦除的过程,先讲所有的类型参数 ti换为最左边界`Comparable`,然后去掉参数类型`A`,得到最终的擦除后结果。
@@ -162,13 +161,13 @@
System.out.println("invoke method(List list)");
}
}
-
+
上面这段代码,有两个重载的函数,因为他们的参数类型不同,一个是`List`另一个是`List` ,但是,这段代码是编译通不过的。因为我们前面讲过,参数`List`和`List`编译之后都被擦除了,变成了一样的原生类型List,擦除动作导致这两个方法的特征签名变得一模一样。
**二、当泛型遇到catch:**
-如果我们自定义了一个泛型异常类GenericException,那么,不要尝试用多个catch取匹配不同的异常类型,例如你想要分别捕获GenericException、GenericException,这也是有问题的。
+如果我们自定义了一个泛型异常类GenericException,那么,不要尝试用多个catch取匹配不同的异常类型,例如你想要分别捕获GenericException、GenericException,这也是有问题的。
三、当泛型内包含静态变量
@@ -185,7 +184,7 @@
public static int var=0;
public void nothing(T x){}
}
-
+
答案是——2!由于经过类型擦除,所有的泛型类实例都关联到同一份字节码上,泛型类的所有静态变量是共享的。
@@ -195,5 +194,5 @@
1\.虚拟机中没有泛型,只有普通类和普通方法,所有泛型类的类型参数在编译时都会被擦除,泛型类并没有自己独有的Class类对象。比如并不存在`List`.class或是`List.class`,而只有`List.class`。 2.创建泛型对象时请指明类型,让编译器尽早的做参数检查(**Effective Java,第23条:请不要在新代码中使用原生态类型**) 3.不要忽略编译器的警告信息,那意味着潜在的`ClassCastException`等着你。 4.静态变量是被泛型类的所有实例所共享的。对于声明为`MyClass`的类,访问其中的静态变量的方法仍然是 `MyClass.myStaticVar`。不管是通过`new MyClass`还是`new MyClass`创建的对象,都是共享一个静态变量。 5.泛型的类型参数不能用在`Java`异常处理的`catch`语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除,`JVM`是无法区分两个异常类型`MyException`和`MyException`的。对于`JVM`来说,它们都是 `MyException`类型的。也就无法执行与异常对应的`catch`语句。
- [1]: http://docs.oracle.com/javase/tutorial/java/generics/erasure.html
- [2]: /archives/255
\ No newline at end of file
+[1]: http://docs.oracle.com/javase/tutorial/java/generics/erasure.html
+[2]: /archives/255
\ No newline at end of file
From 26a0b8710ee76c329f7cbb0c5b95f63d607be1d4 Mon Sep 17 00:00:00 2001
From: lbs0912
Date: Wed, 7 Oct 2020 22:32:06 +0800
Subject: [PATCH 02/84] =?UTF-8?q?=E9=94=99=E5=88=AB=E5=AD=97=20fix?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/basics/concurrent-coding/volatile.md | 2 +-
docs/basics/java-basic/boxing-unboxing.md | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/basics/concurrent-coding/volatile.md b/docs/basics/concurrent-coding/volatile.md
index 6bf347f7..1a1a0ff5 100644
--- a/docs/basics/concurrent-coding/volatile.md
+++ b/docs/basics/concurrent-coding/volatile.md
@@ -46,7 +46,7 @@
我们在[再有人问你Java内存模型是什么,就把这篇文章发给他][1]中分析过:Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。所以,就可能出现线程1改了某个变量的值,但是线程2不可见的情况。
-前面的关于`volatile`的原理中介绍过了,Java中的`volatile`关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次是用之前都从主内存刷新。因此,可以使用`volatile`来保证多线程操作时变量的可见性。
+前面的关于`volatile`的原理中介绍过了,Java中的`volatile`关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次使用之前都从主内存刷新。因此,可以使用`volatile`来保证多线程操作时变量的可见性。
### volatile与有序性
diff --git a/docs/basics/java-basic/boxing-unboxing.md b/docs/basics/java-basic/boxing-unboxing.md
index 00d0aa36..84ae2fd8 100644
--- a/docs/basics/java-basic/boxing-unboxing.md
+++ b/docs/basics/java-basic/boxing-unboxing.md
@@ -282,7 +282,7 @@ Java SE 的自动拆装箱还提供了一个和缓存有关的功能,我们先
}
```
-我们普遍认为上面的两个判断的结果都是 false。虽然比较的值是相等的,但是由于比较的是对象,而对象的引用不一样,所以会认为两个 if 判断都是 false 的。在 Java 中,`==` 比较的是对象应用,而 `equals` 比较的是值。所以,在这个例子中,不同的对象有不同的引用,所以在进行比较的时候都将返回 false。奇怪的是,这里两个类似的 if 条件判断返回不同的布尔值。
+我们普遍认为上面的两个判断的结果都是 false。虽然比较的值是相等的,但是由于比较的是对象,而对象的引用不一样,所以会认为两个 if 判断都是 false 的。在 Java 中,`==` 比较的是对象引用,而 `equals` 比较的是值。所以,在这个例子中,不同的对象有不同的引用,所以在进行比较的时候都将返回 false。奇怪的是,这里两个类似的 if 条件判断返回不同的布尔值。
上面这段代码真正的输出结果:
From ecdddd0738f143aa36964b5f6404a37f830ee97a Mon Sep 17 00:00:00 2001
From: "hollis.zhl"
Date: Tue, 13 Oct 2020 11:05:49 +0800
Subject: [PATCH 03/84] =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/basics/java-basic/Wildcard-Character.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/basics/java-basic/Wildcard-Character.md b/docs/basics/java-basic/Wildcard-Character.md
index b9e2a1b0..3c173ac0 100644
--- a/docs/basics/java-basic/Wildcard-Character.md
+++ b/docs/basics/java-basic/Wildcard-Character.md
@@ -1,10 +1,10 @@
`限定通配符`对类型进⾏限制, 泛型中有两种限定通配符:
-表示类型的上界,格式为:<? extends T>,即类型必须为T类型或者T子类
-表示类型的下界,格式为:<? super T>,即类型必须为T类型或者T的父类
+表示类型的上界,格式为:`<? extends T>`,即类型必须为T类型或者T子类
+表示类型的下界,格式为:`<? super T>`,即类型必须为T类型或者T的父类
泛型类型必须⽤限定内的类型来进⾏初始化,否则会导致编译错误。
-`⾮限定通配符`表⽰可以⽤任意泛型类型来替代,类型为
\ No newline at end of file
+`⾮限定通配符`表⽰可以⽤任意泛型类型来替代,类型为``
\ No newline at end of file
From fbb3ce8cd821897fddf5fb7ce5f4d00c878d98f5 Mon Sep 17 00:00:00 2001
From: Hollis
Date: Sun, 25 Oct 2020 13:53:11 +0800
Subject: [PATCH 04/84] Update class-contant-pool.md
---
docs/basics/java-basic/class-contant-pool.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/basics/java-basic/class-contant-pool.md b/docs/basics/java-basic/class-contant-pool.md
index 781dce6c..29536833 100644
--- a/docs/basics/java-basic/class-contant-pool.md
+++ b/docs/basics/java-basic/class-contant-pool.md
@@ -77,7 +77,7 @@ Class常量池可以理解为是Class文件中的资源仓库。 Class文件中
### 字面量
-前面说过,运行时常量池中主要保存的是字面量和符号引用,那么到底什么字面量?
+前面说过,Class常量池中主要保存的是字面量和符号引用,那么到底什么字面量?
> 在计算机科学中,字面量(literal)是用于表达源代码中一个固定值的表示法(notation)。几乎所有计算机编程语言都具有对基本值的字面量表示,诸如:整数、浮点数以及字符串;而有很多也对布尔类型和字符类型的值也支持字面量表示;还有一些甚至对枚举类型的元素以及像数组、记录和对象等复合类型的值也支持字面量表示法。
@@ -127,4 +127,4 @@ Java代码在进行`Javac`编译的时候,并不像C和C++那样有“连接
[4]: http://www.hollischuang.com/archives/491
[5]: http://www.hollischuang.com/wp-content/uploads/2018/10/15401192359009.jpg
[6]: http://www.hollischuang.com/wp-content/uploads/2018/10/15401195127619.jpg
- [7]: https://blog.csdn.net/luanlouis/article/details/39960815
\ No newline at end of file
+ [7]: https://blog.csdn.net/luanlouis/article/details/39960815
From 8440deea3bfa5fa401d1f487ab6d69cebf604541 Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sat, 14 Nov 2020 15:56:41 +0800
Subject: [PATCH 05/84] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=94=A8=E6=B3=95?=
=?UTF-8?q?=E4=BE=8B=E5=AD=90=E4=B8=AD=E7=9A=84=E6=B3=A8=E9=87=8A=E7=AC=94?=
=?UTF-8?q?=E8=AF=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/basics/java-basic/replace-in-string.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/basics/java-basic/replace-in-string.md b/docs/basics/java-basic/replace-in-string.md
index d89f5651..4cb36dd1 100644
--- a/docs/basics/java-basic/replace-in-string.md
+++ b/docs/basics/java-basic/replace-in-string.md
@@ -20,7 +20,7 @@ replaceAll和replaceFirst的区别主要是替换的内容不同,replaceAll是
//文字替换(全部)
Pattern pattern = Pattern.compile("正则表达式");
Matcher matcher = pattern.matcher("正则表达式 Hello World,正则表达式 Hello World");
-//替换第一个符合正则的数据
+//替换所有符合正则的数据
System.out.println(matcher.replaceAll("Java"));
```
From 73a4e0af8c71a9696e130db17ab1179f310589ba Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 15 Nov 2020 16:11:08 +0800
Subject: [PATCH 06/84] =?UTF-8?q?=E7=AC=94=E8=AF=AF=E5=8B=98=E6=AD=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/basics/java-basic/switch-string.md | 4 ++--
docs/basics/java-basic/value-of-vs-to-string.md | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/basics/java-basic/switch-string.md b/docs/basics/java-basic/switch-string.md
index 1498aab7..deac11de 100644
--- a/docs/basics/java-basic/switch-string.md
+++ b/docs/basics/java-basic/switch-string.md
@@ -71,7 +71,7 @@ Java 7中,switch的参数可以是String类型了,这对我们来说是一
}
-编译后的代码如下: `public class switchDemoChar
+编译后的代码如下:
public class switchDemoChar
{
@@ -147,4 +147,4 @@ Java 7中,switch的参数可以是String类型了,这对我们来说是一
看到这个代码,你知道原来字符串的switch是通过`equals()`和`hashCode()`方法来实现的。**记住,switch中只能使用整型**,比如`byte`。`short`,`char`(ackii码是整型)以及`int`。还好`hashCode()`方法返回的是`int`,而不是`long`。通过这个很容易记住`hashCode`返回的是`int`这个事实。仔细看下可以发现,进行`switch`的实际是哈希值,然后通过使用equals方法比较进行安全检查,这个检查是必要的,因为哈希可能会发生碰撞。因此它的性能是不如使用枚举进行switch或者使用纯整数常量,但这也不是很差。因为Java编译器只增加了一个`equals`方法,如果你比较的是字符串字面量的话会非常快,比如”abc” ==”abc”。如果你把`hashCode()`方法的调用也考虑进来了,那么还会再多一次的调用开销,因为字符串一旦创建了,它就会把哈希值缓存起来。因此如果这个`switch`语句是用在一个循环里的,比如逐项处理某个值,或者游戏引擎循环地渲染屏幕,这里`hashCode()`方法的调用开销其实不会很大。
-好,以上就是关于switch对整型、字符型、和字符串型的支持的实现方式,总结一下我们可以发现,**其实switch只支持一种数据类型,那就是整型,其他数据类型都是转换成整型之后在使用switch的。**
+好,以上就是关于switch对整型、字符型、和字符串型的支持的实现方式,总结一下我们可以发现,**其实switch只支持一种数据类型,那就是整型,其他数据类型都是转换成整型之后再使用switch的。**
diff --git a/docs/basics/java-basic/value-of-vs-to-string.md b/docs/basics/java-basic/value-of-vs-to-string.md
index a3ed3fe4..9a59cfa9 100644
--- a/docs/basics/java-basic/value-of-vs-to-string.md
+++ b/docs/basics/java-basic/value-of-vs-to-string.md
@@ -1,4 +1,4 @@
-我们有三种方式将一个int类型的变量变成呢过String类型,那么他们有什么区别?
+我们有三种方式将一个int类型的变量变成一个String类型,那么他们有什么区别?
1.int i = 5;
2.String i1 = "" + i;
@@ -7,4 +7,4 @@
第三行和第四行没有任何区别,因为String.valueOf(i)也是调用Integer.toString(i)来实现的。
-第二行代码其实是String i1 = (new StringBuilder()).append(i).toString();,首先创建一个StringBuilder对象,然后再调用append方法,再调用toString方法。
\ No newline at end of file
+第二行代码其实是String i1 = (new StringBuilder()).append(i).toString();,首先创建一个StringBuilder对象,然后再调用append方法,再调用toString方法。
From 61f625404cba90792dfcc1fc7f21662a8041aff7 Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 15 Nov 2020 16:29:32 +0800
Subject: [PATCH 07/84] =?UTF-8?q?=E5=B8=B8=E9=87=8F=E6=B1=A0=E8=AE=A1?=
=?UTF-8?q?=E6=95=B0=E5=99=A8=E4=BB=8E1=E5=BC=80=E5=A7=8B=E8=AE=A1?=
=?UTF-8?q?=E6=95=B0=EF=BC=8C=E8=8C=83=E5=9B=B4=E6=98=AF[1,n)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/basics/java-basic/class-contant-pool.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/basics/java-basic/class-contant-pool.md b/docs/basics/java-basic/class-contant-pool.md
index 29536833..d55008c0 100644
--- a/docs/basics/java-basic/class-contant-pool.md
+++ b/docs/basics/java-basic/class-contant-pool.md
@@ -67,7 +67,7 @@ Class常量池可以理解为是Class文件中的资源仓库。 Class文件中
> 从上图中可以看到,反编译后的class文件常量池中共有16个常量。而Class文件中常量计数器的数值是0011,将该16进制数字转换成10进制的结果是17。
>
-> 原因是与Java的语言习惯不同,常量池计数器是从0开始而不是从1开始的,常量池的个数是10进制的17,这就代表了其中有16个常量,索引值范围为1-16。
+> 原因是与Java的语言习惯不同,常量池计数器是从1开始而不是从0开始的,常量池的个数是10进制的17,这就代表了其中有16个常量,索引值范围为1-16。
### 常量池中有什么
From 9339584b702d1aa228b297dbd1bae11e630233c3 Mon Sep 17 00:00:00 2001
From: Hollis
Date: Sat, 28 Nov 2020 16:55:32 +0800
Subject: [PATCH 08/84] Update annotation-in-spring.md
---
docs/basics/java-basic/annotation-in-spring.md | 4 ----
1 file changed, 4 deletions(-)
diff --git a/docs/basics/java-basic/annotation-in-spring.md b/docs/basics/java-basic/annotation-in-spring.md
index 4d93fd1c..b069d257 100644
--- a/docs/basics/java-basic/annotation-in-spring.md
+++ b/docs/basics/java-basic/annotation-in-spring.md
@@ -26,10 +26,6 @@
@Resource默认按名称装配,当找不到与名称匹配的bean才会按类型装配。
-@PostConstruct 初始化注解
-
-@PreDestroy 摧毁注解 默认 单例 启动就加载
-
### Spring中的这几个注解有什么区别:@Component 、@Repository、@Service、@Controller
From 6e33a3f7cbe6447387ed25b9cb6fdd3f9ac5c8b7 Mon Sep 17 00:00:00 2001
From: Hollis
Date: Sat, 28 Nov 2020 16:57:51 +0800
Subject: [PATCH 09/84] Update CET-UTC-GMT-CST.md
---
docs/basics/java-basic/CET-UTC-GMT-CST.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/docs/basics/java-basic/CET-UTC-GMT-CST.md b/docs/basics/java-basic/CET-UTC-GMT-CST.md
index 37e0402b..86872a67 100644
--- a/docs/basics/java-basic/CET-UTC-GMT-CST.md
+++ b/docs/basics/java-basic/CET-UTC-GMT-CST.md
@@ -16,5 +16,7 @@
### 关系
CET=UTC/GMT + 1小时
+
CST=UTC/GMT +8 小时
-CST=CET+9
\ No newline at end of file
+
+CST=CET + 7 小时
From cc7db521a1e49f75de96081cde78d945adaee286 Mon Sep 17 00:00:00 2001
From: Hollis
Date: Sat, 28 Nov 2020 17:04:48 +0800
Subject: [PATCH 10/84] Update synchronizedlist-vector.md
---
docs/basics/java-basic/synchronizedlist-vector.md | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/docs/basics/java-basic/synchronizedlist-vector.md b/docs/basics/java-basic/synchronizedlist-vector.md
index 4da3300f..82fbeb79 100644
--- a/docs/basics/java-basic/synchronizedlist-vector.md
+++ b/docs/basics/java-basic/synchronizedlist-vector.md
@@ -128,6 +128,14 @@ ArrayList类的remove方法内容如下:
> 从内部实现机制来讲ArrayList和Vector都是使用数组(Array)来控制集合中的对象。当你向这两种类型中增加元素的时候,如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大。所以如果你要在集合中保存大量的数据那么使用Vector有一些优势,因为你可以通过设置集合的初始化大小来避免不必要的资源开销。
+**同步代码块和同步方法的区别**
+
+1.同步代码块在锁定的范围上可能比同步方法要小,一般来说锁的范围大小和性能是成反比的。
+
+2.同步块可以更加精确的控制锁的作用域(锁的作用域就是从锁被获取到其被释放的时间),同步方法的锁的作用域就是整个方法。
+
+3.同步代码块可以选择对哪个对象加锁,但是静态方法只能给this对象加锁。
+
**同步代码块和同步方法的区别** 1.同步代码块在锁定的范围上可能比同步方法要小,一般来说锁的范围大小和性能是成反比的。 2.同步块可以更加精确的控制锁的作用域(锁的作用域就是从锁被获取到其被释放的时间),同步方法的锁的作用域就是整个方法。 3.静态代码块可以选择对哪个对象加锁,但是静态方法只能给this对象加锁。
> 因为SynchronizedList只是使用同步代码块包裹了ArrayList的方法,而ArrayList和Vector中同名方法的方法体内容并无太大差异,所以在锁定范围和锁的作用域上两者并无却别。 在锁定的对象区别上,SynchronizedList的同步代码块锁定的是mutex对象,Vector锁定的是this对象。那么mutex对象又是什么呢? 其实SynchronizedList有一个构造函数可以传入一个Object,如果在调用的时候显示的传入一个对象,那么锁定的就是用户传入的对象。如果没有指定,那么锁定的也是this对象。
@@ -140,4 +148,4 @@ ArrayList类的remove方法内容如下:
之前的比较都是基于我们将ArrayList转成SynchronizedList。那么如果我们想把LinkedList变成线程安全的,或者说我想要方便在中间插入和删除的同步的链表,那么我可以将已有的LinkedList直接转成 SynchronizedList,而不用改变他的底层数据结构。而这一点是Vector无法做到的,因为他的底层结构就是使用数组实现的,这个是无法更改的。
-所以,最后,SynchronizedList和Vector最主要的区别: **1\.SynchronizedList有很好的扩展和兼容功能。他可以将所有的List的子类转成线程安全的类。** **2\.使用SynchronizedList的时候,进行遍历时要手动进行同步处理**。 **3\.SynchronizedList可以指定锁定的对象。**
\ No newline at end of file
+所以,最后,SynchronizedList和Vector最主要的区别: **1\.SynchronizedList有很好的扩展和兼容功能。他可以将所有的List的子类转成线程安全的类。** **2\.使用SynchronizedList的时候,进行遍历时要手动进行同步处理**。 **3\.SynchronizedList可以指定锁定的对象。**
From 9632a15b1070e59aafebaabc3d119d2bf12350d0 Mon Sep 17 00:00:00 2001
From: "hollis.zhl"
Date: Sat, 28 Nov 2020 17:22:17 +0800
Subject: [PATCH 11/84] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=BA=BF=E7=A8=8B?=
=?UTF-8?q?=E6=9C=89=E5=85=B3=E7=9F=A5=E8=AF=86=E7=82=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/_sidebar.md | 14 ++-
.../basics/concurrent-coding/deamon-thread.md | 111 ++++++++++++++++++
.../concurrent-coding/implement-of-thread.md | 44 +++++++
.../concurrent-coding/priority-of-thread.md | 61 ++++++++++
.../concurrent-coding/progress-vs-thread.md | 15 +++
.../concurrent-coding/state-of-thread.md | 18 +++
.../concurrent-coding/thread-scheduling.md | 59 ++++++++++
docs/basics/concurrent-coding/thread.md | 17 +++
docs/menu.md | 14 ++-
9 files changed, 341 insertions(+), 12 deletions(-)
create mode 100644 docs/basics/concurrent-coding/deamon-thread.md
create mode 100644 docs/basics/concurrent-coding/implement-of-thread.md
create mode 100644 docs/basics/concurrent-coding/priority-of-thread.md
create mode 100644 docs/basics/concurrent-coding/progress-vs-thread.md
create mode 100644 docs/basics/concurrent-coding/state-of-thread.md
create mode 100644 docs/basics/concurrent-coding/thread-scheduling.md
create mode 100644 docs/basics/concurrent-coding/thread.md
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index 2f2ff055..83e8cbd3 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -429,19 +429,21 @@
* 线程
- * 线程与进程的区别
+ * [线程与进程的区别](/basics/concurrent-coding/progress-vs-thread.md)
- * 线程的实现
+ * [线程的特点](/basics/concurrent-coding/thread.md)
- * 线程的状态
+ * [线程的实现](/basics/concurrent-coding/implement-of-thread.md)
- * 线程优先级
+ * [线程的状态](/basics/concurrent-coding/state-of-thread.md)
- * 线程调度
+ * [线程优先级](/basics/concurrent-coding/priority-of-thread.md)
+
+ * [线程调度](/basics/concurrent-coding/thread-scheduling.md)
* 多线程如何Debug
- * 守护线程
+ * [守护线程](/basics/concurrent-coding/deamon-thread.md)
* 创建线程的多种方式
diff --git a/docs/basics/concurrent-coding/deamon-thread.md b/docs/basics/concurrent-coding/deamon-thread.md
new file mode 100644
index 00000000..694ebc30
--- /dev/null
+++ b/docs/basics/concurrent-coding/deamon-thread.md
@@ -0,0 +1,111 @@
+在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 。用户线程一般用户执行用户级任务,而守护线程也就是“后台线程”,一般用来执行后台任务,守护线程最典型的应用就是GC(垃圾回收器)。
+
+这两种线程其实是没有什么区别的,唯一的区别就是Java虚拟机在所有“用户线程”都结束后就会退出。
+
+我们可以通过使用`setDaemon()`方法通过传递true作为参数,使线程成为一个守护线程。我们必须在启动线程之前调用一个线程的`setDaemon()`方法。否则,就会抛出一个`java.lang.IllegalThreadStateException`。
+
+可以使用`isDaemon()`方法来检查线程是否是守护线程。
+
+ /**
+ * @author Hollis
+ */
+ public class Main {
+ public static void main(String[] args) {
+
+ Thread t1 = new Thread();
+ System.out.println(t1.isDaemon());
+ t1.setDaemon(true);
+ System.out.println(t1.isDaemon());
+ t1.start();
+ t1.setDaemon(false);
+ }
+ }
+
+
+以上代码输出结果:
+
+ false
+ true
+ Exception in thread "main" java.lang.IllegalThreadStateException
+ at java.lang.Thread.setDaemon(Thread.java:1359)
+ at com.hollis.Main.main(Main.java:16)
+
+
+我们提到,当JVM中只剩下守护线程的时候,JVM就会退出,那么写一段代码测试下:
+
+ /**
+ * @author Hollis
+ */
+ public class Main {
+ public static void main(String[] args) {
+
+ Thread childThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ while (true) {
+ System.out.println("I'm child thread..");
+ try {
+ TimeUnit.MILLISECONDS.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ });
+ childThread.start();
+ System.out.println("I'm main thread...");
+ }
+ }
+
+
+以上代码中,我们在Main线程中开启了一个子线程,在并没有显示将其设置为守护线程的情况下,他是一个用户线程,代码比较好理解,就是子线程处于一个while(true)循环中,每隔一秒打印一次`I'm child thread..`
+
+输出结果为:
+
+ I'm main thread...
+ I'm child thread..
+ I'm child thread..
+ .....
+ I'm child thread..
+ I'm child thread..
+
+
+我们再把子线程设置成守护线程,重新运行以上代码。
+
+ /**
+ * @author Hollis
+ */
+ public class Main {
+ public static void main(String[] args) {
+
+ Thread childThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ while (true) {
+ System.out.println("I'm child thread..");
+ try {
+ TimeUnit.MILLISECONDS.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ });
+ childThread.setDaemon(true);
+ childThread.start();
+ System.out.println("I'm main thread...");
+ }
+ }
+
+
+以上代码,我们通过`childThread.setDaemon(true);`把子线程设置成守护线程,然后运行,得到以下结果:
+
+ I'm main thread...
+ I'm child thread..
+
+
+子线程只打印了一次,也就是,在main线程执行结束后,由于子线程是一个守护线程,JVM就会直接退出了。
+
+**值得注意的是,在Daemon线程中产生的新线程也是Daemon的。**
+
+提到线程,有一个很重要的东西我们需要介绍一下,那就是ThreadLocal。
\ No newline at end of file
diff --git a/docs/basics/concurrent-coding/implement-of-thread.md b/docs/basics/concurrent-coding/implement-of-thread.md
new file mode 100644
index 00000000..d65dc721
--- /dev/null
+++ b/docs/basics/concurrent-coding/implement-of-thread.md
@@ -0,0 +1,44 @@
+主流的操作系统都提供了线程实现,实现线程主要有3种方式:使用内核线程实现、使用用户线程实现和使用用户线程加轻量级进程混合实现。
+
+## 使用内核线程实现
+
+内核线程(Kernel-Level Thread,KLT)就是直接由操作系统内核(Kernel,下称内核)支持的线程,这种线程由内核来完成线程切换,内核通过操纵调度器(Scheduler)对线程进行调度,并负责将线程的任务映射到各个处理器上。每个内核线程可以视为内核的一个分身,这样操作系统就有能力同时处理多件事情,支持多线程的内核就叫做多线程内核(Multi-Threads Kernel)。
+
+ 程序一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口——轻量级进程(Light Weight Process,LWP),轻量级进程就是我们通常意义上所讲的线程,由于每个轻量级进程都由一个内核线程支持,因此只有先支持内核线程,才能有轻量级进程。这种轻量级进程与内核线程之间1:1的关系称为一对一的线程模型,如图所示。
+
+![][1]
+
+ 由于内核线程的支持,每个轻量级进程都成为一个独立的调度单元,即使有一个轻量级进程在系统调用中阻塞了,也不会影响整个进程继续工作,但是轻量级进程具有它的局限性:首先,由于是基于内核线程实现的,所以各种线程操作,如创建、析构及同步,都需要进行系统调用。而系统调用的代价相对较高,需要在用户态(User Mode)和内核态(Kernel Mode)中来回切换。其次,每个轻量级进程都需要有一个内核线程的支持,因此轻量级进程要消耗一定的内核资源(如内核线程的栈空间),因此一个系统支持轻量级进程的数量是有限的。
+
+## 使用用户线程实现
+
+ 从广义上来讲,一个线程只要不是内核线程,就可以认为是用户线程(User Thread,UT),因此,从这个定义上来讲,轻量级进程也属于用户线程,但轻量级进程的实现始终是建立在内核之上的,许多操作都要进行系统调用,效率会受到限制。
+
+ 而狭义上的用户线程指的是完全建立在用户空间的线程库上,系统内核不能感知线程存在的实现。用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。如果程序实现得当,这种线程不需要切换到内核态,因此操作可以是非常快速且低消耗的,也可以支持规模更大的线程数量,部分高性能数据库中的多线程就是由用户线程实现的。这种进程与用户线程之间1:N的关系称为一对多的线程模型,如图所示。
+
+![][2]
+
+ 使用用户线程的优势在于不需要系统内核支援,劣势也在于没有系统内核的支援,所有的线程操作都需要用户程序自己处理。线程的创建、切换和调度都是需要考虑的问题,而且由于操作系统只把处理器资源分配到进程,那诸如“阻塞如何处理”、“多处理器系统中如何将线程映射到其他处理器上”这类问题解决起来将会异常困难,甚至不可能完成。因而使用用户线程实现的程序一般都比较复杂 ,除了以前在不支持多线程的操作系统中(如DOS)的多线程程序与少数有特殊需求的程序外,现在使用用户线程的程序越来越少了,Java、Ruby等语言都曾经使用过用户线程,最终又都放弃使用它。
+
+## 使用用户线程加轻量级进程混合实现
+
+线程除了依赖内核线程实现和完全由用户程序自己实现之外,还有一种将内核线程与用户线程一起使用的实现方式。在这种混合实现下,既存在用户线程,也存在轻量级进程。用户线程还是完全建立在用户空间中,因此用户线程的创建、切换、析构等操作依然廉价,并且可以支持大规模的用户线程并发。而操作系统提供支持的轻量级进程则作为用户线程和内核线程之间的桥梁,这样可以使用内核提供的线程调度功能及处理器映射,并且用户线程的系统调用要通过轻量级线程来完成,大大降低了整个进程被完全阻塞的风险。在这种混合模式中,用户线程与轻量级进程的数量比是不定的,即为N:M的关系,如图12-5所示,这种就是多对多的线程模型。
+
+许多UNIX系列的操作系统,如Solaris、HP-UX等都提供了N:M的线程模型实现。
+
+![][3]
+
+## Java线程的实现
+
+ Java线程在JDK 1.2之前,是基于称为“绿色线程”(Green Threads)的用户线程实现的,而在JDK 1.2中,线程模型替换为基于操作系统原生线程模型来实现。因此,在目前的JDK版本中,操作系统支持怎样的线程模型,在很大程度上决定了Java虚拟机的线程是怎样映射的,这点在不同的平台上没有办法达成一致,虚拟机规范中也并未限定Java线程需要使用哪种线程模型来实现。线程模型只对线程的并发规模和操作成本产生影响,对Java程序的编码和运行过程来说,这些差异都是透明的。
+
+ 对于Sun JDK来说,它的Windows版与Linux版都是使用一对一的线程模型实现的,一条Java线程就映射到一条轻量级进程之中,因为Windows和Linux系统提供的线程模型就是一对一的。
+
+ 而在Solaris平台中,由于操作系统的线程特性可以同时支持一对一(通过Bound Threads或Alternate Libthread实现)及多对多(通过LWP/Thread Based Synchronization实现)的线程模型,因此在Solaris版的JDK中也对应提供了两个平台专有的虚拟机参数:-XX:+UseLWPSynchronization(默认值)和-XX:+UseBoundThreads来明确指定虚拟机使用哪种线程模型。 Java语言则提供了在不同硬件和操作系统平台下对线程操作的统一处理,每个已经执行start()且还未结束的java.lang.Thread类的实例就代表了一个线程。我们注意到Thread类与大部分的Java API有显著的差别,它的所有关键方法都是声明为Native的。在Java API中,一个Native方法往往意味着这个方法没有使用或无法使用平台无关的手段来实现(当然也可能是为了执行效率而使用Native方法,不过,通常最高效率的手段也就是平台相关的手段)。
+
+(参考:深入理解Java虚拟机)
+
+
+ [1]: http://www.hollischuang.com/wp-content/uploads/2018/12/15442554190788.jpg
+ [2]: http://www.hollischuang.com/wp-content/uploads/2018/12/15442554407298.jpg
+ [3]: http://www.hollischuang.com/wp-content/uploads/2018/12/15442554705166.jpg
\ No newline at end of file
diff --git a/docs/basics/concurrent-coding/priority-of-thread.md b/docs/basics/concurrent-coding/priority-of-thread.md
new file mode 100644
index 00000000..8ea2e449
--- /dev/null
+++ b/docs/basics/concurrent-coding/priority-of-thread.md
@@ -0,0 +1,61 @@
+我们学习过,Java虚拟机采用抢占式调度模型。也就是说他会给优先级更高的线程优先分配CPU。
+
+虽然Java线程调度是系统自动完成的,但是我们还是可以“建议”系统给某些线程多分配一点执行时间,另外的一些线程则可以少分配一点——这项操作可以通过设置线程优先级来完成。
+
+Java语言一共设置了10个级别的线程优先级(Thread.MIN_PRIORITY至Thread.MAX_PRIORITY),在两个线程同时处于Ready状态时,优先级越高的线程越容易被系统选择执行。
+
+Java 线程优先级使用 1 ~ 10 的整数表示。默认的优先级是5。
+
+ 最低优先级 1:Thread.MIN_PRIORITY
+
+ 最高优先级 10:Thread.MAX_PRIORITY
+
+ 普通优先级 5:Thread.NORM_PRIORITY
+
+
+在Java中,可以使用Thread类的`setPriority()`方法为线程设置了新的优先级。`getPriority()`方法返回线程的当前优先级。当创建一个线程时,其默认优先级是创建该线程的线程的优先级。
+
+以下代码演示如何设置和获取线程的优先:
+
+ /**
+ * @author Hollis
+ */
+ public class Main {
+
+ public static void main(String[] args) {
+ Thread t = Thread.currentThread();
+ System.out.println("Main Thread Priority:" + t.getPriority());
+
+ Thread t1 = new Thread();
+ System.out.println("Thread(t1) Priority:" + t1.getPriority());
+ t1.setPriority(Thread.MAX_PRIORITY - 1);
+ System.out.println("Thread(t1) Priority:" + t1.getPriority());
+
+ t.setPriority(Thread.NORM_PRIORITY);
+ System.out.println("Main Thread Priority:" + t.getPriority());
+
+ Thread t2 = new Thread();
+ System.out.println("Thread(t2) Priority:" + t2.getPriority());
+
+ // Change thread t2 priority to minimum
+ t2.setPriority(Thread.MIN_PRIORITY);
+ System.out.println("Thread(t2) Priority:" + t2.getPriority());
+ }
+
+ }
+
+
+输出结果为:
+
+ Main Thread Priority:5
+ Thread(t1) Priority:5
+ Thread(t1) Priority:9
+ Main Thread Priority:5
+ Thread(t2) Priority:5
+ Thread(t2) Priority:1
+
+
+在上面的代码中,Java虚拟机启动时,就会通过main方法启动一个线程,JVM就会一直运行下去,直到以下任意一个条件发生:
+
+* 调用了exit()方法,并且exit()有权限被正常执行。
+* 所有的“非守护线程”都死了(即JVM中仅仅只有“守护线程”)。
\ No newline at end of file
diff --git a/docs/basics/concurrent-coding/progress-vs-thread.md b/docs/basics/concurrent-coding/progress-vs-thread.md
new file mode 100644
index 00000000..f848f0d5
--- /dev/null
+++ b/docs/basics/concurrent-coding/progress-vs-thread.md
@@ -0,0 +1,15 @@
+为了看起来像是“同时干多件事”,分时操作系统是把CPU的时间划分成长短基本相同的”时间片”,通过操作系统的管理,把这些时间片依次轮流地分配给各个用户的各个任务使用。
+
+在多任务处理系统中,CPU需要处理所有程序的操作,当用户来回切换它们时,需要记录这些程序执行到哪里。在操作系统中,CPU切换到另一个进程需要保存当前进程的状态并恢复另一个进程的状态:当前运行任务转为就绪(或者挂起、删除)状态,另一个被选定的就绪任务成为当前任务。**上下文切换**就是这样一个过程,他允许CPU记录并恢复各种正在运行程序的状态,使它能够完成切换操作。
+
+> 在上下文切换过程中,CPU会停止处理当前运行的程序,并保存当前程序运行的具体位置以便之后继续运行。从这个角度来看,上下文切换有点像我们同时阅读几本书,在来回切换书本的同时我们需要记住每本书当前读到的页码。在程序中,上下文切换过程中的“页码”信息是保存在进程控制块(PCB)中的。PCB还经常被称作“切换帧”(switchframe)。“页码”信息会一直保存到CPU的内存中,直到他们被再次使用。
+
+对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。
+
+而在多个进程之间切换的时候,需要进行上下文切换。但是上下文切换势必会耗费一些资源。于是人们考虑,能不能在一个进程中增加一些“子任务”,这样减少上下文切换的成本。比如我们使用Word的时候,它可以同时进行打字、拼写检查、字数统计等,这些子任务之间共用同一个进程资源,但是他们之间的切换不需要进行上下文切换。
+
+在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。
+
+随着时间的慢慢发展,人们进一步的切分了进程和线程之间的职责。**把进程当做资源分配的基本单元,把线程当做执行的基本单元,同一个进程的多个线程之间共享资源**
+
+拿我们比较熟悉的Java语言来说,Java程序是运行在JVM上面的,每一个JVM其实就是一个进程。所有的资源分配都是基于JVM进程来的。而在这个JVM进程中,又可以创建出很多线程,多个线程之间共享JVM资源,并且多个线程可以并发执行。
\ No newline at end of file
diff --git a/docs/basics/concurrent-coding/state-of-thread.md b/docs/basics/concurrent-coding/state-of-thread.md
new file mode 100644
index 00000000..78049e66
--- /dev/null
+++ b/docs/basics/concurrent-coding/state-of-thread.md
@@ -0,0 +1,18 @@
+线程是有状态的,并且这些状态之间也是可以互相流转的。Java中线程的状态分为6种:
+
+* 1\.初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
+* 1\.运行(RUNNABLE):Java线程中将就绪(READY)和运行中(RUNNING)两种状态笼统的称为“运行”。
+ * 就绪(READY):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中并分配cpu使用权 。
+ * 运行中(RUNNING):就绪(READY)的线程获得了cpu 时间片,开始执行程序代码。
+* 3\.阻塞(BLOCKED):表示线程阻塞于锁(关于锁,在后面章节会介绍)。
+* 4\.等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
+* 5\.超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
+* 6\. 终止(TERMINATED):表示该线程已经执行完毕。
+
+下图是一张线程状态的流转图:
+
+
+
+
+可以看到,图中的各个状态之间的流转路径上都有标注对应的Java中的方法。这些就是Java中进行线程调度的一些api。
+
diff --git a/docs/basics/concurrent-coding/thread-scheduling.md b/docs/basics/concurrent-coding/thread-scheduling.md
new file mode 100644
index 00000000..530ee49c
--- /dev/null
+++ b/docs/basics/concurrent-coding/thread-scheduling.md
@@ -0,0 +1,59 @@
+在关于线程安全的文章中,我们提到过,对于单CPU的计算机来说,在任意时刻只能执行一条机器指令,每个线程只有获得CPU的使用权才能执行指令。
+
+所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得CPU的使用权,分别执行各自的任务。
+
+前面关于线程状态的介绍中,我们知道,线程的运行状态中包含两种子状态,即就绪(READY)和运行中(RUNNING)。
+
+而一个线程想要从就绪状态变成运行中状态,这个过程需要系统调度,即给线程分配CPU的使用权,获得CPU使用权的线程才会从就绪状态变成运行状态。
+
+**给多个线程按照特定的机制分配CPU的使用权的过程就叫做线程调度。**
+
+还记得在介绍进程和线程的区别的时候,我们提到过的一句话吗:进程是分配资源的基本单元,线程是CPU调度的基本单元。这里所说的调度指的就是给其分配CPU时间片,让其执行任务。
+
+## Linux线程调度
+
+在Linux中,线程是由进程来实现,线程就是轻量级进程( lightweight process ),因此在Linux中,线程的调度是按照进程的调度方式来进行调度的,也就是说线程是调度单元。
+
+Linux这样实现的线程的好处的之一是:线程调度直接使用进程调度就可以了,没必要再搞一个进程内的线程调度器。在Linux中,调度器是基于线程的调度策略(scheduling policy)和静态调度优先级(static scheduling priority)来决定那个线程来运行。
+
+在Linux中,主要有三种调度策略。分别是:
+
+* SCHED_OTHER 分时调度策略,(默认的)
+* SCHED_FIFO 实时调度策略,先到先服务
+* SCHED_RR 实时调度策略,时间片轮转
+
+## Windows线程调度
+
+Windows 采用基于优先级的、抢占调度算法来调度线程。
+
+用于处理调度的 Windows 内核部分称为调度程序,Windows 调度程序确保具有最高优先级的线程总是在运行的。由于调度程序选择运行的线程会一直运行,直到被更高优先级的线程所抢占,或终止,或时间片已到,或调用阻塞系统调用(如 I/O)。如果在低优先级线程运行时,更高优先级的实时线程变成就绪,那么低优先级线程就被抢占。这种抢占使得实时线程在需要使用 CPU 时优先得到使用。
+
+# Java线程调度
+
+可以看到,不同的操作系统,有不同的线程调度策略。但是,作为一个Java开发人员来说,我们日常开发过程中一般很少关注操作系统层面的东西。
+
+主要是因为Java程序都是运行在Java虚拟机上面的,而虚拟机帮我们屏蔽了操作系统的差异,所以我们说Java是一个跨平台语言。
+
+**在操作系统中,一个Java程序其实就是一个进程。所以,我们说Java是单进程、多线程的!**
+
+前面关于线程的实现也介绍过,Thread类与大部分的Java API有显著的差别,它的所有关键方法都是声明为Native的,也就是说,他需要根据不同的操作系统有不同的实现。
+
+在Java的多线程程序中,为保证所有线程的执行能按照一定的规则执行,JVM实现了一个线程调度器,它定义了线程调度模型,对于CPU运算的分配都进行了规定,按照这些特定的机制为多个线程分配CPU的使用权。
+
+主要有两种调度模型:**协同式线程调度**和**抢占式调度模型**。
+
+## 协同式线程调度
+
+协同式调度的多线程系统,线程的执行时间由线程本身来控制,线程把自己的工作执行完了之后,要主动通知系统切换到另外一个线程上。协同式多线程的最大好处是实现简单,而且由于线程要把自己的事情干完后才会进行线程切换,切换操作对线程自己是可知的,所以没有什么线程同步的问题。
+
+## 抢占式调度模型
+
+抢占式调度的多线程系统,那么每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定。在这种实现线程调度的方式下,线程的执行时间是系统可控的,也不会有一个线程导致整个进程阻塞的问题。
+
+系统会让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃CPU。
+
+**Java虚拟机采用抢占式调度模型。**
+
+虽然Java线程调度是系统自动完成的,但是我们还是可以“建议”系统给某些线程多分配一点执行时间,另外的一些线程则可以少分配一点——这项操作可以通过设置线程优先级来完成。Java语言一共设置了10个级别的线程优先级(Thread.MIN_PRIORITY至Thread.MAX_PRIORITY),在两个线程同时处于Ready状态时,优先级越高的线程越容易被系统选择执行。
+
+不过,线程优先级并不是太靠谱,原因是Java的线程是通过映射到系统的原生线程上来实现的,所以线程调度最终还是取决于操作系统,虽然现在很多操作系统都提供线程优先级的概念,但是并不见得能与Java线程的优先级一一对应。
\ No newline at end of file
diff --git a/docs/basics/concurrent-coding/thread.md b/docs/basics/concurrent-coding/thread.md
new file mode 100644
index 00000000..c943f7f6
--- /dev/null
+++ b/docs/basics/concurrent-coding/thread.md
@@ -0,0 +1,17 @@
+在多线程操作系统中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。线程具有以下属性。
+
+## 轻型实体
+
+线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。 线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。TCB包括以下信息: (1)线程状态。 (2)当线程不运行时,被保存的现场资源。 (3)一组执行堆栈。 (4)存放每个线程的局部变量主存区。 (5)访问同一个进程中的主存和其它资源。 用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。
+
+## 独立调度和分派的基本单位。
+
+在多线程操作系统中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小(在同一进程中的)。
+
+## 可并发执行。
+
+在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。
+
+## 共享进程资源。
+
+在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。
diff --git a/docs/menu.md b/docs/menu.md
index 06ea55c7..c95b5b67 100644
--- a/docs/menu.md
+++ b/docs/menu.md
@@ -484,19 +484,21 @@ Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer)
* 线程
- * 线程与进程的区别
+ * [线程与进程的区别](/basics/concurrent-coding/progress-vs-thread.md)
- * 线程的实现
+ * [线程的特点](/basics/concurrent-coding/thread.md)
- * 线程的状态
+ * [线程的实现](/basics/concurrent-coding/implement-of-thread.md)
- * 线程优先级
+ * [线程的状态](/basics/concurrent-coding/state-of-thread.md)
- * 线程调度
+ * [线程优先级](/basics/concurrent-coding/priority-of-thread.md)
+
+ * [线程调度](/basics/concurrent-coding/thread-scheduling.md)
* 多线程如何Debug
- * 守护线程
+ * [守护线程](/basics/concurrent-coding/deamon-thread.md)
* 创建线程的多种方式
From da93e973546e1530c96517e0bfc422dd8bc09f71 Mon Sep 17 00:00:00 2001
From: "hollis.zhl"
Date: Sat, 12 Dec 2020 13:33:27 +0800
Subject: [PATCH 12/84] =?UTF-8?q?=E5=A4=9A=E7=BA=BF=E7=A8=8B=E9=83=A8?=
=?UTF-8?q?=E5=88=86=E5=AE=8C=E5=96=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/_sidebar.md | 10 +--
.../create-thread-with-Implement.md | 39 +++++++++++
...create-thread-with-callback-future-task.md | 57 +++++++++++++++
.../create-thread-with-extends.md | 41 +++++++++++
.../create-thread-with-thead-pool.md | 28 ++++++++
.../concurrent-coding/debug-in-multithread.md | 69 +++++++++++++++++++
docs/menu.md | 10 +--
7 files changed, 244 insertions(+), 10 deletions(-)
create mode 100644 docs/basics/concurrent-coding/create-thread-with-Implement.md
create mode 100644 docs/basics/concurrent-coding/create-thread-with-callback-future-task.md
create mode 100644 docs/basics/concurrent-coding/create-thread-with-extends.md
create mode 100644 docs/basics/concurrent-coding/create-thread-with-thead-pool.md
create mode 100644 docs/basics/concurrent-coding/debug-in-multithread.md
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index 83e8cbd3..ff3daef5 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -441,19 +441,19 @@
* [线程调度](/basics/concurrent-coding/thread-scheduling.md)
- * 多线程如何Debug
+ * [多线程如何Debug](/basics/concurrent-coding/debug-in-multithread.md)
* [守护线程](/basics/concurrent-coding/deamon-thread.md)
* 创建线程的多种方式
- * 继承Thread类创建线程
+ * [继承Thread类创建线程](/basics/concurrent-coding/create-thread-with-extends.md)
- * 实现Runnable接口创建线程
+ * [实现Runnable接口创建线程](/basics/concurrent-coding/create-thread-with-Implement.md)
- * 通过Callable和FutureTask创建线程
+ * [通过Callable和FutureTask创建线程](/basics/concurrent-coding/create-thread-with-callback-future-task.md)
- * 通过线程池创建线程
+ * [通过线程池创建线程](/basics/concurrent-coding/create-thread-with-thead-pool.md)
* 线程池
diff --git a/docs/basics/concurrent-coding/create-thread-with-Implement.md b/docs/basics/concurrent-coding/create-thread-with-Implement.md
new file mode 100644
index 00000000..a94fd0fd
--- /dev/null
+++ b/docs/basics/concurrent-coding/create-thread-with-Implement.md
@@ -0,0 +1,39 @@
+
+
+ public class MultiThreads {
+ public static void main(String[] args) throws InterruptedException {
+ System.out.println(Thread.currentThread().getName());
+
+
+ System.out.println("实现Runnable接口创建线程");
+ RunnableThread runnableThread = new RunnableThread();
+ new Thread(runnableThread).start();
+
+ }
+ }
+
+ class RunnableThread implements Runnable {
+
+ @Override
+ public void run() {
+ System.out.println(Thread.currentThread().getName());
+ }
+ }
+
+
+输出结果:
+
+ main
+ 实现Runnable接口创建线程
+ Thread-1
+
+
+通过实现接口,同样覆盖`run()`就可以创建一个新的线程了。
+
+我们都知道,Java是不支持多继承的,所以,使用Runnbale接口的形式,就可以避免要多继承 。比如有一个类A,已经继承了类B,就无法再继承Thread类了,这时候要想实现多线程,就需要使用Runnable接口了。
+
+除此之外,两者之间几乎无差别。
+
+但是,这两种创建线程的方式,其实是有一个缺点的,那就是:在执行完任务之后无法获取执行结果。
+
+如果我们希望再主线程中得到子线程的执行结果的话,就需要用到Callable和FutureTask
diff --git a/docs/basics/concurrent-coding/create-thread-with-callback-future-task.md b/docs/basics/concurrent-coding/create-thread-with-callback-future-task.md
new file mode 100644
index 00000000..062b7cea
--- /dev/null
+++ b/docs/basics/concurrent-coding/create-thread-with-callback-future-task.md
@@ -0,0 +1,57 @@
+自从Java 1.5开始,提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果。
+
+ public class MultiThreads {
+ public static void main(String[] args) throws InterruptedException {
+ CallableThread callableThread = new CallableThread();
+ FutureTask futureTask = new FutureTask<>(callableThread);
+ new Thread(futureTask).start();
+ System.out.println(futureTask.get());
+ }
+
+ class CallableThread implements Callable {
+ @Override
+ public Object call() throws Exception {
+ System.out.println(Thread.currentThread().getName());
+ return "Hollis";
+ }
+
+ }
+
+
+输出结果:
+
+ main
+ 通过Callable和FutureTask创建线程
+ Thread-2
+ Hollis
+
+
+Callable位于java.util.concurrent包下,它也是一个接口,在它里面也只声明了一个方法,只不过这个方法call(),和Runnable接口中的run()方法不同的是,call()方法有返回值。
+
+以上代码中,我们在CallableThread的call方法中返回字符串"Hollis",在主线程是可以获取到的。
+
+FutureTask可用于异步获取执行结果或取消执行任务的场景。通过传入Callable的任务给FutureTask,直接调用其run方法或者放入线程池执行,之后可以在外部通过FutureTask的get方法异步获取执行结果,因此,FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
+
+另外,FutureTask还可以确保即使调用了多次run方法,它都只会执行一次Runnable或者Callable任务,或者通过cancel取消FutureTask的执行等。
+
+值得注意的是,`futureTask.get()`会阻塞主线程,一直等子线程执行完并返回后才能继续执行主线程后面的代码。
+
+一般,在Callable执行完之前的这段时间,主线程可以先去做一些其他的事情,事情都做完之后,再获取Callable的返回结果。可以通过`isDone()`来判断子线程是否执行完。
+
+以上代码改造下就是如下内容:
+
+ public class MultiThreads {
+ public static void main(String[] args) throws InterruptedException {
+ CallableThread callableThread = new CallableThread();
+ FutureTask futureTask = new FutureTask<>(callableThread);
+ new Thread(futureTask).start();
+
+ System.out.println("主线程先做其他重要的事情");
+ if(!futureTask.isDone()){
+ // 继续做其他事儿
+ }
+ System.out.println(future.get()); // 可能会阻塞等待结果
+ }
+
+
+一般,我们会把Callable放到线程池中,然后让线程池去执行Callable中的代码。关于线程池前面介绍过了,是一种避免重复创建线程的开销的技术手段,线程池也可以用来创建线程。
\ No newline at end of file
diff --git a/docs/basics/concurrent-coding/create-thread-with-extends.md b/docs/basics/concurrent-coding/create-thread-with-extends.md
new file mode 100644
index 00000000..1e7b8060
--- /dev/null
+++ b/docs/basics/concurrent-coding/create-thread-with-extends.md
@@ -0,0 +1,41 @@
+
+
+ /**
+ * @author Hollis
+ */
+ public class MultiThreads {
+
+ public static void main(String[] args) throws InterruptedException {
+ System.out.println(Thread.currentThread().getName());
+
+ System.out.println("继承Thread类创建线程");
+ SubClassThread subClassThread = new SubClassThread();
+ subClassThread.start();
+ }
+ }
+
+ class SubClassThread extends Thread {
+
+ @Override
+ public void run() {
+ System.out.println(getName());
+ }
+ }
+
+
+输出结果:
+
+ main
+ 继承Thread类创建线程
+ Thread-0
+
+
+SubClassThread是一个继承了Thread类的子类,继承Thread类,并重写其中的run方法。然后new 一个SubClassThread的对象,并调用其start方法,即可启动一个线程。之后就会运行run中的代码。
+
+每个线程都是通过某个特定Thread对象所对应的方法`run()`来完成其操作的,方法`run()`称为线程体。通过调用Thread类的`start()`方法来启动一个线程。
+
+在主线程中,调用了子线程的`start()`方法后,主线程无需等待子线程的执行,即可执行后续的代码。而子线程便会开始执行其`run()`方法。
+
+当然,`run()`方法也是一个公有方法,在main函数中也可以直接调用这个方法,但是直接调用`run()`的话,主线程就需要等待其执行完,这种情况下,`run()`就是一个普通方法。
+
+如果读者感兴趣的话,查看一下前面介绍的Thread的源码,就可以发现,他继承了一个接口,那就是`java.lang.Runnable`,其实,开发者在代码中也可以直接通过这个接口创建一个新的线程。
\ No newline at end of file
diff --git a/docs/basics/concurrent-coding/create-thread-with-thead-pool.md b/docs/basics/concurrent-coding/create-thread-with-thead-pool.md
new file mode 100644
index 00000000..68a869e3
--- /dev/null
+++ b/docs/basics/concurrent-coding/create-thread-with-thead-pool.md
@@ -0,0 +1,28 @@
+Java中提供了对线程池的支持,有很多种方式。Jdk提供给外部的接口也很简单。直接调用ThreadPoolExecutor构造一个就可以了:
+
+ public class MultiThreads {
+ public static void main(String[] args) throws InterruptedException, ExecutionException {
+ System.out.println(Thread.currentThread().getName());
+ System.out.println("通过线程池创建线程");
+ ExecutorService executorService = new ThreadPoolExecutor(1, 1, 60L, TimeUnit.SECONDS,
+ new ArrayBlockingQueue(10));
+ executorService.execute(new Runnable() {
+ @Override
+ public void run() {
+ System.out.println(Thread.currentThread().getName());
+ }
+ });
+ }
+ }
+
+
+输出结果:
+
+ main
+ 通过线程池创建线程
+ pool-1-thread-1
+
+
+所谓线程池本质是一个hashSet。多余的任务会放在阻塞队列中。
+
+线程池的创建方式其实也有很多,也可以通过Executors静态工厂构建,但一般不建议。建议使用线程池来创建线程,并且建议使用带有ThreadFactory参数的ThreadPoolExecutor(需要依赖guava)构造方法设置线程名字,具体原因我们在后面的章节中在详细介绍。
\ No newline at end of file
diff --git a/docs/basics/concurrent-coding/debug-in-multithread.md b/docs/basics/concurrent-coding/debug-in-multithread.md
new file mode 100644
index 00000000..f51bbc67
--- /dev/null
+++ b/docs/basics/concurrent-coding/debug-in-multithread.md
@@ -0,0 +1,69 @@
+在学习过了前面几篇文章之后,相信很多人对于Java中的多线程都有了一定的了解,相信很多读者已经尝试过中写一些多线程的代码了。
+
+但是我之前面试过很多人,很多人都知道多线程怎么实现,但是却不知道如何调试多线程的代码,这篇文章我们来介绍下如何调试多线程的代码。
+
+首先我们写一个多线程的例子,使用继承Runnable接口的方式定义多个线程,并启动执行。
+
+ /**
+ * @author Hollis
+ */
+ public class MultiThreadDebug {
+
+ public static void main(String[] args) {
+ MyThread myThread = new MyThread();
+
+ Thread thread1 = new Thread(myThread, "thread 1");
+ Thread thread2 = new Thread(myThread, "thread 2");
+ Thread thread3 = new Thread(myThread, "thread 3");
+
+ thread1.start();
+
+ thread2.start();
+
+ thread3.start();
+ }
+ }
+
+ class MyThread implements Runnable {
+
+ @Override
+ public void run() {
+ System.out.println(Thread.currentThread().getName() + " running");
+ }
+ }
+
+ 我们尝试在代码中设置断点,并使用debug模式启动。
+
+
+![][1]
+
+如题,程序启动后,会进入一个线程的断点中,我们尝试看一下当前是哪个线程:
+
+![][2]
+
+发现是thread 1进入了断点。接着,我们尝试让代码继续执行,代码就直接结束运行,并且控制台打印如下:
+
+ Connected to the target VM, address: '127.0.0.1:55768', transport: 'socket'
+ thread 3 running
+ Disconnected from the target VM, address: '127.0.0.1:55768', transport: 'socket'
+ thread 2 running
+ thread 1 running
+
+ Process finished with exit code 0
+
+
+如果我们多次执行这个代码,就会发现,每一次打印的结果都不一样,三个线程的输出顺序是随机的,并且每一次debug只会进入到一个线程的执行。
+
+每次执行结果随即是因为不一定哪个线程可以先获得CPU时间片。
+
+那么,我们怎么才能让每一个线程的执行都能被debug呢?如何在多线程中进行debug排查问题呢?
+
+其实,在IDEA中有一个设置,那就是当我们在断点处单击鼠标右键就会弹出一个设置对话框,当我们把其中的All 修改为 Thread之后,尝试重新执行debug代码。
+
+![][3]
+
+重新执行之后,就可以发现,每一个线程都会进入到断点当中了。
+
+ [1]: https://www.hollischuang.com/wp-content/uploads/2020/11/16065562943648.jpg
+ [2]: https://www.hollischuang.com/wp-content/uploads/2020/11/16065563249582.jpg
+ [3]: https://www.hollischuang.com/wp-content/uploads/2020/11/16065565440571.jpg
\ No newline at end of file
diff --git a/docs/menu.md b/docs/menu.md
index c95b5b67..39044d69 100644
--- a/docs/menu.md
+++ b/docs/menu.md
@@ -496,19 +496,19 @@ Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer)
* [线程调度](/basics/concurrent-coding/thread-scheduling.md)
- * 多线程如何Debug
+ * [多线程如何Debug](/basics/concurrent-coding/debug-in-multithread.md)
* [守护线程](/basics/concurrent-coding/deamon-thread.md)
* 创建线程的多种方式
- * 继承Thread类创建线程
+ * [继承Thread类创建线程](/basics/concurrent-coding/create-thread-with-extends.md)
- * 实现Runnable接口创建线程
+ * [实现Runnable接口创建线程](/basics/concurrent-coding/create-thread-with-Implement.md)
- * 通过Callable和FutureTask创建线程
+ * [通过Callable和FutureTask创建线程](/basics/concurrent-coding/create-thread-with-callback-future-task.md)
- * 通过线程池创建线程
+ * [通过线程池创建线程](/basics/concurrent-coding/create-thread-with-thead-pool.md)
* 线程池
From f9acb9a65f25936a617690413c014a7b43839c44 Mon Sep 17 00:00:00 2001
From: "hollis.zhl"
Date: Sat, 12 Dec 2020 13:35:56 +0800
Subject: [PATCH 13/84] =?UTF-8?q?=E7=B1=BB=E5=8A=A0=E8=BD=BD=E9=83=A8?=
=?UTF-8?q?=E5=88=86=E5=A2=9E=E5=8A=A0=E7=9B=B8=E5=85=B3=E7=9F=A5=E8=AF=86?=
=?UTF-8?q?=E7=82=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/_sidebar.md | 8 +++++++-
docs/menu.md | 8 +++++++-
2 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index ff3daef5..7130a458 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -719,7 +719,13 @@
* 类加载过程
- * 双亲委派(破坏双亲委派)
+ * 如何判断JVM中类和其他类是不是同一个类
+
+ * 双亲委派原则
+
+ * 如何打破双亲委派
+
+ * 如何自定义类加载器
* 模块化(jboss modules、osgi、jigsaw)
diff --git a/docs/menu.md b/docs/menu.md
index 39044d69..a23dc246 100644
--- a/docs/menu.md
+++ b/docs/menu.md
@@ -774,7 +774,13 @@ Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer)
* 类加载过程
- * 双亲委派(破坏双亲委派)
+ * 如何判断JVM中类和其他类是不是同一个类
+
+ * 双亲委派原则
+
+ * 如何打破双亲委派
+
+ * 如何自定义类加载器
* 模块化(jboss modules、osgi、jigsaw)
From 0a200542c716e1ebf50f0a257da2b74cb01da8cf Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 13 Dec 2020 18:30:48 +0800
Subject: [PATCH 14/84] fix wrongly written and dorp repeated paragraph.
---
docs/basics/java-basic/synchronizedlist-vector.md | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/docs/basics/java-basic/synchronizedlist-vector.md b/docs/basics/java-basic/synchronizedlist-vector.md
index 82fbeb79..a673eb7d 100644
--- a/docs/basics/java-basic/synchronizedlist-vector.md
+++ b/docs/basics/java-basic/synchronizedlist-vector.md
@@ -136,9 +136,7 @@ ArrayList类的remove方法内容如下:
3.同步代码块可以选择对哪个对象加锁,但是静态方法只能给this对象加锁。
-**同步代码块和同步方法的区别** 1.同步代码块在锁定的范围上可能比同步方法要小,一般来说锁的范围大小和性能是成反比的。 2.同步块可以更加精确的控制锁的作用域(锁的作用域就是从锁被获取到其被释放的时间),同步方法的锁的作用域就是整个方法。 3.静态代码块可以选择对哪个对象加锁,但是静态方法只能给this对象加锁。
-
-> 因为SynchronizedList只是使用同步代码块包裹了ArrayList的方法,而ArrayList和Vector中同名方法的方法体内容并无太大差异,所以在锁定范围和锁的作用域上两者并无却别。 在锁定的对象区别上,SynchronizedList的同步代码块锁定的是mutex对象,Vector锁定的是this对象。那么mutex对象又是什么呢? 其实SynchronizedList有一个构造函数可以传入一个Object,如果在调用的时候显示的传入一个对象,那么锁定的就是用户传入的对象。如果没有指定,那么锁定的也是this对象。
+> 因为SynchronizedList只是使用同步代码块包裹了ArrayList的方法,而ArrayList和Vector中同名方法的方法体内容并无太大差异,所以在锁定范围和锁的作用域上两者并无区别。 在锁定的对象区别上,SynchronizedList的同步代码块锁定的是mutex对象,Vector锁定的是this对象。那么mutex对象又是什么呢? 其实SynchronizedList有一个构造函数可以传入一个Object,如果在调用的时候显示的传入一个对象,那么锁定的就是用户传入的对象。如果没有指定,那么锁定的也是this对象。
所以,SynchronizedList和Vector的区别目前为止有两点: 1.如果使用add方法,那么他们的扩容机制不一样。 2.SynchronizedList可以指定锁定的对象。
From 7e7655df7ad2e921bf8caae35e4853d3c5fcd9e5 Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 13 Dec 2020 18:50:05 +0800
Subject: [PATCH 15/84] fix wrong punctuation or character
---
docs/basics/java-basic/set-repetition.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/basics/java-basic/set-repetition.md b/docs/basics/java-basic/set-repetition.md
index e290e936..56dcc8f5 100644
--- a/docs/basics/java-basic/set-repetition.md
+++ b/docs/basics/java-basic/set-repetition.md
@@ -1,9 +1,9 @@
在Java的Set体系中,根据实现方式不同主要分为两大类。HashSet和TreeSet。
-1、TreeSet 是二叉树实现的,Treeset中的数据是自动排好序的,不允许放入null值
-2、HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束
+1、TreeSet 是二叉树实现的,TreeSet中的数据是自动排好序的,不允许放入null值
+2、HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束
-在HashSet中,基本的操作都是有HashMap底层实现的,因为HashSet底层是用HashMap存储数据的。当向HashSet中添加元素的时候,首先计算元素的hashcode值,然后通过扰动计算和按位与的方式计算出这个元素的存储位置,如果这个位置位空,就将元素添加进去;如果不为空,则用equals方法比较元素是否相等,相等就不添加,否则找一个空位添加。
+在HashSet中,基本的操作都是由HashMap底层实现的,因为HashSet底层是用HashMap存储数据的。当向HashSet中添加元素的时候,首先计算元素的hashCode值,然后通过扰动计算和按位与的方式计算出这个元素的存储位置,如果这个位置为空,就将元素添加进去;如果不为空,则用equals方法比较元素是否相等,相等就不添加,否则找一个空位添加。
TreeSet的底层是TreeMap的keySet(),而TreeMap是基于红黑树实现的,红黑树是一种平衡二叉查找树,它能保证任何一个节点的左右子树的高度差不会超过较矮的那棵的一倍。
From a56f2e36de8d795bc0078d1acb9d4b23e0054c7a Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 13 Dec 2020 19:06:45 +0800
Subject: [PATCH 16/84] fix fileencoding
---
docs/basics/java-basic/set-repetition.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/basics/java-basic/set-repetition.md b/docs/basics/java-basic/set-repetition.md
index 56dcc8f5..8f0236b4 100644
--- a/docs/basics/java-basic/set-repetition.md
+++ b/docs/basics/java-basic/set-repetition.md
@@ -1,7 +1,7 @@
在Java的Set体系中,根据实现方式不同主要分为两大类。HashSet和TreeSet。
1、TreeSet 是二叉树实现的,TreeSet中的数据是自动排好序的,不允许放入null值
-2、HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束
+2、HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中的唯一约束
在HashSet中,基本的操作都是由HashMap底层实现的,因为HashSet底层是用HashMap存储数据的。当向HashSet中添加元素的时候,首先计算元素的hashCode值,然后通过扰动计算和按位与的方式计算出这个元素的存储位置,如果这个位置为空,就将元素添加进去;如果不为空,则用equals方法比较元素是否相等,相等就不添加,否则找一个空位添加。
From cdf4e22054c1b2d16958effe05ab1a2a415e82ef Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 13 Dec 2020 19:09:19 +0800
Subject: [PATCH 17/84] fix fileencoding
---
docs/basics/java-basic/set-repetition.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/basics/java-basic/set-repetition.md b/docs/basics/java-basic/set-repetition.md
index 8f0236b4..ec4ccd98 100644
--- a/docs/basics/java-basic/set-repetition.md
+++ b/docs/basics/java-basic/set-repetition.md
@@ -1,7 +1,7 @@
在Java的Set体系中,根据实现方式不同主要分为两大类。HashSet和TreeSet。
1、TreeSet 是二叉树实现的,TreeSet中的数据是自动排好序的,不允许放入null值
-2、HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中的唯一约束
+2、HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null值,但只能放入一个null,两者中的值都不能重复,就如数据库中的唯一约束
在HashSet中,基本的操作都是由HashMap底层实现的,因为HashSet底层是用HashMap存储数据的。当向HashSet中添加元素的时候,首先计算元素的hashCode值,然后通过扰动计算和按位与的方式计算出这个元素的存储位置,如果这个位置为空,就将元素添加进去;如果不为空,则用equals方法比较元素是否相等,相等就不添加,否则找一个空位添加。
From 6747ea980056e072e2f0a509b234a3a9668a232b Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 13 Dec 2020 19:10:09 +0800
Subject: [PATCH 18/84] fix fileencoding
---
docs/basics/java-basic/set-repetition.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/basics/java-basic/set-repetition.md b/docs/basics/java-basic/set-repetition.md
index ec4ccd98..f55534f4 100644
--- a/docs/basics/java-basic/set-repetition.md
+++ b/docs/basics/java-basic/set-repetition.md
@@ -1,7 +1,7 @@
在Java的Set体系中,根据实现方式不同主要分为两大类。HashSet和TreeSet。
-1、TreeSet 是二叉树实现的,TreeSet中的数据是自动排好序的,不允许放入null值
-2、HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null值,但只能放入一个null,两者中的值都不能重复,就如数据库中的唯一约束
+1、TreeSet 是二叉树实现的,TreeSet中的数据是自动排好序的,不允许放null值
+2、HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放null值,但只能放入一个null,两者中的值都不能重复,就如数据库中的唯一约束
在HashSet中,基本的操作都是由HashMap底层实现的,因为HashSet底层是用HashMap存储数据的。当向HashSet中添加元素的时候,首先计算元素的hashCode值,然后通过扰动计算和按位与的方式计算出这个元素的存储位置,如果这个位置为空,就将元素添加进去;如果不为空,则用equals方法比较元素是否相等,相等就不添加,否则找一个空位添加。
From 9901de5df019dd514cc841ee2ec9c05b69965865 Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 13 Dec 2020 19:11:36 +0800
Subject: [PATCH 19/84] fix fileencoding
---
docs/basics/java-basic/set-repetition.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/basics/java-basic/set-repetition.md b/docs/basics/java-basic/set-repetition.md
index f55534f4..ec4ccd98 100644
--- a/docs/basics/java-basic/set-repetition.md
+++ b/docs/basics/java-basic/set-repetition.md
@@ -1,7 +1,7 @@
在Java的Set体系中,根据实现方式不同主要分为两大类。HashSet和TreeSet。
-1、TreeSet 是二叉树实现的,TreeSet中的数据是自动排好序的,不允许放null值
-2、HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放null值,但只能放入一个null,两者中的值都不能重复,就如数据库中的唯一约束
+1、TreeSet 是二叉树实现的,TreeSet中的数据是自动排好序的,不允许放入null值
+2、HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null值,但只能放入一个null,两者中的值都不能重复,就如数据库中的唯一约束
在HashSet中,基本的操作都是由HashMap底层实现的,因为HashSet底层是用HashMap存储数据的。当向HashSet中添加元素的时候,首先计算元素的hashCode值,然后通过扰动计算和按位与的方式计算出这个元素的存储位置,如果这个位置为空,就将元素添加进去;如果不为空,则用equals方法比较元素是否相等,相等就不添加,否则找一个空位添加。
From 69b7269bf9c080e6eba6806f9a4f259fbda7de86 Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 13 Dec 2020 19:13:02 +0800
Subject: [PATCH 20/84] fix fileencoding
---
docs/basics/java-basic/set-repetition.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/basics/java-basic/set-repetition.md b/docs/basics/java-basic/set-repetition.md
index ec4ccd98..d9072a77 100644
--- a/docs/basics/java-basic/set-repetition.md
+++ b/docs/basics/java-basic/set-repetition.md
@@ -1,7 +1,7 @@
在Java的Set体系中,根据实现方式不同主要分为两大类。HashSet和TreeSet。
-1、TreeSet 是二叉树实现的,TreeSet中的数据是自动排好序的,不允许放入null值
-2、HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null值,但只能放入一个null,两者中的值都不能重复,就如数据库中的唯一约束
+1、TreeSet 是二叉树实现的,TreeSet中的数据是自动排好序的,不允许放入 null值
+2、HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入 null值,但只能放入一个null,两者中的值都不能重复,就如数据库中的唯一约束
在HashSet中,基本的操作都是由HashMap底层实现的,因为HashSet底层是用HashMap存储数据的。当向HashSet中添加元素的时候,首先计算元素的hashCode值,然后通过扰动计算和按位与的方式计算出这个元素的存储位置,如果这个位置为空,就将元素添加进去;如果不为空,则用equals方法比较元素是否相等,相等就不添加,否则找一个空位添加。
From a2ecb31ee57c4cebc2f81d5130412724c1752993 Mon Sep 17 00:00:00 2001
From: liujinmu
Date: Tue, 15 Dec 2020 10:01:06 +0800
Subject: [PATCH 21/84] Update integer-scope.md
typo
---
docs/basics/java-basic/integer-scope.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/basics/java-basic/integer-scope.md b/docs/basics/java-basic/integer-scope.md
index 7ed5f264..9edbf3b2 100644
--- a/docs/basics/java-basic/integer-scope.md
+++ b/docs/basics/java-basic/integer-scope.md
@@ -1,6 +1,6 @@
Java中的整型主要包含byte、short、int和long这四种,表示的数字范围也是从小到大的,之所以表示范围不同主要和他们存储数据时所占的字节数有关。
-先来个简答的科普,1字节=8位(bit)。java中的整型属于有符号数。
+先来个简单的科普,1字节=8位(bit)。java中的整型属于有符号数。
先来看计算中8bit可以表示的数字:
最小值:10000000 (-128)(-2^7)
From 43ec13f09baf115b3f335ba5ca728610b79508af Mon Sep 17 00:00:00 2001
From: liujinmu
Date: Tue, 15 Dec 2020 10:03:27 +0800
Subject: [PATCH 22/84] Update integer-cache.md
typo
---
docs/basics/java-basic/integer-cache.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/basics/java-basic/integer-cache.md b/docs/basics/java-basic/integer-cache.md
index 9b47ceea..66be41db 100644
--- a/docs/basics/java-basic/integer-cache.md
+++ b/docs/basics/java-basic/integer-cache.md
@@ -27,7 +27,7 @@
}
-我们普遍认为上面的两个判断的结果都是false。虽然比较的值是相等的,但是由于比较的是对象,而对象的引用不一样,所以会认为两个if判断都是false的。在Java中,`==`比较的是对象应用,而`equals`比较的是值。所以,在这个例子中,不同的对象有不同的引用,所以在进行比较的时候都将返回false。奇怪的是,这里两个类似的if条件判断返回不同的布尔值。
+我们普遍认为上面的两个判断的结果都是false。虽然比较的值是相等的,但是由于比较的是对象,而对象的引用不一样,所以会认为两个if判断都是false的。在Java中,`==`比较的是对象引用,而`equals`比较的是值。所以,在这个例子中,不同的对象有不同的引用,所以在进行比较的时候都将返回false。奇怪的是,这里两个类似的if条件判断返回不同的布尔值。
上面这段代码真正的输出结果:
@@ -161,4 +161,4 @@ IntegerCache是Integer类中定义的一个`private static`的内部类。接下
[2]: http://www.hollischuang.com/?p=1174
[3]: http://javapapers.com/
[4]: http://www.hollischuang.com
- [5]: http://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.1.7
\ No newline at end of file
+ [5]: http://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.1.7
From 678e990d992c4463227d1d0212608b53586a19ee Mon Sep 17 00:00:00 2001
From: dramatist
Date: Mon, 21 Dec 2020 11:00:46 +0800
Subject: [PATCH 23/84] fix typo
---
docs/basics/java-basic/annotation-in-java.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/basics/java-basic/annotation-in-java.md b/docs/basics/java-basic/annotation-in-java.md
index 5863debc..1dc1eb04 100644
--- a/docs/basics/java-basic/annotation-in-java.md
+++ b/docs/basics/java-basic/annotation-in-java.md
@@ -2,11 +2,11 @@
@Override 表示当前方法覆盖了父类的方法
-@Deprecation 表示方法已经过时,方法上有横线,使用时会有警告。
+@Deprecated 表示方法已经过时,方法上有横线,使用时会有警告。
@SuppressWarnings 表示关闭一些警告信息(通知java编译器忽略特定的编译警告)
-SafeVarargs (jdk1.7更新) 表示:专门为抑制“堆污染”警告提供的。
+@SafeVarargs (jdk1.7更新) 表示:专门为抑制“堆污染”警告提供的。
@FunctionalInterface (jdk1.8更新) 表示:用来指定某个接口必须是函数式接口,否则就会编译出错。
From 1537abeae695abe3250077f363219de0685eeea2 Mon Sep 17 00:00:00 2001
From: dramatist
Date: Mon, 21 Dec 2020 11:07:17 +0800
Subject: [PATCH 24/84] =?UTF-8?q?=E8=A1=A5=E5=85=85=E4=B8=A4=E4=B8=AA?=
=?UTF-8?q?=E5=85=83=E6=B3=A8=E8=A7=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/basics/java-basic/meta-annotation.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/docs/basics/java-basic/meta-annotation.md b/docs/basics/java-basic/meta-annotation.md
index 8c0f5823..310eddde 100644
--- a/docs/basics/java-basic/meta-annotation.md
+++ b/docs/basics/java-basic/meta-annotation.md
@@ -11,4 +11,5 @@
@Retention
就是元注解。
-元注解有四个:@Target(表示该注解可以用于什么地方)、@Retention(表示再什么级别保存该注解信息)、@Documented(将此注解包含再javadoc中)、@Inherited(允许子类继承父类中的注解)。
\ No newline at end of file
+元注解有六个:@Target(表示该注解可以用于什么地方)、@Retention(表示再什么级别保存该注解信息)、@Documented(将此注解包含再javadoc中)、@Inherited(允许子类继承父类中的注解)、@Repeatable(1.8新增,允许一个注解在一个元素上使用多次)、@Native(1.8新增,修饰成员变量,表示这个变量可以被本地代码引用,常常被代码生成工具使用)。
+
From 42199a9959a11c388257e80be91a41008099420c Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sat, 26 Dec 2020 20:53:08 +0800
Subject: [PATCH 25/84] fixed wrong characters
---
docs/basics/java-basic/hash-in-hashmap.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/basics/java-basic/hash-in-hashmap.md b/docs/basics/java-basic/hash-in-hashmap.md
index 00e14542..fae6933a 100644
--- a/docs/basics/java-basic/hash-in-hashmap.md
+++ b/docs/basics/java-basic/hash-in-hashmap.md
@@ -51,7 +51,7 @@
### 源码解析
-首先,在同一个版本的Jdk中,HashMap、HashTable以及ConcurrentHashMap里面的hash方法的实现是不同的。再不同的版本的JDK中(Java7 和 Java8)中也是有区别的。我会尽量全部介绍到。相信,看文这篇文章,你会彻底理解hash方法。
+首先,在同一个版本的Jdk中,HashMap、HashTable以及ConcurrentHashMap里面的hash方法的实现是不同的。在不同的版本的JDK中(Java7 和 Java8)中也是有区别的。我会尽量全部介绍到。相信,看完这篇文章,你会彻底理解hash方法。
在上代码之前,我们先来做个简单分析。我们知道,hash方法的功能是根据Key来定位这个K-V在链表数组中的位置的。也就是hash方法的输入应该是个Object类型的Key,输出应该是个int类型的数组下标。如果让你设计这个方法,你会怎么做?
@@ -252,4 +252,4 @@ Jdk的源代码,每一行都很有意思,都值得花时间去钻研、推
[7]: https://www.jianshu.com/p/7e7f52a49ffc
[8]: http://blog.csdn.net/justloveyou_/article/details/62893086
[9]: http://zhaox.github.io/2016/07/05/hashmap-vs-hashtable
- [10]: https://www.zhihu.com/question/51784530
\ No newline at end of file
+ [10]: https://www.zhihu.com/question/51784530
From 7527a03b70710a1e0a24b095fb40d37a5b1a2ebe Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sat, 26 Dec 2020 21:18:21 +0800
Subject: [PATCH 26/84] fixed wrong characters in
---
docs/basics/java-basic/hash-in-hashmap.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/basics/java-basic/hash-in-hashmap.md b/docs/basics/java-basic/hash-in-hashmap.md
index fae6933a..ddb52259 100644
--- a/docs/basics/java-basic/hash-in-hashmap.md
+++ b/docs/basics/java-basic/hash-in-hashmap.md
@@ -156,7 +156,7 @@ HashMap的数据是存储在链表数组里面的。在对HashMap进行插入/
>
> 也就是说,HashTable的链表数组的默认大小是一个素数、奇数。之后的每次扩充结果也都是奇数。
>
-> 由于HashTable会尽量使用素数、奇数作为容量的大小。当哈希表的大小为素数时,简单的取模哈希的结果会更加均匀。(这个是可以证明出来的,由于不是本文重点,暂不详细介绍,可参考:http://zhaox.github.io/algorithm/2015/06/29/hash)
+> 由于HashTable会尽量使用素数、奇数作为容量的大小。当哈希表的大小为素数时,简单的取模哈希的结果会更加均匀。(这个是可以证明出来的,由于不是本文重点,暂不详细介绍,可参考:http://zhaox.github.io/algorithm/2015/06/29/hash
至此,我们看完了Java 7中HashMap和HashTable中对于hash的实现,我们来做个简单的总结。
@@ -210,7 +210,7 @@ HashMap的数据是存储在链表数组里面的。在对HashMap进行插入/
HashTable In Java 8
-在Java 8的HashTable中,已经不在有hash方法了。但是哈希的操作还是在的,比如在put方法中就有如下实现:
+在Java 8的HashTable中,已经不再有hash方法了。但是哈希的操作还是在的,比如在put方法中就有如下实现:
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
From bedbddfb11c36c603252f0833e271c976e0162cf Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sat, 26 Dec 2020 21:46:42 +0800
Subject: [PATCH 27/84] fixed wrong characters in
---
.../java-basic/hashmap-default-loadfactor.md | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/docs/basics/java-basic/hashmap-default-loadfactor.md b/docs/basics/java-basic/hashmap-default-loadfactor.md
index 2f88bdd2..1f76e207 100644
--- a/docs/basics/java-basic/hashmap-default-loadfactor.md
+++ b/docs/basics/java-basic/hashmap-default-loadfactor.md
@@ -20,7 +20,7 @@
首先我们来介绍下什么是负载因子(loadFactory),如果读者对这部分已经有了解,那么可以直接跨过这一段。
-我们知道,第一次创建HashMap的时候,就会指定其容量(如果未显示制定,默认是16,详见[为啥HashMap的默认容量是16?][3]),那随着我们不断的向HashMap中put元素的时候,就有可能会超过其容量,那么就需要有一个扩容机制。
+我们知道,第一次创建HashMap的时候,就会指定其容量(如果未显示指定,默认是16,详见[为啥HashMap的默认容量是16?][3]),那随着我们不断的向HashMap中put元素的时候,就有可能会超过其容量,那么就需要有一个扩容机制。
所谓扩容,就是扩大HashMap的容量:
@@ -37,14 +37,14 @@
在HashMap中,临界值(threshold) = 负载因子(loadFactor) * 容量(capacity)。
-loadFactor是装载因子,表示HashMap满的程度,默认值为0.75f,也就是说默认情况下,当HashMap中元素个数达到了容量的3/4的时候就会进行自动扩容。(相见[HashMap中傻傻分不清楚的那些概念][4])
+loadFactor是装载因子,表示HashMap满的程度,默认值为0.75f,也就是说默认情况下,当HashMap中元素个数达到了容量的3/4的时候就会进行自动扩容。(详见[HashMap中傻傻分不清楚的那些概念][4])
### 为什么要扩容
还记得前面我们说过,HashMap在扩容到过程中不仅要对其容量进行扩充,还需要进行rehash!所以,这个过程其实是很耗时的,并且Map中元素越多越耗时。
-rehash的过程相当于对其中所有的元素重新做一遍hash,重新计算要分配到那个桶中。
+rehash的过程相当于对其中所有的元素重新做一遍hash,重新计算要分配到哪个桶中。
那么,有没有人想过一个问题,既然这么麻烦,为啥要扩容?HashMap不是一个数组链表吗?不扩容的话,也是可以无限存储的呀。为啥要扩容?
@@ -66,7 +66,7 @@ HashMap将数组和链表组合在一起,发挥了两者的优势,我们可
HashMap基于链表的数组的数据结构实现的
-我们在向HashMap中put元素的时候,就需要先定外到是数组中的哪条链表,然后把这个元素挂在这个链表的后面。
+我们在向HashMap中put元素的时候,就需要先定位到是数组中的哪条链表,然后把这个元素挂在这个链表的后面。
当我们从HashMap中get元素的时候,也是需要定位到是数组中的哪条链表,然后再逐一遍历链表中的元素,直到查找到需要的元素为止。
@@ -88,11 +88,11 @@ HashMap基于链表的数组的数据结构实现的
1、容量太小。容量小,碰撞的概率就高了。狼多肉少,就会发生争强。
-2、hash算法不够好。算法不合理,就可能都分到同一个或几个桶中。分配不均,也会发生争强。
+2、hash算法不够好。算法不合理,就可能都分到同一个或几个桶中。分配不均,也会发生争抢。
所以,解决HashMap中的哈希碰撞也是从这两方面入手。
-这两点在HashMap中都有很好的提现。两种方法相结合,**在合适的时候扩大数组容量,再通过一个合适的hash算法计算元素分配到哪个数组中,就可以大大的减少冲突的概率。就能避免查询效率低下的问题。**
+这两点在HashMap中都有很好的体现。两种方法相结合,**在合适的时候扩大数组容量,再通过一个合适的hash算法计算元素分配到哪个数组中,就可以大大的减少冲突的概率。就能避免查询效率低下的问题。**
### 为什么默认loadFactory是0.75
@@ -145,7 +145,7 @@ HashMap基于链表的数组的数据结构实现的
所以,合理值大概在0.7左右。
-当然,这个数学计算方法,并不是在Java的官方文档中提现的,我们也无从考察到底有没有这层考虑,就像我们根本不知道鲁迅写文章时候怎么想的一样,只能推测。这个推测来源于Stack Overflor(https://stackoverflow.com/questions/10901752/what-is-the-significance-of-load-factor-in-hashmap)
+当然,这个数学计算方法,并不是在Java的官方文档中体现的,我们也无从考察到底有没有这层考虑,就像我们根本不知道鲁迅写文章时候怎么想的一样,只能推测。这个推测来源于Stack Overflor(https://stackoverflow.com/questions/10901752/what-is-the-significance-of-load-factor-in-hashmap)
#### 0\.75的必然因素
@@ -180,7 +180,7 @@ HashMap是一种K-V结构,为了提升其查询及插入的速度,底层采
loadFactory太大,比如等于1,那么就会有很高的哈希冲突的概率,会大大降低查询速度。
-loadFactory太小,比如等于0.5,那么频繁扩容没,就会大大浪费空间。
+loadFactory太小,比如等于0.5,那么频繁扩容,就会大大浪费空间。
所以,这个值需要介于0.5和1之间。根据数学公式推算。这个值在log(2)的时候比较合理。
@@ -208,4 +208,4 @@ https://preshing.com/20110504/hash-collision-probabilities/
[4]: http://www.hollischuang.com/archives/2416
[5]: http://www.hollischuang.com/archives/2091
[6]: http://www.hollischuang.com/wp-content/uploads/2020/02/15823447916666.jpg
- [7]: http://www.hollischuang.com/wp-content/uploads/2020/02/15823459128857.jpg
\ No newline at end of file
+ [7]: http://www.hollischuang.com/wp-content/uploads/2020/02/15823459128857.jpg
From 817682ba7ebb021be1f1dfa68ab56b6c4f9a49a5 Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sat, 26 Dec 2020 21:51:21 +0800
Subject: [PATCH 28/84] fixed wrong characters in
---
docs/basics/java-basic/hashmap-init-capacity.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/basics/java-basic/hashmap-init-capacity.md b/docs/basics/java-basic/hashmap-init-capacity.md
index afe3b496..2e27a749 100644
--- a/docs/basics/java-basic/hashmap-init-capacity.md
+++ b/docs/basics/java-basic/hashmap-init-capacity.md
@@ -24,7 +24,7 @@
**但是,这么做不仅不对,而且以上方式创建出来的Map的容量也不是7。**
-因为,当我们使用HashMap(int initialCapacity)来初始化容量的时候,HashMap并不会使用我们传进来的initialCapacity直接作为初识容量。
+因为,当我们使用HashMap(int initialCapacity)来初始化容量的时候,HashMap并不会使用我们传进来的initialCapacity直接作为初始容量。
**JDK会默认帮我们计算一个相对合理的值当做初始容量。所谓合理值,其实是找到第一个比用户传入的值大的2的幂。**
@@ -83,4 +83,4 @@
或者哪一天你碰到一个面试官问你一些细节的时候,你也能有个印象,或者某一天你也可以拿这个出去面试问其他人~!啊哈哈哈。
[1]: http://www.hollischuang.com/archives/2416
- [2]: http://www.hollischuang.com/wp-content/uploads/2019/12/15756974111211.jpg
\ No newline at end of file
+ [2]: http://www.hollischuang.com/wp-content/uploads/2019/12/15756974111211.jpg
From 084b6dc1ddafde98a5935807544e3f7096ae3075 Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sat, 26 Dec 2020 21:55:57 +0800
Subject: [PATCH 29/84] fixed wrong characters in
---
docs/basics/java-basic/hashmap-default-loadfactor.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/basics/java-basic/hashmap-default-loadfactor.md b/docs/basics/java-basic/hashmap-default-loadfactor.md
index 1f76e207..9a2f56b3 100644
--- a/docs/basics/java-basic/hashmap-default-loadfactor.md
+++ b/docs/basics/java-basic/hashmap-default-loadfactor.md
@@ -86,7 +86,7 @@ HashMap基于链表的数组的数据结构实现的
无外乎两种情况:
-1、容量太小。容量小,碰撞的概率就高了。狼多肉少,就会发生争强。
+1、容量太小。容量小,碰撞的概率就高了。狼多肉少,就会发生争抢。
2、hash算法不够好。算法不合理,就可能都分到同一个或几个桶中。分配不均,也会发生争抢。
From 73741f594435b6c17b78759357b288e1077eda56 Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sat, 26 Dec 2020 22:09:58 +0800
Subject: [PATCH 30/84] fixed wrong characters in
---
docs/basics/java-basic/stream.md | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/docs/basics/java-basic/stream.md b/docs/basics/java-basic/stream.md
index 9455b4df..80a1eab1 100644
--- a/docs/basics/java-basic/stream.md
+++ b/docs/basics/java-basic/stream.md
@@ -66,7 +66,7 @@ filter 方法用于通过设置的条件过滤出元素。以下代码片段使
List strings = Arrays.asList("Hollis", "", "HollisChuang", "H", "hollis");
strings.stream().filter(string -> !string.isEmpty()).forEach(System.out::println);
- //Hollis, , HollisChuang, H, hollis
+ //Hollis, HollisChuang, H, hollis
**map**
@@ -74,13 +74,13 @@ filter 方法用于通过设置的条件过滤出元素。以下代码片段使
map 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:
List numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
- numbers.stream().map( i -> i*i).forEach(System.out::println);
+ numbers.stream().map(i -> i*i).forEach(System.out::println);
//9,4,4,9,49,9,25
**limit/skip**
-limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素。以下代码片段使用 limit 方法保理4个元素:
+limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素。以下代码片段使用 limit 方法保留4个元素:
List numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().limit(4).forEach(System.out::println);
@@ -120,9 +120,9 @@ distinct主要用来去重,以下代码片段使用 distinct 对元素进行
### Stream最终操作
-Stream的中间操作得到的结果还是一个Stream,那么如何把一个Stream转换成我们需要的类型呢?比如计算出流中元素的个数、将流装换成集合等。这就需要最终操作(terminal operation)
+Stream的中间操作得到的结果还是一个Stream,那么如何把一个Stream转换成我们需要的类型呢?比如计算出流中元素的个数、将流换成集合等。这就需要最终操作(terminal operation)
-最终操作会消耗流,产生一个最终结果。也就是说,在最终操作之后,不能再次使用流,也不能在使用任何中间操作,否则将抛出异常:
+最终操作会消耗流,产生一个最终结果。也就是说,在最终操作之后,不能再次使用流,也不能再使用任何中间操作,否则将抛出异常:
java.lang.IllegalStateException: stream has already been operated upon or closed
@@ -145,7 +145,7 @@ Stream 提供了方法 'forEach' 来迭代流中的每个数据。以下代码
count用来统计流中的元素个数。
- List strings = Arrays.asList("Hollis", "HollisChuang", "hollis","Hollis666", "Hello", "HelloWorld", "Hollis");
+ List strings = Arrays.asList("Hollis", "HollisChuang", "hollis", "Hollis666", "Hello", "HelloWorld", "Hollis");
System.out.println(strings.stream().count());
//7
@@ -160,23 +160,23 @@ collect就是一个归约操作,可以接受各种做法作为参数,将流
//Hollis, HollisChuang, Hollis666, Hollis
-接下来,我们还是使用一张图,来演示下,前文的例子中,当一个Stream先后通过filter、map、sort、limit以及distinct处理后会,在分别使用不同的最终操作可以得到怎样的结果:
+接下来,我们还是使用一张图,来演示下,前文的例子中,当一个Stream先后通过filter、map、sort、limit以及distinct处理后,在分别使用不同的最终操作可以得到怎样的结果:
下图,展示了文中介绍的所有操作的位置、输入、输出以及使用一个案例展示了其结果。 ![][6]
### 总结
-本文介绍了Java 8中的Stream 的用途,优点等。还接受了Stream的几种用法,分别是Stream创建、中间操作和最终操作。
+本文介绍了Java 8中的Stream 的用途,优点等。还介绍了Stream的几种用法,分别是Stream创建、中间操作和最终操作。
Stream的创建有两种方式,分别是通过集合类的stream方法、通过Stream的of方法。
Stream的中间操作可以用来处理Stream,中间操作的输入和输出都是Stream,中间操作可以是过滤、转换、排序等。
-Stream的最终操作可以将Stream转成其他形式,如计算出流中元素的个数、将流装换成集合、以及元素的遍历等。
+Stream的最终操作可以将Stream转成其他形式,如计算出流中元素的个数、将流换成集合、以及元素的遍历等。
[1]: https://www.hollischuang.com/wp-content/uploads/2019/03/15521192454583.jpg
[2]: https://www.hollischuang.com/wp-content/uploads/2019/03/15521194075219.jpg
[3]: https://www.hollischuang.com/wp-content/uploads/2019/03/15521194556484.jpg
[4]: https://www.hollischuang.com/wp-content/uploads/2019/03/15521242025506.jpg
[5]: https://www.hollischuang.com/wp-content/uploads/2019/03/15521194606851.jpg
- [6]: https://www.hollischuang.com/wp-content/uploads/2019/03/15521245463720.jpg
\ No newline at end of file
+ [6]: https://www.hollischuang.com/wp-content/uploads/2019/03/15521245463720.jpg
From 8e8b89487ac5731245ac345970f3a261b9702122 Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sat, 26 Dec 2020 22:12:06 +0800
Subject: [PATCH 31/84] fixed wrong characters in
---
docs/basics/java-basic/stream.md | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/docs/basics/java-basic/stream.md b/docs/basics/java-basic/stream.md
index 80a1eab1..c0a26c73 100644
--- a/docs/basics/java-basic/stream.md
+++ b/docs/basics/java-basic/stream.md
@@ -120,7 +120,6 @@ distinct主要用来去重,以下代码片段使用 distinct 对元素进行
### Stream最终操作
-Stream的中间操作得到的结果还是一个Stream,那么如何把一个Stream转换成我们需要的类型呢?比如计算出流中元素的个数、将流换成集合等。这就需要最终操作(terminal operation)
最终操作会消耗流,产生一个最终结果。也就是说,在最终操作之后,不能再次使用流,也不能再使用任何中间操作,否则将抛出异常:
@@ -172,7 +171,7 @@ Stream的创建有两种方式,分别是通过集合类的stream方法、通
Stream的中间操作可以用来处理Stream,中间操作的输入和输出都是Stream,中间操作可以是过滤、转换、排序等。
-Stream的最终操作可以将Stream转成其他形式,如计算出流中元素的个数、将流换成集合、以及元素的遍历等。
+Stream的最终操作可以将Stream转成其他形式,如计算出流中元素的个数、将流转换成集合、以及元素的遍历等。
[1]: https://www.hollischuang.com/wp-content/uploads/2019/03/15521192454583.jpg
[2]: https://www.hollischuang.com/wp-content/uploads/2019/03/15521194075219.jpg
From 0261bf5de79b91558f3bf521b0f7f16d48fa632c Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sat, 26 Dec 2020 22:14:09 +0800
Subject: [PATCH 32/84] fixed wrong characters in
---
docs/basics/java-basic/stream.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/basics/java-basic/stream.md b/docs/basics/java-basic/stream.md
index c0a26c73..76a3d75a 100644
--- a/docs/basics/java-basic/stream.md
+++ b/docs/basics/java-basic/stream.md
@@ -120,6 +120,7 @@ distinct主要用来去重,以下代码片段使用 distinct 对元素进行
### Stream最终操作
+Stream的中间操作得到的结果还是一个Stream,那么如何把一个Stream转换成我们需要的类型呢?比如计算出流中元素的个数、将流装换成集合等。这就需要最终操作(terminal operation)
最终操作会消耗流,产生一个最终结果。也就是说,在最终操作之后,不能再次使用流,也不能再使用任何中间操作,否则将抛出异常:
From 2c1abfcb132b7595306783641f052d8e77193272 Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sat, 26 Dec 2020 22:15:18 +0800
Subject: [PATCH 33/84] fixed wrong characters in
---
docs/basics/java-basic/stream.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/basics/java-basic/stream.md b/docs/basics/java-basic/stream.md
index 76a3d75a..1f95713f 100644
--- a/docs/basics/java-basic/stream.md
+++ b/docs/basics/java-basic/stream.md
@@ -120,7 +120,7 @@ distinct主要用来去重,以下代码片段使用 distinct 对元素进行
### Stream最终操作
-Stream的中间操作得到的结果还是一个Stream,那么如何把一个Stream转换成我们需要的类型呢?比如计算出流中元素的个数、将流装换成集合等。这就需要最终操作(terminal operation)
+Stream的中间操作得到的结果还是一个Stream,那么如何把一个Stream转换成我们需要的类型呢?比如计算出流中元素的个数、将流转换成集合等。这就需要最终操作(terminal operation)
最终操作会消耗流,产生一个最终结果。也就是说,在最终操作之后,不能再次使用流,也不能再使用任何中间操作,否则将抛出异常:
From 341a9daa176ecf6199f71a6ea500cafa84e464ad Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 27 Dec 2020 12:48:39 +0800
Subject: [PATCH 34/84] fixed issue#68: mixed divisor up with devidend
---
docs/basics/java-basic/fail-fast-vs-fail-safe.md | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/docs/basics/java-basic/fail-fast-vs-fail-safe.md b/docs/basics/java-basic/fail-fast-vs-fail-safe.md
index d425685e..0e81f59f 100644
--- a/docs/basics/java-basic/fail-fast-vs-fail-safe.md
+++ b/docs/basics/java-basic/fail-fast-vs-fail-safe.md
@@ -11,14 +11,14 @@
举一个最简单的fail-fast的例子:
public int divide(int divisor,int dividend){
- if(dividend == 0){
- throw new RuntimeException("dividend can't be null");
+ if(divisor == 0){
+ throw new RuntimeException("divisor can't be null");
}
- return divisor/dividend;
+ return dividend/divisor;
}
-上面的代码是一个对两个整数做除法的方法,在divide方法中,我们对被除数做了个简单的检查,如果其值为0,那么就直接抛出一个异常,并明确提示异常原因。这其实就是fail-fast理念的实际应用。
+上面的代码是一个对两个整数做除法的方法,在divide方法中,我们对除数做了个简单的检查,如果其值为0,那么就直接抛出一个异常,并明确提示异常原因。这其实就是fail-fast理念的实际应用。
这样做的好处就是可以预先识别出一些错误情况,一方面可以避免执行复杂的其他代码,另外一方面,这种异常情况被识别之后也可以针对性的做一些单独处理。
@@ -257,4 +257,4 @@ CopyOnWriteArrayList中add/remove等写方法是需要加锁的,目的是为
**所以CopyOnWrite容器是一种读写分离的思想,读和写不同的容器。**而Vector在读写的时候使用同一个容器,读写互斥,同时只能做一件事儿。
[1]: https://www.hollischuang.com/archives/58
- [2]: https://www.hollischuang.com/wp-content/uploads/2019/04/15551448234429.jpg
\ No newline at end of file
+ [2]: https://www.hollischuang.com/wp-content/uploads/2019/04/15551448234429.jpg
From 5c36592fc303a97a7fb1edfcb1326de60a98417c Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 27 Dec 2020 13:01:18 +0800
Subject: [PATCH 35/84] fixed issue#85: fixed wrong format
---
docs/basics/java-basic/syntactic-sugar.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/basics/java-basic/syntactic-sugar.md b/docs/basics/java-basic/syntactic-sugar.md
index f1dc7f5b..8c085333 100644
--- a/docs/basics/java-basic/syntactic-sugar.md
+++ b/docs/basics/java-basic/syntactic-sugar.md
@@ -682,7 +682,7 @@ Java里,对于文件操作IO流、数据库连接等开销非常昂贵的资
}
-上面这段代码,有两个重载的函数,因为他们的参数类型不同,一个是List另一个是List ,但是,这段代码是编译通不过的。因为我们前面讲过,参数List和List编译之后都被擦除了,变成了一样的原生类型List,擦除动作导致这两个方法的特征签名变得一模一样。
+上面这段代码,有两个重载的函数,因为他们的参数类型不同,一个是`List`另一个是`List` ,但是,这段代码是编译通不过的。因为我们前面讲过,参数`List`和`List`编译之后都被擦除了,变成了一样的原生类型List,擦除动作导致这两个方法的特征签名变得一模一样。
**二、当泛型遇到catch** 泛型的类型参数不能用在Java异常处理的catch语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除,JVM是无法区分两个异常类型`MyException`和`MyException`的
From d2185fe344dc31ec248fcfda66cb29c182e5ba1e Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 27 Dec 2020 13:12:40 +0800
Subject: [PATCH 36/84] fixed issue#93: fixed wrong reference
---
docs/basics/object-oriented/why-pass-by-reference.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/basics/object-oriented/why-pass-by-reference.md b/docs/basics/object-oriented/why-pass-by-reference.md
index 58a5f027..0eb2f63d 100644
--- a/docs/basics/object-oriented/why-pass-by-reference.md
+++ b/docs/basics/object-oriented/why-pass-by-reference.md
@@ -2,7 +2,7 @@
### 辟谣时间
-关于这个问题,在[StackOverflow][2]上也引发过广泛的讨论,看来很多程序员对于这个问题的理解都不尽相同,甚至很多人理解的是错误的。还有的人可能知道Java中的参数传递是值传递,但是说不出来为什么。
+关于这个问题,在[StackOverflow][5]上也引发过广泛的讨论,看来很多程序员对于这个问题的理解都不尽相同,甚至很多人理解的是错误的。还有的人可能知道Java中的参数传递是值传递,但是说不出来为什么。
在开始深入讲解之前,有必要纠正一下大家以前的那些错误看法了。如果你有以下想法,那么你有必要好好阅读本文。
@@ -225,4 +225,4 @@ OK,以上就是本文的全部内容,不知道本文是否帮助你解开了
[3]: https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html
[4]: https://en.wikipedia.org/wiki/Evaluation_strategy
[5]: https://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value
- [6]: https://blog.penjee.com/passing-by-value-vs-by-reference-java-graphical/
\ No newline at end of file
+ [6]: https://blog.penjee.com/passing-by-value-vs-by-reference-java-graphical/
From 3488e56e2e0723e249e8cf95eb3e969abbe7daa2 Mon Sep 17 00:00:00 2001
From: "hollis.zhl"
Date: Wed, 6 Jan 2021 15:55:33 +0800
Subject: [PATCH 37/84] =?UTF-8?q?=E8=B0=83=E6=95=B4=E9=A6=96=E9=A1=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 18 +++++++++---------
docs/README.md | 14 ++++++++------
docs/menu.md | 17 +++++++++--------
3 files changed, 26 insertions(+), 23 deletions(-)
diff --git a/README.md b/README.md
index 38c77f05..89b8bccd 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,15 @@
欢迎大家参与共建~
+### 联系我们
+
+欢迎关注作者的公众号,可以直接后台留言。
+
+
+
+*公众号后台回复:"成神导图",即可获取《Java工程师成神之路最新版思维导图》*
+
+
### 在线阅读地址
GitHub Pages 完整阅读:[进入](https://hollischuang.github.io/toBeTopJavaer/)
@@ -43,12 +52,3 @@ Hollis,阿里巴巴技术专家,51CTO专栏作家,CSDN博客专家,掘
请直接在[GitHub](https://github.com/hollischuang/toBeTopJavaer)上以issue或者PR的形式提出
如果本项目中的内容侵犯了您的任何权益,欢迎通过邮箱(hollischuang@gmail)与我联系
-
-
-### 联系我们
-
-欢迎关注作者的公众号,可以直接后台留言。
-
-
-
-如果获取《Java工程师成神之路最新版思维导图》,请在公众号后台回复:"成神导图"
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
index 7d6b89ff..57416c18 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -15,6 +15,14 @@
欢迎大家参与共建~
+### 联系我们
+
+欢迎关注作者的公众号,可以直接后台留言。
+
+
+
+*公众号后台回复:"成神导图",即可获取《Java工程师成神之路最新版思维导图》*
+
### 关于作者
Hollis,阿里巴巴技术专家,51CTO专栏作家,CSDN博客专家,掘金优秀作者,《程序员的三门课》联合作者,《Java工程师成神之路》系列文章作者;热衷于分享计算机编程相关技术,博文全网阅读量上千万。
@@ -43,12 +51,6 @@ GitHub Pages 完整阅读:[进入](https://hollischuang.github.io/toBeTopJavae
Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer) (国内访问速度较快)
-### 联系我们
-
-欢迎关注作者的公众号,可以直接后台留言。
-
-
-如果获取《Java工程师成神之路最新版思维导图》,请在公众号后台回复:"成神导图"
### 开始阅读
\ No newline at end of file
diff --git a/docs/menu.md b/docs/menu.md
index a23dc246..3ea819c5 100644
--- a/docs/menu.md
+++ b/docs/menu.md
@@ -15,6 +15,15 @@
欢迎大家参与共建~
+### 联系我们
+
+欢迎关注作者的公众号,可以直接后台留言。
+
+
+
+*公众号后台回复:"成神导图",即可获取《Java工程师成神之路最新版思维导图》*
+
+
### 关于作者
Hollis,阿里巴巴技术专家,51CTO专栏作家,CSDN博客专家,掘金优秀作者,《程序员的三门课》联合作者,《Java工程师成神之路》系列文章作者;热衷于分享计算机编程相关技术,博文全网阅读量上千万。
@@ -43,14 +52,6 @@ GitHub Pages 完整阅读:[进入](https://hollischuang.github.io/toBeTopJavae
Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer) (国内访问速度较快)
-### 联系我们
-
-欢迎关注作者的公众号,可以直接后台留言。
-
-
-
-如果获取《Java工程师成神之路最新版思维导图》,请在公众号后台回复:"成神导图"
-
### 目录
From 829794aaa4dd699ccaead8b24e999e7fba7a6960 Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 10 Jan 2021 20:56:54 +0800
Subject: [PATCH 38/84] fixed wrong characters in
---
docs/basics/java-basic/fail-fast-vs-fail-safe.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/basics/java-basic/fail-fast-vs-fail-safe.md b/docs/basics/java-basic/fail-fast-vs-fail-safe.md
index 0e81f59f..c1917387 100644
--- a/docs/basics/java-basic/fail-fast-vs-fail-safe.md
+++ b/docs/basics/java-basic/fail-fast-vs-fail-safe.md
@@ -111,9 +111,9 @@ CMException,当方法检测到对象的并发修改,但不允许这种修改
}
-如上,在该方法中对modCount和expectedModCount进行了比较,如果二者不想等,则抛出CMException。
+如上,在该方法中对modCount和expectedModCount进行了比较,如果二者不相等,则抛出CMException。
-那么,modCount和expectedModCount是什么?是什么原因导致他们的值不想等的呢?
+那么,modCount和expectedModCount是什么?是什么原因导致他们的值不相等的呢?
modCount是ArrayList中的一个成员变量。它表示该集合实际被修改的次数。
From e49b76f7f3a992c7b3e1f090d52a4b6b756cd0d0 Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 10 Jan 2021 21:24:04 +0800
Subject: [PATCH 39/84] fixed wrong characters in
---
docs/basics/java-basic/enum-usage.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/basics/java-basic/enum-usage.md b/docs/basics/java-basic/enum-usage.md
index 32c7029c..3e27f168 100644
--- a/docs/basics/java-basic/enum-usage.md
+++ b/docs/basics/java-basic/enum-usage.md
@@ -42,9 +42,9 @@
}
-程序`getChineseSeason(Season.SPRING)`是我们预期的使用方法。可`getChineseSeason(5)`显然就不是了,而且编译很通过,在运行时会出现什么情况,我们就不得而知了。这显然就不符合`Java`程序的类型安全。
+程序`getChineseSeason(Season.SPRING)`是我们预期的使用方法。可`getChineseSeason(5)`显然就不是了,而且编译会通过,在运行时会出现什么情况,我们就不得而知了。这显然就不符合`Java`程序的类型安全。
-接下来我们来考虑一下这种模式的**可读性**。使用枚举的大多数场合,我都需要方便得到枚举类型的字符串表达式。如果将`int`枚举常量打印出来,我们所见到的就是一组数字,这是没什么太大的用处。我们可能会想到使用`String`常量代替`int`常量。虽然它为这些常量提供了可打印的字符串,但是它会导致性能问题,因为它依赖于字符串的比较操作,所以这种模式也是我们不期望的。 从**类型安全性**和**程序可读性**两方面考虑,`int`和`String`枚举模式的缺点就显露出来了。幸运的是,从`Java1.5`发行版本开始,就提出了另一种可以替代的解决方案,可以避免`int`和`String`枚举模式的缺点,并提供了许多额外的好处。那就是枚举类型(`enum type`)。接下来的章节将介绍枚举类型的定义、特征、应用场景和优缺点。
+接下来我们来考虑一下这种模式的**可读性**。使用枚举的大多数场合,我都需要方便得到枚举类型的字符串表达式。如果将`int`枚举常量打印出来,我们所见到的就是一组数字,这没什么太大的用处。我们可能会想到使用`String`常量代替`int`常量。虽然它为这些常量提供了可打印的字符串,但是它会导致性能问题,因为它依赖于字符串的比较操作,所以这种模式也是我们不期望的。 从**类型安全性**和**程序可读性**两方面考虑,`int`和`String`枚举模式的缺点就显露出来了。幸运的是,从`Java1.5`发行版本开始,就提出了另一种可以替代的解决方案,可以避免`int`和`String`枚举模式的缺点,并提供了许多额外的好处。那就是枚举类型(`enum type`)。接下来的章节将介绍枚举类型的定义、特征、应用场景和优缺点。
### 2 定义
From 5da5d7614e6a30dc63cc592f974f5197ae080f90 Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 10 Jan 2021 21:25:51 +0800
Subject: [PATCH 40/84] fixed wrong characters in
---
docs/basics/java-basic/enum-impl.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/basics/java-basic/enum-impl.md b/docs/basics/java-basic/enum-impl.md
index 51a18c1f..190cdd67 100644
--- a/docs/basics/java-basic/enum-impl.md
+++ b/docs/basics/java-basic/enum-impl.md
@@ -40,6 +40,6 @@ Java SE5提供了一种新的类型-Java的枚举类型,关键字enum可以将
}
}
-通过反编译后代码我们可以看到,public final class T extends Enum,说明,该类是继承了Enum类的,同时final关键字告诉我们,这个类也是不能被继承的。
+通过反编译代码我们可以看到,public final class T extends Enum,说明,该类是继承了Enum类的,同时final关键字告诉我们,这个类也是不能被继承的。
-当我们使用enmu来定义一个枚举类型的时候,编译器会自动帮我们创建一个final类型的类继承Enum类,所以枚举类型不能被继承。
\ No newline at end of file
+当我们使用enmu来定义一个枚举类型的时候,编译器会自动帮我们创建一个final类型的类继承Enum类,所以枚举类型不能被继承。
From 0c30a810fd3ce068cb4bd18704d1987b1c888443 Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 10 Jan 2021 21:33:09 +0800
Subject: [PATCH 41/84] fixed wrong characters in
---
docs/basics/java-basic/enum-singleton.md | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/docs/basics/java-basic/enum-singleton.md b/docs/basics/java-basic/enum-singleton.md
index 146fc3f0..089bd57a 100644
--- a/docs/basics/java-basic/enum-singleton.md
+++ b/docs/basics/java-basic/enum-singleton.md
@@ -2,12 +2,12 @@
单例相关文章一览:
-[设计模式(二)——单例模式][1]
-[设计模式(三)——JDK中的那些单例][2]
-[单例模式的七种写法][3]
-[单例与序列化的那些事儿][4]
-[不使用synchronized和lock,如何实现一个线程安全的单例?][5]
-[不使用synchronized和lock,如何实现一个线程安全的单例?(二)][6]
+[设计模式(二)——单例模式][1]
+[设计模式(三)——JDK中的那些单例][2]
+[单例模式的七种写法][3]
+[单例与序列化的那些事儿][4]
+[不使用synchronized和lock,如何实现一个线程安全的单例?][5]
+[不使用synchronized和lock,如何实现一个线程安全的单例?(二)][6]
如果你对单例不是很了解,或者对于单例的线程安全问题以及序列化会破坏单例等问题不是很清楚,可以先阅读以上文章。上面六篇文章看完之后,相信你一定会对单例模式有更多,更深入的理解。
@@ -15,7 +15,7 @@
### 哪种写单例的方式最好
-在StakcOverflow中,有一个关于[What is an efficient way to implement a singleton pattern in Java?][7]的讨论:
+在StackOverflow中,有一个关于[What is an efficient way to implement a singleton pattern in Java?][7]的讨论:
@@ -78,7 +78,7 @@
通过将定义好的枚举[反编译][9],我们就能发现,其实枚举在经过`javac`的编译之后,会被转换成形如`public final class T extends Enum`的定义。
-而且,枚举中的各个枚举项同事通过`static`来定义的。如:
+而且,枚举中的各个枚举项同时通过`static`来定义的。如:
public enum T {
SPRING,SUMMER,AUTUMN,WINTER;
@@ -145,4 +145,4 @@
[9]: http://www.hollischuang.com/archives/58
[10]: http://www.hollischuang.com/archives/199
[11]: http://www.hollischuang.com/archives/201
- [12]: https://docs.oracle.com/javase/7/docs/platform/serialization/spec/serial-arch.html#6469
\ No newline at end of file
+ [12]: https://docs.oracle.com/javase/7/docs/platform/serialization/spec/serial-arch.html#6469
From afd86007ea16ca5306c97758a2d03f76dbaea47a Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 10 Jan 2021 21:44:12 +0800
Subject: [PATCH 42/84] fixed wrong characters in
---
docs/basics/java-basic/enum-serializable.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/basics/java-basic/enum-serializable.md b/docs/basics/java-basic/enum-serializable.md
index b588b37e..ff85c4d6 100644
--- a/docs/basics/java-basic/enum-serializable.md
+++ b/docs/basics/java-basic/enum-serializable.md
@@ -91,7 +91,7 @@
**2\. 枚举自己处理序列化**
-> 我们知道,以前的所有的单例模式都有一个比较大的问题,就是一旦实现了Serializable接口之后,就不再是单例得了,因为,每次调用 readObject()方法返回的都是一个新创建出来的对象,有一种解决办法就是使用readResolve()方法来避免此事发生。但是,**为了保证枚举类型像Java规范中所说的那样,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定。**原文如下:
+> 我们知道,以前的所有的单例模式都有一个比较大的问题,就是一旦实现了Serializable接口之后,就不再是单例的了,因为,每次调用 readObject()方法返回的都是一个新创建出来的对象,有一种解决办法就是使用readResolve()方法来避免此事发生。但是,**为了保证枚举类型像Java规范中所说的那样,每一个枚举类型及其定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定。**原文如下:
>
> > Enum constants are serialized differently than ordinary serializable or externalizable objects. The serialized form of an enum constant consists solely of its name; field values of the constant are not present in the form. To serialize an enum constant, ObjectOutputStream writes the value returned by the enum constant's name method. To deserialize an enum constant, ObjectInputStream reads the constant name from the stream; the deserialized constant is then obtained by calling the java.lang.Enum.valueOf method, passing the constant's enum type along with the received constant name as arguments. Like other serializable or externalizable objects, enum constants can function as the targets of back references appearing subsequently in the serialization stream. The process by which enum constants are serialized cannot be customized: any class-specific writeObject, readObject, readObjectNoData, writeReplace, and readResolve methods defined by enum types are ignored during serialization and deserialization. Similarly, any serialPersistentFields or serialVersionUID field declarations are also ignored--all enum types have a fixedserialVersionUID of 0L. Documenting serializable fields and data for enum types is unnecessary, since there is no variation in the type of data sent.
>
From d6ad7e28f86d89b91e93ebac0f4333f225ade3c0 Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 10 Jan 2021 22:04:55 +0800
Subject: [PATCH 43/84] fixed wrong characters in
---
docs/basics/java-basic/synchronized-vs-asynchronization.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/basics/java-basic/synchronized-vs-asynchronization.md b/docs/basics/java-basic/synchronized-vs-asynchronization.md
index d3cded25..79fd30cf 100644
--- a/docs/basics/java-basic/synchronized-vs-asynchronization.md
+++ b/docs/basics/java-basic/synchronized-vs-asynchronization.md
@@ -20,5 +20,5 @@
3 老张把响水壶放到火上,一直在水壶旁等着水开。(异步阻塞)
4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)
-1和2的区别是,调用方在得到返回之前所做的事情不一行。
-1和3的区别是,被调用方对于烧水的处理不一样。
\ No newline at end of file
+1和2的区别是,调用方在得到返回之前所做的事情不一样。
+1和3的区别是,被调用方对于烧水的处理不一样。
From 7faabc00ff810fe45aa394a56d4ea57fcce88781 Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 10 Jan 2021 22:15:09 +0800
Subject: [PATCH 44/84] fixed wrong characters in
---
docs/basics/java-basic/linux-io.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/basics/java-basic/linux-io.md b/docs/basics/java-basic/linux-io.md
index 79fc8a6c..051973b7 100644
--- a/docs/basics/java-basic/linux-io.md
+++ b/docs/basics/java-basic/linux-io.md
@@ -49,7 +49,7 @@ while(true){
而多路复用IO模式,通过一个线程就可以管理多个socket,只有当socket真正有读写事件发生才会占用资源来进行实际的读写操作。因此,多路复用IO比较适合连接数比较多的情况。
-另外多路复用IO为何比非阻塞IO模型的效率高是因为在非阻塞IO中,不断地询问socket状态时通过用户线程去进行的,而在多路复用IO中,轮询每个socket状态是内核在进行的,这个效率要比用户线程要高的多。
+另外多路复用IO为何比非阻塞IO模型的效率高是因为在非阻塞IO中,不断地询问socket状态是通过用户线程去进行的,而在多路复用IO中,轮询每个socket状态是内核在进行的,这个效率要比用户线程要高的多。
不过要注意的是,多路复用IO模型是通过轮询的方式来检测是否有事件到达,并且对到达的事件逐一进行响应。因此对于多路复用IO模型来说,一旦事件响应体很大,那么就会导致后续的事件迟迟得不到处理,并且会影响新的事件轮询。
@@ -60,10 +60,10 @@ while(true){
在信号驱动IO模型中,当用户线程发起一个IO请求操作,会给对应的socket注册一个信号函数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号之后,便在信号函数中调用IO读写操作来进行实际的IO请求操作。
### 异步IO模型
-异步IO模型是比较理想的IO模型,在异步IO模型中,当用户线程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从内核的角度,当它受到一个asynchronous read之后,它会立刻返回,说明read请求已经成功发起了,因此不会对用户线程产生任何block。然后,内核会等待数据准备完成,然后将数据拷贝到用户线程,当这一切都完成之后,内核会给用户线程发送一个信号,告诉它read操作完成了。也就说用户线程完全不需要实际的整个IO操作是如何进行的,只需要先发起一个请求,当接收内核返回的成功信号时表示IO操作已经完成,可以直接去使用数据了。
+异步IO模型是比较理想的IO模型,在异步IO模型中,当用户线程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从内核的角度,当它受到一个asynchronous read之后,它会立刻返回,说明read请求已经成功发起了,因此不会对用户线程产生任何block。然后,内核会等待数据准备完成,然后将数据拷贝到用户线程,当这一切都完成之后,内核会给用户线程发送一个信号,告诉它read操作完成了。也就说用户线程完全不需要知道实际的整个IO操作是如何进行的,只需要先发起一个请求,当接收内核返回的成功信号时表示IO操作已经完成,可以直接去使用数据了。
-也就说在异步IO模型中,IO操作的两个阶段都不会阻塞用户线程,这两个阶段都是由内核自动完成,然后发送一个信号告知用户线程操作已完成。用户线程中不需要再次调用IO函数进行具体的读写。这点是和信号驱动模型有所不同的,在信号驱动模型中,当用户线程接收到信号表示数据已经就绪,然后需要用户线程调用IO函数进行实际的读写操作;而在异步IO模型中,收到信号表示IO操作已经完成,不需要再在用户线程中调用iO函数进行实际的读写操作。
+也就说在异步IO模型中,IO操作的两个阶段都不会阻塞用户线程,这两个阶段都是由内核自动完成,然后发送一个信号告知用户线程操作已完成。用户线程中不需要再次调用IO函数进行具体的读写。这点是和信号驱动模型有所不同的,在信号驱动模型中,当用户线程接收到信号表示数据已经就绪,然后需要用户线程调用IO函数进行实际的读写操作;而在异步IO模型中,收到信号表示IO操作已经完成,不需要再在用户线程中调用IO函数进行实际的读写操作。
注意,异步IO是需要操作系统的底层支持,在Java 7中,提供了Asynchronous IO。
-前面四种IO模型实际上都属于同步IO,只有最后一种是真正的异步IO,因为无论是多路复用IO还是信号驱动模型,IO操作的第2个阶段都会引起用户线程阻塞,也就是内核进行数据拷贝的过程都会让用户线程阻塞。
\ No newline at end of file
+前面四种IO模型实际上都属于同步IO,只有最后一种是真正的异步IO,因为无论是多路复用IO还是信号驱动模型,IO操作的第2个阶段都会引起用户线程阻塞,也就是内核进行数据拷贝的过程都会让用户线程阻塞。
From d8817191a36b548683139c5c21a6423250313a8c Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 10 Jan 2021 22:24:11 +0800
Subject: [PATCH 45/84] fixed wrong characters in
---
docs/basics/java-basic/bio-vs-nio-vs-aio.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/basics/java-basic/bio-vs-nio-vs-aio.md b/docs/basics/java-basic/bio-vs-nio-vs-aio.md
index 9a976240..7349cd3d 100644
--- a/docs/basics/java-basic/bio-vs-nio-vs-aio.md
+++ b/docs/basics/java-basic/bio-vs-nio-vs-aio.md
@@ -1,5 +1,5 @@
### IO
-什么是IO? 它是指计算机与外部世界或者一个程序与计算机的其余部分的之间的接口。它对于任何计算机系统都非常关键,因而所有 I/O 的主体实际上是内置在操作系统中的。单独的程序一般是让系统为它们完成大部分的工作。
+什么是IO? 它是指计算机与外部世界或者一个程序与计算机的其余部分之间的接口。它对于任何计算机系统都非常关键,因而所有 I/O 的主体实际上是内置在操作系统中的。单独的程序一般是让系统为它们完成大部分的工作。
在 Java 编程中,直到最近一直使用 流 的方式完成 I/O。所有 I/O 都被视为单个的字节的移动,通过一个称为 Stream 的对象一次移动一个字节。流 I/O 用于与外部世界接触。它也在内部使用,用于将对象转换为字节,然后再转换回对象。
@@ -25,7 +25,7 @@ Java AIO即Async非阻塞,是异步非阻塞的IO。
BIO (Blocking I/O):同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。这里假设一个烧开水的场景,有一排水壶在烧开水,BIO的工作模式就是, 叫一个线程停留在一个水壶那,直到这个水壶烧开,才去处理下一个水壶。但是实际上线程在等待水壶烧开的时间段什么都没有做。
-NIO (New I/O):同时支持阻塞与非阻塞模式,但这里我们以其同步非阻塞I/O模式来说明,那么什么叫做同步非阻塞?如果还拿烧开水来说,NIO的做法是叫一个线程不断的轮询每个水壶的状态,看看是否有水壶的状态发生了改变,从而进行下一步的操作。
+NIO (New I/O):同时支持阻塞与非阻塞模式,但这里我们以其同步非阻塞I/O模式来说明,那么什么叫做同步非阻塞?如果还拿烧开水来说,NIO的做法是叫一个线程不断地轮询每个水壶的状态,看看是否有水壶的状态发生了改变,从而进行下一步的操作。
AIO ( Asynchronous I/O):异步非阻塞I/O模型。异步非阻塞与同步非阻塞的区别在哪里?异步非阻塞无需一个线程去轮询所有IO操作的状态改变,在相应的状态改变后,系统会通知对应的线程来处理。对应到烧开水中就是,为每个水壶上面装了一个开关,水烧开之后,水壶会自动通知我水烧开了。
@@ -225,4 +225,4 @@ public class WriteToFile {
}
}
-```
\ No newline at end of file
+```
From da04e0deff22f8aa2a59f434901f329ee2fc0a23 Mon Sep 17 00:00:00 2001
From: "hollis.zhl"
Date: Sun, 17 Jan 2021 16:04:00 +0800
Subject: [PATCH 46/84] =?UTF-8?q?=E9=83=A8=E5=88=86=E7=9F=A5=E8=AF=86?=
=?UTF-8?q?=E7=82=B9=E5=AE=8C=E5=96=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/basics/java-basic/float.md | 54 ++++++++--
docs/basics/java-basic/length-of-string.md | 2 +-
.../basics/object-oriented/characteristics.md | 98 ++++++++++++++++++-
.../object-oriented-vs-procedure-oriented.md | 28 +++---
docs/basics/object-oriented/polymorphism.md | 9 +-
5 files changed, 165 insertions(+), 26 deletions(-)
diff --git a/docs/basics/java-basic/float.md b/docs/basics/java-basic/float.md
index f7cf14af..c1418645 100644
--- a/docs/basics/java-basic/float.md
+++ b/docs/basics/java-basic/float.md
@@ -1,11 +1,53 @@
-在计算机科学中,浮点是一种对于实数的近似值数值表现法,由一个有效数字(即尾数)加上幂数来表示,通常是乘以某个基数的整数次指数得到。以这种表示法表示的数值,称为浮点数(floating-point number)。
+我们知道,计算机的数字的存储和运算都是通过二进制进行的,对于,十进制整数转换为二进制整数采用"除2取余,逆序排列"法
-计算机使用浮点数运算的主因,在于电脑使用二进位制的运算。例如:4÷2=2,4的二进制表示为100、2的二进制表示为010,在二进制中,相当于退一位数(100 -> 010)。
+具体做法是:
-1的二进制是01,1.0/2=0.5,那么,0.5的二进制表示应该为(0.1),以此类推,0.25的二进制表示为0.01,所以,并不是说所有的十进制小数都能准确的用二进制表示出来,如0.1,因此只能使用近似值的方式表达。
+* 用2整除十进制整数,可以得到一个商和余数;
+* 再用2去除商,又会得到一个商和余数,如此进行,直到商为小于1时为止
+* 然后把先得到的余数作为二进制数的低位有效位,后得到的余数作为二进制数的高位有效位,依次排列起来。
-也就是说,,十进制的小数在计算机中是由一个整数或定点数(即尾数)乘以某个基数(计算机中通常是2)的整数次幂得到的,这种表示方法类似于基数为10的科学计数法。
+如,我们想要把127转换成二进制,做法如下:
-一个浮点数a由两个数m和e来表示:a = m × be。在任意一个这样的系统中,我们选择一个基数b(记数系统的基)和精度p(即使用多少位来存储)。m(即尾数)是形如±d.ddd...ddd的p位数(每一位是一个介于0到b-1之间的整数,包括0和b-1)。如果m的第一位是非0整数,m称作正规化的。有一些描述使用一个单独的符号位(s 代表+或者-)来表示正负,这样m必须是正的。e是指数。
+
-位(bit)是衡量浮点数所需存储空间的单位,通常为32位或64位,分别被叫作单精度和双精度。
\ No newline at end of file
+那么,十进制小数转换成二进制小数,又该如何计算呢?
+
+
+十进制小数转换成二进制小数采用"乘2取整,顺序排列"法。
+
+具体做法是:
+
+* 用2乘十进制小数,可以得到积
+* 将积的整数部分取出,再用2乘余下的小数部分,又得到一个积
+* 再将积的整数部分取出,如此进行,直到积中的小数部分为零,此时0或1为二进制的最后一位。或者达到所要求的精度为止。
+
+
+如尝试将0.625转成二进制:
+
+
+
+但是0.625是一个特列,用同样的算法,请计算下0.1对应的二进制是多少:
+
+
+
+我们发现,0.1的二进制表示中出现了无限循环的情况,也就是(0.1)10 = (0.000110011001100…)2
+
+这种情况,计算机就没办法用二进制精确的表示0.1了。
+
+所以,为了解决部分小数无法使用二进制精确表示的问题,于是就有了IEEE 754规范。
+
+IEEE二进制浮点数算术标准(IEEE 754)是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用。
+
+>浮点数和小数并不是完全一样的,计算机中小数的表示法,其实有定点和浮点两种。因为在位数相同的情况下,定点数的表示范围要比浮点数小。所以在计算机科学中,使用浮点数来表示实数的近似值。
+
+IEEE 754规定了四种表示浮点数值的方式:单精确度(32位)、双精确度(64位)、延伸单精确度(43比特以上,很少使用)与延伸双精确度(79比特以上,通常以80位实现)。
+
+其中最常用的就是32位单精度浮点数和64位双精度浮点数。
+
+IEEE并没有解决小数无法精确表示的问题,只是提出了一种使用近似值表示小数的方式,并且引入了精度的概念。
+
+一个浮点数a由两个数m和e来表示:a = m × b^e。
+
+在任意一个这样的系统中,我们选择一个基数b(记数系统的基)和精度p(即使用多少位来存储)。m(即尾数)是形如±d.ddd...ddd的p位数(每一位是一个介于0到b-1之间的整数,包括0和b-1)。
+
+如果m的第一位是非0整数,m称作规格化的。有一些描述使用一个单独的符号位(s 代表+或者-)来表示正负,这样m必须是正的。e是指数。
\ No newline at end of file
diff --git a/docs/basics/java-basic/length-of-string.md b/docs/basics/java-basic/length-of-string.md
index 9ea59f2c..3bee1665 100644
--- a/docs/basics/java-basic/length-of-string.md
+++ b/docs/basics/java-basic/length-of-string.md
@@ -87,7 +87,7 @@ CONSTANT_Utf8_info 结构用于表示字符串常量的值:
代码中可以看出,当参数类型为String,并且长度大于等于65535的时候,就会导致编译失败。
-这个地方大家可以尝试着debug一下javac的编译过程(视频中有对java的编译过程进行debug的方法),也可以发现这个地方会报错。
+这个地方大家可以尝试着debug一下javac的编译过程,也可以发现这个地方会报错。
如果我们尝试以65534个字符定义字符串,则会发现可以正常编译。
diff --git a/docs/basics/object-oriented/characteristics.md b/docs/basics/object-oriented/characteristics.md
index 5b3b3bc0..6f722cd2 100644
--- a/docs/basics/object-oriented/characteristics.md
+++ b/docs/basics/object-oriented/characteristics.md
@@ -2,14 +2,104 @@
### 封装(Encapsulation)
-所谓封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。封装是面向对象的特征之一,是对象和类概念的主要特性。简单的说,一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。
+所谓封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
+简单的说,一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。
+
+#### 封装举例
+
+如我们想要定义一个矩形,先定义一个Rectangle类,并其中通过封装的手段放入一些必备数据
+
+ /**
+ * 矩形
+ */
+ class Rectangle {
+
+ /**
+ * 设置矩形的长度和宽度
+ */
+ public Rectangle(int length, int width) {
+ this.length = length;
+ this.width = width;
+ }
+
+ /**
+ * 长度
+ */
+ private int length;
+
+ /**
+ * 宽度
+ */
+ private int width;
+
+ /**
+ * 获得矩形面积
+ *
+ * @return
+ */
+ public int area() {
+ return this.length * this.width;
+ }
+ }
+
### 继承(Inheritance)
-继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。继承概念的实现方式有二类:实现继承与接口继承。实现继承是指直接使用基类的属性和方法而无需额外编码的能力;接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;
+继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
+
+通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。
+
+继承概念的实现方式有二类:实现继承与接口继承。实现继承是指直接使用基类的属性和方法而无需额外编码的能力;接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;
+
+#### 继承举例
+
+我们想要定义一个正方形,因为已经有了矩形,所以我们可以直接继承Rectangle类,因为正方形是长方形的一种特例。
+
+
+ /**
+ * 正方形,继承自矩形
+ */
+ class Square extends Rectangle {
+
+ /**
+ * 设置正方形边长
+ *
+ * @param length
+ */
+ public Square(int length) {
+ super(length, length);
+ }
+ }
+
+
+以上是继承的用法,还有一种实现接口的方式,如我们定义一个Shape类,表示图形:
+
+ /**
+ * 图形
+ */
+ interface Shape{
+ public int area();
+ }
+
+
+那么,矩形Rectangle类可以使用implements实现这个接口
+
+ /**
+ * 矩形
+ */
+ class Rectangle implements Shape{
+
+ }
+
+实现接口后,Rectangle需要实现Shape中的area方法。
+
### 多态(Polymorphism)
-所谓多态就是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。
+所谓多态就是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。
+
+这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。
+
+最常见的多态就是将子类传入父类参数中,运行时调用父类方法时通过传入的子类决定具体的内部结构或行为。
-最常见的多态就是将子类传入父类参数中,运行时调用父类方法时通过传入的子类决定具体的内部结构或行为。
\ No newline at end of file
+关于多态的例子,我们后面的章节中还会深入展开。
\ No newline at end of file
diff --git a/docs/basics/object-oriented/object-oriented-vs-procedure-oriented.md b/docs/basics/object-oriented/object-oriented-vs-procedure-oriented.md
index d10394c3..dd8232a1 100644
--- a/docs/basics/object-oriented/object-oriented-vs-procedure-oriented.md
+++ b/docs/basics/object-oriented/object-oriented-vs-procedure-oriented.md
@@ -1,8 +1,14 @@
+面向对象和面向过程是两种软件开发方法,或者说是两种不同的开发范式。
### 什么是面向过程?
-#### 概述: 自顶而下的编程模式.
+“面向过程”(Procedure Oriented)是一种以过程为中心的编程思想,是一种自顶而下的编程模式。
+
+
+最典型的面向过程的编程语言就是C语言。
+
+#### 概述
把问题分解成一个一个步骤,每个步骤用函数实现,依次调用即可。
@@ -13,27 +19,25 @@
### 什么是面向对象?
-#### 概述: 将事务高度抽象化的编程模式.
+面向对象程序设计的雏形,早在出现在1960年的Simula语言中,当时的程序设计领域正面临着一种危机:在软硬件环境逐渐复杂的情况下,软件如何得到良好的维护?
-将问题分解成一个一个步骤,对每个步骤进行相应的抽象,形成对象,通过不同对象之间的调用,组合解决问题。
+面向对象程序设计在某种程度上通过强调可重复性解决了这一问题。
-就是说,在进行面向对象进行编程的时候,要把属性、行为等封装成对象,然后基于这些对象及对象的能力进行业务逻辑的实现。
+目前较为流行的面向对象语言主要有Java、C#、C++、Python、Ruby、PHP等
-比如:想要造一辆车,上来要先把车的各种属性定义出来,然后抽象成一个Car类。
+面向对象是一种将事务高度抽象化的编程模式
-### 举例说明区别
+#### 概述:
-同样一个象棋设计.
+将问题分解成一个一个步骤,对每个步骤进行相应的抽象,形成对象,通过不同对象之间的调用,组合解决问题。
-面向对象:创建黑白双方的对象负责演算,棋盘的对象负责画布,规则的对象负责判断,例子可以看出,面向对象更重视不重复造轮子,即创建一次,重复使用.
+就是说,在进行面向对象进行编程的时候,要把属性、行为等封装成对象,然后基于这些对象及对象的能力进行业务逻辑的实现。
-面向过程:开始—黑走—棋盘—判断—白走—棋盘—判断—循环。只需要关注每一步怎么实现即可.
+比如:想要造一辆车,上来要先把车的各种属性定义出来,然后抽象成一个Car类。
-### 优劣对比
-面向对象:占用资源相对高,速度相对慢
+面向对象具有三大基本特征和五大基本原则,这一点在后面的章节中展开介绍。
-面向过程:占用资源相对低,速度相对快
diff --git a/docs/basics/object-oriented/polymorphism.md b/docs/basics/object-oriented/polymorphism.md
index ff84a312..3fe9c92b 100644
--- a/docs/basics/object-oriented/polymorphism.md
+++ b/docs/basics/object-oriented/polymorphism.md
@@ -40,20 +40,23 @@
这样,就实现了多态,同样是Parent类的实例,p.call 调用的是Son类的实现、p1.call调用的是Daughter的实现。
-有人说,你自己定义的时候不就已经知道p是son,p1是Daughter了么。但是,有些时候你用到的对象并不都是自己声明的啊 。
+有人说,你自己定义的时候不就已经知道p是son,p1是Daughter了么。但是,有些时候你用到的对象并不都是自己声明的。
比如Spring 中的IOC出来的对象,你在使用的时候就不知道他是谁,或者说你可以不用关心他是谁。根据具体情况而定。
+### 静态多态
另外,还有一种说法,包括维基百科也说明,多态还分为动态多态和静态多态。
上面提到的那种动态绑定认为是动态多态,因为只有在运行期才能知道真正调用的是哪个类的方法。
-还有一种静态多态,一般认为Java中的函数重载是一种静态多态,因为他需要在编译期决定具体调用哪个方法、
+还有一种静态多态,一般认为Java中的函数重载是一种静态多态,因为他需要在编译期决定具体调用哪个方法
关于这个动态静态的说法,我更偏向于重载和多态其实是无关的。
-但是也要看情况,普通场合,我会认为只有方法的重写算是多态,毕竟这是我的观点。但是如果在面试的时候,我“可能”会认为重载也算是多态,毕竟面试官也有他的观点。我会和面试官说:我认为,多态应该是一种运行期特性,Java中的重写是多态的体现。不过也有人提出重载是一种静态多态的想法,这个问题在StackOverflow等网站上有很多人讨论,但是并没有什么定论。我更加倾向于重载不是多态。
+但是也要看情况,普通场合,我会认为只有方法的重写算是多态,毕竟这是我的观点。
+
+但是如果在面试的时候,我“可能”会认为重载也算是多态,毕竟面试官也有他的观点。我会和面试官说:我认为,多态应该是一种运行期特性,Java中的重写是多态的体现。不过也有人提出重载是一种静态多态的想法,这个问题在StackOverflow等网站上有很多人讨论,但是并没有什么定论。我更加倾向于重载不是多态。
这样沟通,既能体现出你了解的多,又能表现出你有自己的思维,不是那种别人说什么就是什么的。
From db601d03b5c373825226b12f7ed0cdbe2fea8432 Mon Sep 17 00:00:00 2001
From: "hollis.zhl"
Date: Sun, 17 Jan 2021 16:25:35 +0800
Subject: [PATCH 47/84] =?UTF-8?q?=E8=A1=A5=E5=85=85=E5=8F=8C=E4=BA=B2?=
=?UTF-8?q?=E5=A7=94=E6=B4=BE=E7=9A=84=E7=9F=A5=E8=AF=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/_sidebar.md | 22 ++++++--
docs/basement/jvm/break-parants-delegate.md | 3 ++
docs/basement/jvm/define-class-loader.md | 29 +++++++++++
.../basement/jvm/exclusive-in-runtime-area.md | 3 ++
.../jvm/implements-of-parents-delegate.md | 51 +++++++++++++++++++
docs/basement/jvm/moduler.md | 39 ++++++++++++++
docs/basement/jvm/parents-delegate.md | 30 +++++++++++
.../jvm/relation-with-parents-delegate.md | 12 +++++
docs/basement/jvm/runtime-area.md | 25 +++++++++
.../jvm/sample-of-break-parents-delegate.md | 14 +++++
docs/basement/jvm/spi-parents-delegate.md | 35 +++++++++++++
docs/basement/jvm/tomcat-parents-delegate.md | 11 ++++
docs/basement/jvm/why-parents-delegate.md | 15 ++++++
docs/menu.md | 22 ++++++--
14 files changed, 301 insertions(+), 10 deletions(-)
create mode 100644 docs/basement/jvm/break-parants-delegate.md
create mode 100644 docs/basement/jvm/define-class-loader.md
create mode 100644 docs/basement/jvm/exclusive-in-runtime-area.md
create mode 100644 docs/basement/jvm/implements-of-parents-delegate.md
create mode 100644 docs/basement/jvm/moduler.md
create mode 100644 docs/basement/jvm/parents-delegate.md
create mode 100644 docs/basement/jvm/relation-with-parents-delegate.md
create mode 100644 docs/basement/jvm/runtime-area.md
create mode 100644 docs/basement/jvm/sample-of-break-parents-delegate.md
create mode 100644 docs/basement/jvm/spi-parents-delegate.md
create mode 100644 docs/basement/jvm/tomcat-parents-delegate.md
create mode 100644 docs/basement/jvm/why-parents-delegate.md
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index 7130a458..a07ea636 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -603,7 +603,7 @@
* 运行时数据区
- * 运行时数据区哪些是线程独享
+ * [运行时数据区哪些是线程独享](/basement/jvm/exclusive-in-runtime-area.md)
* 堆和栈区别
@@ -721,13 +721,25 @@
* 如何判断JVM中类和其他类是不是同一个类
- * 双亲委派原则
+ * [双亲委派原则](/basement/jvm/parents-delegate.md)
- * 如何打破双亲委派
+ * [为什么需要双亲委派?](/basement/jvm/why-parents-delegate.md)
- * 如何自定义类加载器
+ * [“父子加载器”之间的关系是继承吗?](/basement/jvm/relation-with-parents-delegate.md)
- * 模块化(jboss modules、osgi、jigsaw)
+ * [双亲委派是如何实现的?](/basement/jvm/implements-of-parents-delegate.md)
+
+ * [如何打破双亲委派](/basement/jvm/ibreak-parants-delegate.md)
+
+ * [如何自定义类加载器](/basement/jvm/define-class-loader.md)
+
+ * [双亲委派被破坏的例子](/basement/jvm/sample-of-break-parents-delegate.md)
+
+ * [为什么JNDI,JDBC等需要破坏双亲委派?](/basement/jvm/spi-parents-delegate.md)
+
+ * [为什么Tomcat要破坏双亲委派](/basement/jvm/tomcat-parents-delegate.md)
+
+ * [模块化(jboss modules、osgi、jigsaw)](/basement/jvm/moduler.md)
* 打包工具
diff --git a/docs/basement/jvm/break-parants-delegate.md b/docs/basement/jvm/break-parants-delegate.md
new file mode 100644
index 00000000..4a31d080
--- /dev/null
+++ b/docs/basement/jvm/break-parants-delegate.md
@@ -0,0 +1,3 @@
+知道了双亲委派模型的实现,那么想要破坏双亲委派机制就很简单了。
+
+因为他的双亲委派过程都是在loadClass方法中实现的,那么想要破坏这种机制,那么就自定义一个类加载器,重写其中的loadClass方法,使其不进行双亲委派即可。
\ No newline at end of file
diff --git a/docs/basement/jvm/define-class-loader.md b/docs/basement/jvm/define-class-loader.md
new file mode 100644
index 00000000..d0133017
--- /dev/null
+++ b/docs/basement/jvm/define-class-loader.md
@@ -0,0 +1,29 @@
+ClassLoader中和类加载有关的方法有很多,前面提到了loadClass,除此之外,还有findClass和defineClass等,那么这几个方法有什么区别呢?
+
+* loadClass()
+ * 就是主要进行类加载的方法,默认的双亲委派机制就实现在这个方法中。
+* findClass()
+ * 根据名称或位置加载.class字节码
+* definclass()
+ * 把字节码转化为Class
+
+这里面需要展开讲一下loadClass和findClass,我们前面说过,当我们想要自定义一个类加载器的时候,并且像破坏双亲委派原则时,我们会重写loadClass方法。
+
+那么,如果我们想定义一个类加载器,但是不想破坏双亲委派模型的时候呢?
+
+这时候,就可以继承ClassLoader,并且重写findClass方法。findClass()方法是JDK1.2之后的ClassLoader新添加的一个方法。
+
+ /**
+ * @since 1.2
+ */
+ protected Class> findClass(String name) throws ClassNotFoundException {
+ throw new ClassNotFoundException(name);
+ }
+
+这个方法只抛出了一个异常,没有默认实现。
+
+JDK1.2之后已不再提倡用户直接覆盖loadClass()方法,而是建议把自己的类加载逻辑实现到findClass()方法中。
+
+因为在loadClass()方法的逻辑里,如果父类加载器加载失败,则会调用自己的findClass()方法来完成加载。
+
+所以,如果你想定义一个自己的类加载器,并且要遵守双亲委派模型,那么可以继承ClassLoader,并且在findClass中实现你自己的加载逻辑即可。
\ No newline at end of file
diff --git a/docs/basement/jvm/exclusive-in-runtime-area.md b/docs/basement/jvm/exclusive-in-runtime-area.md
new file mode 100644
index 00000000..b0332c5e
--- /dev/null
+++ b/docs/basement/jvm/exclusive-in-runtime-area.md
@@ -0,0 +1,3 @@
+在JVM运行时内存区域中,PC寄存器、虚拟机栈和本地方法栈是线程独享的。
+
+而Java堆、方法区是线程共享的。但是值得注意的是,Java堆其实还未每一个线程单独分配了一块TLAB空间,这部分空间在分配时是线程独享的,在使用时是线程共享的。
\ No newline at end of file
diff --git a/docs/basement/jvm/implements-of-parents-delegate.md b/docs/basement/jvm/implements-of-parents-delegate.md
new file mode 100644
index 00000000..d46750e6
--- /dev/null
+++ b/docs/basement/jvm/implements-of-parents-delegate.md
@@ -0,0 +1,51 @@
+双亲委派模型对于保证Java程序的稳定运作很重要,但它的实现并不复杂。
+
+实现双亲委派的代码都集中在java.lang.ClassLoader的loadClass()方法之中:
+
+ protected Class> loadClass(String name, boolean resolve)
+ throws ClassNotFoundException
+ {
+ synchronized (getClassLoadingLock(name)) {
+ // First, check if the class has already been loaded
+ Class> c = findLoadedClass(name);
+ if (c == null) {
+ long t0 = System.nanoTime();
+ try {
+ if (parent != null) {
+ c = parent.loadClass(name, false);
+ } else {
+ c = findBootstrapClassOrNull(name);
+ }
+ } catch (ClassNotFoundException e) {
+ // ClassNotFoundException thrown if class not found
+ // from the non-null parent class loader
+ }
+
+ if (c == null) {
+ // If still not found, then invoke findClass in order
+ // to find the class.
+ long t1 = System.nanoTime();
+ c = findClass(name);
+
+ // this is the defining class loader; record the stats
+ sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
+ sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
+ sun.misc.PerfCounter.getFindClasses().increment();
+ }
+ }
+ if (resolve) {
+ resolveClass(c);
+ }
+ return c;
+ }
+ }
+
+代码不难理解,主要就是以下几个步骤:
+
+1、先检查类是否已经被加载过
+
+2、若没有加载则调用父加载器的loadClass()方法进行加载
+
+3、若父加载器为空则默认使用启动类加载器作为父加载器。
+
+4、如果父类加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。
\ No newline at end of file
diff --git a/docs/basement/jvm/moduler.md b/docs/basement/jvm/moduler.md
new file mode 100644
index 00000000..11eea7b7
--- /dev/null
+++ b/docs/basement/jvm/moduler.md
@@ -0,0 +1,39 @@
+近几年模块化技术已经很成熟了,在JDK 9中已经应用了模块化的技术。
+
+其实早在JDK 9之前,OSGI这种框架已经是模块化的了,**而OSGI之所以能够实现模块热插拔和模块内部可见性的精准控制都归结于其特殊的类加载机制,加载器之间的关系不再是双亲委派模型的树状结构,而是发展成复杂的网状结构。**
+
+
+
+**在JDK中,双亲委派也不是绝对的了。**
+
+在JDK9之前,JVM的基础类以前都是在rt.jar这个包里,这个包也是JRE运行的基石。
+
+这不仅是违反了单一职责原则,同样程序在编译的时候会将很多无用的类也一并打包,造成臃肿。
+
+**在JDK9中,整个JDK都基于模块化进行构建,以前的rt.jar, tool.jar被拆分成数十个模块,编译的时候只编译实际用到的模块,同时各个类加载器各司其职,只加载自己负责的模块。**
+
+ Class> c = findLoadedClass(cn);
+ if (c == null) {
+ // 找到当前类属于哪个模块
+ LoadedModule loadedModule = findLoadedModule(cn);
+ if (loadedModule != null) {
+ //获取当前模块的类加载器
+ BuiltinClassLoader loader = loadedModule.loader();
+ //进行类加载
+ c = findClassInModuleOrNull(loadedModule, cn);
+ } else {
+ // 找不到模块信息才会进行双亲委派
+ if (parent != null) {
+ c = parent.loadClassOrNull(cn);
+ }
+ }
+ }
+
+
+### 总结
+
+以上,从什么是双亲委派,到如何实现与破坏双亲委派,又从破坏双亲委派的示例等多个方面全面介绍了关于双亲委派的知识。
+
+相信通过学习本文,你一定对双亲委派机制有了更加深刻的了解。
+
+阅读过本文之后,反手在简历上写下:熟悉Java的类加载机制,不服来问!
\ No newline at end of file
diff --git a/docs/basement/jvm/parents-delegate.md b/docs/basement/jvm/parents-delegate.md
new file mode 100644
index 00000000..876a66e2
--- /dev/null
+++ b/docs/basement/jvm/parents-delegate.md
@@ -0,0 +1,30 @@
+虚拟机在加载类的过程中需要使用类加载器进行加载,而在Java中,类加载器有很多,那么当JVM想要加载一个.class文件的时候,到底应该由哪个类加载器加载呢?
+
+这就不得不提到”双亲委派机制”。
+
+首先,我们需要知道的是,Java语言系统中支持以下4种类加载器:
+
+
+* Bootstrap ClassLoader 启动类加载器
+* Extention ClassLoader 标准扩展类加载器
+* Application ClassLoader 应用类加载器
+* User ClassLoader 用户自定义类加载器
+
+这四种类加载器之间,是存在着一种层次关系的,如下图
+
+
+
+一般认为上一层加载器是下一层加载器的父加载器,那么,除了BootstrapClassLoader之外,所有的加载器都是有父加载器的。
+
+那么,所谓的双亲委派机制,指的就是:当一个类加载器收到了类加载的请求的时候,他不会直接去加载指定的类,而是把这个请求委托给自己的父加载器去加载。只有父加载器无法加载这个类的时候,才会由当前这个加载器来负责类的加载。
+
+那么,什么情况下父加载器会无法加载某一个类呢?
+
+其实,Java中提供的这四种类型的加载器,是有各自的职责的:
+
+* Bootstrap ClassLoader ,主要负责加载Java核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。
+* Extention ClassLoader,主要负责加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。
+* Application ClassLoader ,主要负责加载当前应用的classpath下的所有类
+* User ClassLoader , 用户自定义的类加载器,可加载指定路径的class文件
+
+那么也就是说,一个用户自定义的类,如com.hollis.ClassHollis 是无论如何也不会被Bootstrap和Extention加载器加载的。
\ No newline at end of file
diff --git a/docs/basement/jvm/relation-with-parents-delegate.md b/docs/basement/jvm/relation-with-parents-delegate.md
new file mode 100644
index 00000000..dd71242e
--- /dev/null
+++ b/docs/basement/jvm/relation-with-parents-delegate.md
@@ -0,0 +1,12 @@
+很多人看到父加载器、子加载器这样的名字,就会认为Java中的类加载器之间存在着继承关系。
+
+甚至网上很多文章也会有类似的错误观点。
+
+这里需要明确一下,双亲委派模型中,类加载器之间的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合(Composition)关系来复用父加载器的代码的。
+
+如下为ClassLoader中父加载器的定义:
+
+ public abstract class ClassLoader {
+ // The parent class loader for delegation
+ private final ClassLoader parent;
+ }
\ No newline at end of file
diff --git a/docs/basement/jvm/runtime-area.md b/docs/basement/jvm/runtime-area.md
new file mode 100644
index 00000000..69a2115a
--- /dev/null
+++ b/docs/basement/jvm/runtime-area.md
@@ -0,0 +1,25 @@
+Java虚拟机在执行Java程序的过程中会把他所管理的内存划分为若干个不同的数据区域。《Java虚拟机规范》中规定了JVM所管理的内存需要包括一下几个运行时区域:
+
+
+
+主要包含了PC寄存器(程序计数器)、Java虚拟机栈、本地方法栈、Java堆、方法区以及运行时常量池。
+
+
+
+1、以上是Java虚拟机规范,不同的虚拟机实现会各有不同,但是一般会遵守规范。
+
+2、规范中定义的方法区,只是一种概念上的区域,并说明了其应该具有什么功能。但是并没有规定这个区域到底应该处于何处。所以,对于不同的虚拟机实现来说,是由一定的自由度的。
+
+3、不同版本的方法区所处位置不同,上图中划分的是逻辑区域,并不是绝对意义上的物理区域。因为某些版本的JDK中方法区其实是在堆中实现的。
+
+4、运行时常量池用于存放编译期生成的各种字面量和符号应用。但是,Java语言并不要求常量只有在编译期才能产生。比如在运行期,String.intern也会把新的常量放入池中。
+
+5、除了以上介绍的JVM运行时内存外,还有一块内存区域可供使用,那就是直接内存。Java虚拟机规范并没有定义这块内存区域,所以他并不由JVM管理,是利用本地方法库直接在堆外申请的内存区域。
+
+6、堆和栈的数据划分也不是绝对的,如HotSpot的JIT会针对对象分配做相应的优化。
+
+如上,做个总结,JVM内存结构,由Java虚拟机规范定义。描述的是Java程序执行过程中,由JVM管理的不同数据区域。各个区域有其特定的功能。
+
+
+但是,需要注意的是,上面的区域划分只是逻辑区域,对于有些区域的限制是比较松的,所以不同的虚拟机厂商在实现上,甚至是同一款虚拟机的不同版本也是不尽相同的。
+
diff --git a/docs/basement/jvm/sample-of-break-parents-delegate.md b/docs/basement/jvm/sample-of-break-parents-delegate.md
new file mode 100644
index 00000000..ab9f627d
--- /dev/null
+++ b/docs/basement/jvm/sample-of-break-parents-delegate.md
@@ -0,0 +1,14 @@
+双亲委派机制的破坏不是什么稀奇的事情,很多框架、容器等都会破坏这种机制来实现某些功能。
+
+*第一种被破坏的情况是在双亲委派出现之前。*
+
+由于双亲委派模型是在JDK1.2之后才被引入的,而在这之前已经有用户自定义类加载器在用了。所以,这些是没有遵守双亲委派原则的。
+
+*第二种,是JNDI、JDBC等需要加载SPI接口实现类的情况。*
+
+*第三种是为了实现热插拔热部署工具。*为了让代码动态生效而无需重启,实现方式时把模块连同类加载器一起换掉就实现了代码的热替换。
+
+*第四种时tomcat等web容器的出现。*
+
+*第五种时OSGI、Jigsaw等模块化技术的应用。*
+
diff --git a/docs/basement/jvm/spi-parents-delegate.md b/docs/basement/jvm/spi-parents-delegate.md
new file mode 100644
index 00000000..b620a1f0
--- /dev/null
+++ b/docs/basement/jvm/spi-parents-delegate.md
@@ -0,0 +1,35 @@
+我们日常开发中,大多数时候会通过API的方式调用Java提供的那些基础类,这些基础类时被Bootstrap加载的。
+
+但是,调用方式除了API之外,还有一种SPI的方式。
+
+如典型的JDBC服务,我们通常通过以下方式创建数据库连接:
+
+ Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql", "root", "1234");
+
+
+在以上代码执行之前,DriverManager会先被类加载器加载,因为java.sql.DriverManager类是位于rt.jar下面的 ,所以他会被根加载器加载。
+
+类加载时,会执行该类的静态方法。其中有一段关键的代码是:
+
+ ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
+
+
+这段代码,会尝试加载classpath下面的所有实现了Driver接口的实现类。
+
+那么,问题就来了。
+
+**DriverManager是被根加载器加载的,那么在加载时遇到以上代码,会尝试加载所有Driver的实现类,但是这些实现类基本都是第三方提供的,根据双亲委派原则,第三方的类不能被根加载器加载。**
+
+那么,怎么解决这个问题呢?
+
+于是,就**在JDBC中通过引入ThreadContextClassLoader(线程上下文加载器,默认情况下是AppClassLoader)的方式破坏了双亲委派原则。**
+
+我们深入到ServiceLoader.load方法就可以看到:
+
+ public static ServiceLoader load(Class service) {
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ return ServiceLoader.load(service, cl);
+ }
+
+
+第一行,获取当前线程的线程上下⽂类加载器 AppClassLoader,⽤于加载 classpath 中的具体实现类。
\ No newline at end of file
diff --git a/docs/basement/jvm/tomcat-parents-delegate.md b/docs/basement/jvm/tomcat-parents-delegate.md
new file mode 100644
index 00000000..4a04a7b1
--- /dev/null
+++ b/docs/basement/jvm/tomcat-parents-delegate.md
@@ -0,0 +1,11 @@
+我们知道,Tomcat是web容器,那么一个web容器可能需要部署多个应用程序。
+
+不同的应用程序可能会依赖同一个第三方类库的不同版本,但是不同版本的类库中某一个类的全路径名可能是一样的。
+
+如多个应用都要依赖hollis.jar,但是A应用需要依赖1.0.0版本,但是B应用需要依赖1.0.1版本。这两个版本中都有一个类是com.hollis.Test.class。
+
+**如果采用默认的双亲委派类加载机制,那么是无法加载多个相同的类。**
+
+所以,**Tomcat破坏双亲委派原则,提供隔离的机制,为每个web容器单独提供一个WebAppClassLoader加载器。**
+
+Tomcat的类加载机制:为了实现隔离性,优先加载 Web 应用自己定义的类,所以没有遵照双亲委派的约定,每一个应用自己的类加载器——WebAppClassLoader负责加载本身的目录下的class文件,加载不到时再交给CommonClassLoader加载,这和双亲委派刚好相反。
\ No newline at end of file
diff --git a/docs/basement/jvm/why-parents-delegate.md b/docs/basement/jvm/why-parents-delegate.md
new file mode 100644
index 00000000..83e88e20
--- /dev/null
+++ b/docs/basement/jvm/why-parents-delegate.md
@@ -0,0 +1,15 @@
+如前文我们提到的,因为类加载器之间有严格的层次关系,那么也就使得Java类也随之具备了层次关系。
+
+或者说这种层次关系是优先级。
+
+比如一个定义在java.lang包下的类,因为它被存放在rt.jar之中,所以在被加载过程汇总,会被一直委托到Bootstrap ClassLoader,最终由Bootstrap ClassLoader所加载。
+
+而一个用户自定义的com.hollis.ClassHollis类,他也会被一直委托到Bootstrap ClassLoader,但是因为Bootstrap ClassLoader不负责加载该类,那么会在由Extention ClassLoader尝试加载,而Extention ClassLoader也不负责这个类的加载,最终才会被Application ClassLoader加载。
+
+这种机制有几个好处。
+
+首先,通过委派的方式,可以避免类的重复加载,当父加载器已经加载过某一个类时,子加载器就不会再重新加载这个类。
+
+另外,通过双亲委派的方式,还保证了安全性。因为Bootstrap ClassLoader在加载的时候,只会加载JAVA_HOME中的jar包里面的类,如java.lang.Integer,那么这个类是不会被随意替换的,除非有人跑到你的机器上, 破坏你的JDK。
+
+那么,就可以避免有人自定义一个有破坏功能的java.lang.Integer被加载。这样可以有效的防止核心Java API被篡改。
\ No newline at end of file
diff --git a/docs/menu.md b/docs/menu.md
index 3ea819c5..d5bd9a5d 100644
--- a/docs/menu.md
+++ b/docs/menu.md
@@ -659,7 +659,7 @@ Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer)
* 运行时数据区
- * 运行时数据区哪些是线程独享
+ * [运行时数据区哪些是线程独享](/basement/jvm/exclusive-in-runtime-area.md)
* 堆和栈区别
@@ -777,13 +777,25 @@ Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer)
* 如何判断JVM中类和其他类是不是同一个类
- * 双亲委派原则
+ * [双亲委派原则](/basement/jvm/parents-delegate.md)
- * 如何打破双亲委派
+ * [为什么需要双亲委派?](/basement/jvm/why-parents-delegate.md)
- * 如何自定义类加载器
+ * [“父子加载器”之间的关系是继承吗?](/basement/jvm/relation-with-parents-delegate.md)
- * 模块化(jboss modules、osgi、jigsaw)
+ * [双亲委派是如何实现的?](/basement/jvm/implements-of-parents-delegate.md)
+
+ * [如何打破双亲委派](/basement/jvm/ibreak-parants-delegate.md)
+
+ * [如何自定义类加载器](/basement/jvm/define-class-loader.md)
+
+ * [双亲委派被破坏的例子](/basement/jvm/sample-of-break-parents-delegate.md)
+
+ * [为什么JNDI,JDBC等需要破坏双亲委派?](/basement/jvm/spi-parents-delegate.md)
+
+ * [为什么Tomcat要破坏双亲委派](/basement/jvm/tomcat-parents-delegate.md)
+
+ * [模块化(jboss modules、osgi、jigsaw)](/basement/jvm/moduler.md)
* 打包工具
From ce79781594f03ba449ec7280eae2ab778e573759 Mon Sep 17 00:00:00 2001
From: "hollis.zhl"
Date: Sun, 17 Jan 2021 16:43:01 +0800
Subject: [PATCH 48/84] =?UTF-8?q?=E7=9F=A5=E8=AF=86=E4=BD=93=E7=B3=BB?=
=?UTF-8?q?=E5=AE=8C=E5=96=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/_sidebar.md | 36 +++++++++++++++++++++++++++++++++---
docs/menu.md | 32 +++++++++++++++++++++++++++++++-
2 files changed, 64 insertions(+), 4 deletions(-)
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index a07ea636..282bbc6a 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -1,5 +1,4 @@
-
* 基础篇
* 面向对象
@@ -171,6 +170,8 @@
* [枚举的线程安全性问题](/basics/java-basic/enum-thread-safe.md)
+ * 为什么不建议在对外接口中使用枚举
+
* IO
* [字符流、字节流](/basics/java-basic/byte-stream-vs-character-stream.md)
@@ -349,6 +350,8 @@
* [yyyy和YYYY有什么区别?](/basics/java-basic/YYYY-vs-yyyy.md)
+ * 为什么日期格式化时必须有使用y表示年,而不能用Y?
+
* 编码方式
* [什么是ASCII?](/basics/java-basic/ASCII.md)
@@ -387,6 +390,12 @@
* javax.management.*
+ * BigDecimal
+
+ * 为什么0.1+0.2不等于0.3
+
+ * 为什么不能使用BigDecimal的equals比较大小
+
* Java 8
* [lambda表达式](/basics/java-basic/lambda.md)
@@ -931,7 +940,11 @@
* 用Java写一个简单的静态文件的HTTP服务器
- * http/2
+ * HTTP/2
+
+ * HTTP/2 存在哪些问题?
+
+ * HTTP/3
* Java RMI,Socket,HttpClient
@@ -1009,10 +1022,14 @@
* AOP原理
+ * Spring AOP不支持方法自调用的问题
+
* 实现Spring的IOC
* spring四种依赖注入方式
+ * 为什么我不建议使用@Transactional声明事务
+
* Spring MVC
* 什么是MVC
@@ -1270,6 +1287,9 @@
* 为什么kill -9 不能随便执行
+ * rm一个被打开的文件会发生什么
+ * rm一个被打开的文件会发生什么
+
* 进程间通信
* 服务器性能指标
@@ -1396,6 +1416,10 @@
* memcached
+ * Redis
+
+ * Redis多线程
+
* 分别使用数据库锁、NoSql实现分布式锁
* 性能调优
@@ -1738,6 +1762,12 @@
* 主主复制
* 异地多活
+
+ * 预案
+
+ * 预热
+
+ * 限流
* 高性能
@@ -1911,4 +1941,4 @@
* Swift
- * Rust
\ No newline at end of file
+ * Rust
\ No newline at end of file
diff --git a/docs/menu.md b/docs/menu.md
index d5bd9a5d..bfd90c1d 100644
--- a/docs/menu.md
+++ b/docs/menu.md
@@ -227,6 +227,8 @@ Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer)
* [枚举的线程安全性问题](/basics/java-basic/enum-thread-safe.md)
+ * 为什么不建议在对外接口中使用枚举
+
* IO
* [字符流、字节流](/basics/java-basic/byte-stream-vs-character-stream.md)
@@ -405,6 +407,8 @@ Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer)
* [yyyy和YYYY有什么区别?](/basics/java-basic/YYYY-vs-yyyy.md)
+ * 为什么日期格式化时必须有使用y表示年,而不能用Y?
+
* 编码方式
* [什么是ASCII?](/basics/java-basic/ASCII.md)
@@ -443,6 +447,12 @@ Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer)
* javax.management.*
+ * BigDecimal
+
+ * 为什么0.1+0.2不等于0.3
+
+ * 为什么不能使用BigDecimal的equals比较大小
+
* Java 8
* [lambda表达式](/basics/java-basic/lambda.md)
@@ -987,7 +997,11 @@ Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer)
* 用Java写一个简单的静态文件的HTTP服务器
- * http/2
+ * HTTP/2
+
+ * HTTP/2 存在哪些问题?
+
+ * HTTP/3
* Java RMI,Socket,HttpClient
@@ -1065,10 +1079,14 @@ Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer)
* AOP原理
+ * Spring AOP不支持方法自调用的问题
+
* 实现Spring的IOC
* spring四种依赖注入方式
+ * 为什么我不建议使用@Transactional声明事务
+
* Spring MVC
* 什么是MVC
@@ -1326,6 +1344,8 @@ Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer)
* 为什么kill -9 不能随便执行
+ * rm一个被打开的文件会发生什么
+
* 进程间通信
* 服务器性能指标
@@ -1452,6 +1472,10 @@ Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer)
* memcached
+ * Redis
+
+ * Redis多线程
+
* 分别使用数据库锁、NoSql实现分布式锁
* 性能调优
@@ -1794,6 +1818,12 @@ Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer)
* 主主复制
* 异地多活
+
+ * 预案
+
+ * 预热
+
+ * 限流
* 高性能
From c5f0cea14f2b64dfb8360f158642d473ddc3e118 Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 17 Jan 2021 21:09:45 +0800
Subject: [PATCH 49/84] fixed wrong characters in
Date: Sun, 17 Jan 2021 21:15:51 +0800
Subject: [PATCH 50/84] fixed wrong characters in
---
docs/basics/java-basic/static-proxy.md | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/docs/basics/java-basic/static-proxy.md b/docs/basics/java-basic/static-proxy.md
index 7886da15..b3c474e4 100644
--- a/docs/basics/java-basic/static-proxy.md
+++ b/docs/basics/java-basic/static-proxy.md
@@ -12,7 +12,7 @@ public class HelloSeriviceImpl implements HelloSerivice{
}
}
```
-上面的代码比较简单,定义了一个接口和其实现类。这就是代理模式中的目标对象和目标对象的接口。接下类定义代理对象。
+上面的代码比较简单,定义了一个接口和其实现类。这就是代理模式中的目标对象和目标对象的接口。接下来定义代理对象。
```
public class HelloSeriviceProxy implements HelloSerivice{
@@ -50,9 +50,10 @@ public class Main {
这就是一个简单的静态的代理模式的实现。代理模式中的所有角色(代理对象、目标对象、目标对象的接口)等都是在编译期就确定好的。
-静态代理的用途
-控制真实对象的访问权限 通过代理对象控制对真实对象的使用权限。
+静态代理的用途
-避免创建大对象 通过使用一个代理小对象来代表一个真实的大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。
+1.控制真实对象的访问权限:通过代理对象控制真实对象的使用权限。
-增强真实对象的功能 这个比较简单,通过代理可以在调用真实对象的方法的前后增加额外功能。
\ No newline at end of file
+2.避免创建大对象:通过使用一个代理小对象来代表一个真实的大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。
+
+3.增强真实对象的功能:这个比较简单,通过代理可以在调用真实对象的方法的前后增加额外功能。
From 8f72ec454b9d912186064d7f67c281271cd1a2ce Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 17 Jan 2021 21:24:01 +0800
Subject: [PATCH 51/84] fixed wrong characters in
---
docs/basics/java-basic/dynamic-proxy-implementation.md | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/docs/basics/java-basic/dynamic-proxy-implementation.md b/docs/basics/java-basic/dynamic-proxy-implementation.md
index 1ca4889a..6f5c7e1c 100644
--- a/docs/basics/java-basic/dynamic-proxy-implementation.md
+++ b/docs/basics/java-basic/dynamic-proxy-implementation.md
@@ -6,7 +6,8 @@ Java中,实现动态代理有两种方式:
关于这两种动态代理的写法本文就不深入展开了,读者感兴趣的话,后面我再写文章单独介绍。本文主要来简单说一下这两种动态代理的区别和用途。
-JDK动态代理和Cglib动态代理的区别
+JDK动态代理和Cglib动态代理的区别
+
JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的类,就可以使用CGLIB实现。
Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。
@@ -120,4 +121,4 @@ public class DoCGLib {
proxyImp.add();
}
}
-```
\ No newline at end of file
+```
From f262dce74b1fbd07179c209b24cea4cbd4fcd79d Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 24 Jan 2021 22:10:58 +0800
Subject: [PATCH 52/84] fixed wrong characters in
---
.../java-basic/diff-serializable-vs-externalizable.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/basics/java-basic/diff-serializable-vs-externalizable.md b/docs/basics/java-basic/diff-serializable-vs-externalizable.md
index 5b2ed9a7..7e94fc59 100644
--- a/docs/basics/java-basic/diff-serializable-vs-externalizable.md
+++ b/docs/basics/java-basic/diff-serializable-vs-externalizable.md
@@ -1,17 +1,17 @@
-Java中的类通过实现 `java.io.Serializable` 接口以启⽤其序列化功能。 未实现此接⼜的类将⽆法使其任何状态序列化或反序列化。
+Java中的类通过实现 `java.io.Serializable` 接口以启⽤其序列化功能。 未实现此接口的类将⽆法使其任何状态序列化或反序列化。
可序列化类的所有⼦类型本⾝都是可序列化的。
-序列化接⼜没有⽅法或字段, 仅⽤于标识可序列化的语义。
+序列化接口没有⽅法或字段, 仅⽤于标识可序列化的语义。
当试图对⼀个对象进⾏序列化的时候, 如果遇到不⽀持`Serializable` 接口的对象。 在此情况下, 将抛`NotSerializableException`。
如果要序列化的类有⽗类, 要想同时将在⽗类中定义过的变量持久化下来, 那么⽗类也应该集成`java.io.Serializable`接口。
-`Externalizable`继承了`Serializable`, 该接⼜中定义了两个抽象⽅法:`writeExternal()`与`readExternal()`。 当使⽤`Externalizable`接口来进⾏序列化与反序列化的时候需要开发⼈员重写writeExternal()与readExternal()⽅法。
+`Externalizable`继承了`Serializable`, 该接口中定义了两个抽象⽅法:`writeExternal()`与`readExternal()`。 当使⽤`Externalizable`接口来进⾏序列化与反序列化的时候需要开发⼈员重写writeExternal()与readExternal()⽅法。
如果没有在这两个⽅法中定义序列化实现细节, 那么序列化之后, 对象内容为空。
-实现`Externalizable`接⼜的类必须要提供⼀个`public`的⽆参的构造器。
+实现`Externalizable`接口的类必须要提供⼀个`public`的⽆参的构造器。
所以, 实现`Externalizable`, 并实现`writeExternal()`和`readExternal()`⽅法可以指定序列化哪些属性。
From 349517a895173d7bb56760cf8f19226d7b862cfa Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 24 Jan 2021 22:37:31 +0800
Subject: [PATCH 53/84] fixed wrong characters in
---
docs/basics/java-basic/protobuf.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/basics/java-basic/protobuf.md b/docs/basics/java-basic/protobuf.md
index f8c8738c..170b140d 100644
--- a/docs/basics/java-basic/protobuf.md
+++ b/docs/basics/java-basic/protobuf.md
@@ -3,7 +3,7 @@ Protocol Buffer (简称Protobuf) 是Google出品的性能优异、跨语言、
2001年初,Protobuf首先在Google内部创建, 我们把它称之为 proto1,一直以来在Google的内部使用,其中也不断的演化,根据使用者的需求也添加很多新的功能,一些内部库依赖它。几乎每个Google的开发者都会使用到它。
-Google开始开源它的内部项目时,因为依赖的关系,所以他们决定首先把Protobuf开源出去。
+Google开始开源它的内部项目时,因为依赖的关系,他们决定首先把Protobuf开源出去。
-目前Protobuf的稳定版本是3.9.2,于2019年9月23日发布。由于很多公司很早的就采用了Protobuf,所以很多项目还在使用proto2协议,目前是proto2和proto3同时在使用的状态。
+目前Protobuf的稳定版本是3.9.2,于2019年9月23日发布。由于很多公司很早的就采用了Protobuf,很多项目还在使用proto2协议,目前是proto2和proto3同时在使用的状态。
From 5f79c19959156251002bef2cba477ba1518660e7 Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 24 Jan 2021 22:48:38 +0800
Subject: [PATCH 54/84] fixed wrong characters in
---
docs/basics/java-basic/bug-in-apache-commons-collections.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/basics/java-basic/bug-in-apache-commons-collections.md b/docs/basics/java-basic/bug-in-apache-commons-collections.md
index e040cdc2..cdea3072 100644
--- a/docs/basics/java-basic/bug-in-apache-commons-collections.md
+++ b/docs/basics/java-basic/bug-in-apache-commons-collections.md
@@ -100,7 +100,7 @@ Commons Collections中提供了一个Transformer接口,主要是可以用来
![][7]
-那么,结合序列化,现在的攻击更加进了一步,不再需要一定要传入`newTransformer.transform(Runtime.getRuntime());`这样的代码了,只要代码中有 `transformer.transform()`方法的调用即可,无论里面是什么参数:
+那么,结合序列化,现在的攻击更加进了一步,不再需要传入`newTransformer.transform(Runtime.getRuntime());`这样的代码了,只要代码中有 `transformer.transform()`方法的调用即可,无论里面是什么参数:
![][8]
@@ -249,4 +249,4 @@ BadAttributeValueExpException类是Java中提供的一个异常类,他的readO
[12]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944537014741.jpg
[13]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944526874284.jpg
[14]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944525715616.jpg
- [15]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944525999226.jpg
\ No newline at end of file
+ [15]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944525999226.jpg
From d1d3fc40499e9a485b1dbb2f782d2156e4e15df3 Mon Sep 17 00:00:00 2001
From: baichangfu
Date: Sun, 24 Jan 2021 22:59:31 +0800
Subject: [PATCH 55/84] fixed wrong characters in
---
docs/basics/java-basic/bug-in-fastjson.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/basics/java-basic/bug-in-fastjson.md b/docs/basics/java-basic/bug-in-fastjson.md
index 503dc641..82219f3c 100644
--- a/docs/basics/java-basic/bug-in-fastjson.md
+++ b/docs/basics/java-basic/bug-in-fastjson.md
@@ -195,15 +195,15 @@ fastjson的主要功能就是将Java Bean序列化成JSON字符串,这样得
而黑白名单又是通过startWith检测的,那么黑客只要在自己想要使用的攻击类库前后加上`L`和`;`就可以绕过黑白名单的检查了,也不耽误被fastjson正常加载。
-如`Lcom.sun.rowset.JdbcRowSetImpl;`,会先通过白名单校验,然后fastjson在加载类的时候会去掉前后的`L`和`,变成了`com.sun.rowset.JdbcRowSetImpl`。
+如`Lcom.sun.rowset.JdbcRowSetImpl;`,会先通过白名单校验,然后fastjson在加载类的时候会去掉前后的`L`和`;`,变成了`com.sun.rowset.JdbcRowSetImpl`。
为了避免被攻击,在之后的 v1.2.42版本中,在进行黑白名单检测的时候,fastjson先判断目标类的类名的前后是不是`L`和`;`,如果是的话,就截取掉前后的`L`和`;`再进行黑白名单的校验。
看似解决了问题,但是黑客发现了这个规则之后,就在攻击时在目标类前后双写`LL`和`;;`,这样再被截取之后还是可以绕过检测。如`LLcom.sun.rowset.JdbcRowSetImpl;;`
-魔高一尺,道高一丈。在 v1.2.43中,fastjson这次在黑白名单判断之前,增加了一个是否以`LL`未开头的判断,如果目标类以`LL`开头,那么就直接抛异常,于是就又短暂的修复了这个漏洞。
+魔高一尺,道高一丈。在 v1.2.43中,fastjson这次在黑白名单判断之前,增加了一个是否以`LL`开头的判断,如果目标类以`LL`开头,那么就直接抛异常,于是就又短暂的修复了这个漏洞。
-黑客在`L`和`;`这里走不通了,于是想办法从其他地方下手,因为fastjson在加载类的时候,不只对`L`和`;`这样的类进行特殊处理,还对`[`也被特殊处理了。
+黑客在`L`和`;`这里走不通了,于是想办法从其他地方下手,因为fastjson在加载类的时候,不只对`L`和`;`这样的类进行特殊处理,还对`[`特殊处理了。
同样的攻击手段,在目标类前面添加`[`,v1.2.43以前的所有版本又沦陷了。
@@ -334,4 +334,4 @@ http://www.lmxspace.com/2019/06/29/FastJson-反序列化学习
[3]: https://www.hollischuang.com/wp-content/uploads/2020/07/15938462506312.jpg
[4]: https://www.hollischuang.com/wp-content/uploads/2020/07/15938495572144.jpg
[5]: https://www.hollischuang.com/wp-content/uploads/2020/07/15938532891003.jpg
- [6]: https://www.hollischuang.com/wp-content/uploads/2020/07/15938545656293.jpg
\ No newline at end of file
+ [6]: https://www.hollischuang.com/wp-content/uploads/2020/07/15938545656293.jpg
From 91e39b9acb2b22bf885e336f08a935cdba571d2e Mon Sep 17 00:00:00 2001
From: zhengshuo <694814357@qq.com>
Date: Sat, 20 Feb 2021 10:43:23 +0800
Subject: [PATCH 56/84] =?UTF-8?q?fix=20wrong=20describe=20=E5=BA=94?=
=?UTF-8?q?=E7=94=A8->=E5=BC=95=E7=94=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/basics/object-oriented/why-pass-by-reference.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/basics/object-oriented/why-pass-by-reference.md b/docs/basics/object-oriented/why-pass-by-reference.md
index 0eb2f63d..4fc1042d 100644
--- a/docs/basics/object-oriented/why-pass-by-reference.md
+++ b/docs/basics/object-oriented/why-pass-by-reference.md
@@ -159,7 +159,7 @@
-在参数传递的过程中,实际参数的地址`0X1213456`被拷贝给了形参。这个过程其实就是值传递,只不过传递的值得内容是对象的应用。
+在参数传递的过程中,实际参数的地址`0X1213456`被拷贝给了形参。这个过程其实就是值传递,只不过传递的值得内容是对象的引用。
那为什么我们改了user中的属性的值,却对原来的user产生了影响呢?
From f8986be00fc37a6d00d144325662e20b1807ed82 Mon Sep 17 00:00:00 2001
From: zhengshuo <694814357@qq.com>
Date: Sat, 20 Feb 2021 10:47:46 +0800
Subject: [PATCH 57/84] fix wrong describe
---
docs/basics/object-oriented/why-pass-by-reference.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/basics/object-oriented/why-pass-by-reference.md b/docs/basics/object-oriented/why-pass-by-reference.md
index 4fc1042d..3f0d890d 100644
--- a/docs/basics/object-oriented/why-pass-by-reference.md
+++ b/docs/basics/object-oriented/why-pass-by-reference.md
@@ -159,7 +159,7 @@
-在参数传递的过程中,实际参数的地址`0X1213456`被拷贝给了形参。这个过程其实就是值传递,只不过传递的值得内容是对象的引用。
+在参数传递的过程中,实际参数的地址`0X1213456`被拷贝给了形参。这个过程其实就是值传递,只不过传递的值的内容是对象的引用。
那为什么我们改了user中的属性的值,却对原来的user产生了影响呢?
From 9e33ef8697743392563d302812647150e5aeb0f7 Mon Sep 17 00:00:00 2001
From: "guo.lin"
Date: Thu, 25 Feb 2021 00:00:05 +0800
Subject: [PATCH 58/84] format generic doc.
---
docs/basics/java-basic/generics-problem.md | 2 +-
docs/basics/java-basic/type-erasue.md | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/docs/basics/java-basic/generics-problem.md b/docs/basics/java-basic/generics-problem.md
index ccbd2ab6..0a99bea7 100644
--- a/docs/basics/java-basic/generics-problem.md
+++ b/docs/basics/java-basic/generics-problem.md
@@ -1,6 +1,6 @@
-###一、当泛型遇到重载
+### 一、当泛型遇到重载
public class GenericTypes {
diff --git a/docs/basics/java-basic/type-erasue.md b/docs/basics/java-basic/type-erasue.md
index 50fea639..790685db 100644
--- a/docs/basics/java-basic/type-erasue.md
+++ b/docs/basics/java-basic/type-erasue.md
@@ -4,11 +4,11 @@
通常情况下,一个编译器处理泛型有两种方式:
-1\.`Code specialization`。在实例化一个泛型类或泛型方法时都产生一份新的目标代码(字节码or二进制代码)。例如,针对一个泛型List`,可能需要 针对`String`,`Integer`,`Float`产生三份目标代码。
+1\.`Code specialization`。在实例化一个泛型类或泛型方法时都产生一份新的目标代码(字节码or二进制代码)。例如,针对一个泛型`List`,可能需要 针对`String`,`Integer`,`Float`产生三份目标代码。
2\.`Code sharing`。对每个泛型类只生成唯一的一份目标代码;该泛型类的所有实例都映射到这份目标代码上,在需要的时候执行类型检查和类型转换。
-**C++**中的模板(`template`)是典型的`Code specialization`实现。**C++**编译器会为每一个泛型类实例生成一份执行代码。执行代码中`Integer List`和`String List`是两种不同的类型。这样会导致**代码膨胀(code bloat)**。 **C#**里面泛型无论在程序源码中、编译后的`IL`中(Intermediate Language,中间语言,这时候泛型是一个占位符)或是运行期的CLR中都是切实存在的,`List`与`List`就是两个不同的类型,它们在系统运行期生成,有自己的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型被称为`真实泛型`。 **Java**语言中的泛型则不一样,它只在程序源码中存在,在编译后的字节码文件中,就已经被替换为原来的原生类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此对于运行期的Java语言来说,`ArrayList`与`ArrayList`就是同一个类。所以说泛型技术实际上是Java语言的一颗语法糖,Java语言中的泛型实现方法称为**类型擦除**,基于这种方法实现的泛型被称为`伪泛型`。
+**C++** 中的模板(`template`)是典型的`Code specialization`实现。**C++** 编译器会为每一个泛型类实例生成一份执行代码。执行代码中`Integer List`和`String List`是两种不同的类型。这样会导致**代码膨胀(code bloat)**。 **C#** 里面泛型无论在程序源码中、编译后的`IL`中(Intermediate Language,中间语言,这时候泛型是一个占位符)或是运行期的CLR中都是切实存在的,`List`与`List`就是两个不同的类型,它们在系统运行期生成,有自己的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型被称为`真实泛型`。 **Java**语言中的泛型则不一样,它只在程序源码中存在,在编译后的字节码文件中,就已经被替换为原来的原生类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此对于运行期的Java语言来说,`ArrayList`与`ArrayList`就是同一个类。所以说泛型技术实际上是Java语言的一颗语法糖,Java语言中的泛型实现方法称为**类型擦除**,基于这种方法实现的泛型被称为`伪泛型`。
`C++`和`C#`是使用`Code specialization`的处理机制,前面提到,他有一个缺点,那就是**会导致代码膨胀**。另外一个弊端是在引用类型系统中,浪费空间,因为引用类型集合中元素本质上都是一个指针。没必要为每个类型都产生一份执行代码。而这也是Java编译器中采用`Code sharing`方式处理泛型的主要原因。
@@ -18,7 +18,7 @@
### 二、什么是类型擦除
-前面我们多次提到这个词:**类型擦除**(`type erasue`)**,那么到底什么是类型擦除呢?
+前面我们多次提到这个词:**类型擦除**(`type erasue`),那么到底什么是类型擦除呢?
> 类型擦除指的是通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上。类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且再必要的时候添加类型检查和类型转换的方法。 类型擦除可以简单的理解为将泛型java代码转换为普通java代码,只不过编译器更直接点,将泛型java代码直接转换成普通java字节码。 类型擦除的主要过程如下: 1.将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。(这部分内容可以看:[Java泛型中extends和super的理解][2]) 2.移除所有的类型参数。
@@ -195,4 +195,4 @@
1\.虚拟机中没有泛型,只有普通类和普通方法,所有泛型类的类型参数在编译时都会被擦除,泛型类并没有自己独有的Class类对象。比如并不存在`List`.class或是`List.class`,而只有`List.class`。 2.创建泛型对象时请指明类型,让编译器尽早的做参数检查(**Effective Java,第23条:请不要在新代码中使用原生态类型**) 3.不要忽略编译器的警告信息,那意味着潜在的`ClassCastException`等着你。 4.静态变量是被泛型类的所有实例所共享的。对于声明为`MyClass`的类,访问其中的静态变量的方法仍然是 `MyClass.myStaticVar`。不管是通过`new MyClass`还是`new MyClass`创建的对象,都是共享一个静态变量。 5.泛型的类型参数不能用在`Java`异常处理的`catch`语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除,`JVM`是无法区分两个异常类型`MyException`和`MyException`的。对于`JVM`来说,它们都是 `MyException`类型的。也就无法执行与异常对应的`catch`语句。
[1]: http://docs.oracle.com/javase/tutorial/java/generics/erasure.html
-[2]: /archives/255
\ No newline at end of file
+[2]: /archives/255
From 9d7ad8bd70b21ba6716e6829cd8d188820a404be Mon Sep 17 00:00:00 2001
From: "hollis.zhl"
Date: Sun, 28 Feb 2021 16:13:24 +0800
Subject: [PATCH 59/84] =?UTF-8?q?=E6=96=87=E7=AB=A0=E5=86=85=E5=AE=B9?=
=?UTF-8?q?=E8=A1=A8=E8=BF=B0=E4=BF=AE=E6=94=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../basics/object-oriented/characteristics.md | 24 --------
docs/basics/object-oriented/constructor.md | 57 ++++++++++++++++++-
.../object-oriented/extends-implement.md | 17 +++---
.../inheritance-composition.md | 14 ++++-
docs/basics/object-oriented/polymorphism.md | 24 ++++----
5 files changed, 87 insertions(+), 49 deletions(-)
diff --git a/docs/basics/object-oriented/characteristics.md b/docs/basics/object-oriented/characteristics.md
index 6f722cd2..7b1e4a53 100644
--- a/docs/basics/object-oriented/characteristics.md
+++ b/docs/basics/object-oriented/characteristics.md
@@ -49,8 +49,6 @@
通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。
-继承概念的实现方式有二类:实现继承与接口继承。实现继承是指直接使用基类的属性和方法而无需额外编码的能力;接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;
-
#### 继承举例
我们想要定义一个正方形,因为已经有了矩形,所以我们可以直接继承Rectangle类,因为正方形是长方形的一种特例。
@@ -72,28 +70,6 @@
}
-以上是继承的用法,还有一种实现接口的方式,如我们定义一个Shape类,表示图形:
-
- /**
- * 图形
- */
- interface Shape{
- public int area();
- }
-
-
-那么,矩形Rectangle类可以使用implements实现这个接口
-
- /**
- * 矩形
- */
- class Rectangle implements Shape{
-
- }
-
-实现接口后,Rectangle需要实现Shape中的area方法。
-
-
### 多态(Polymorphism)
所谓多态就是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。
diff --git a/docs/basics/object-oriented/constructor.md b/docs/basics/object-oriented/constructor.md
index a0b3c011..42f9dd74 100644
--- a/docs/basics/object-oriented/constructor.md
+++ b/docs/basics/object-oriented/constructor.md
@@ -1,5 +1,58 @@
-构造函数,是一种特殊的方法。 主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。 特别的一个类可以有多个构造函数,可根据其参数个数的不同或参数类型的不同来区分它们即构造函数的重载。
+构造函数,是一种特殊的方法。 主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。
-构造函数跟一般的实例方法十分相似;但是与其它方法不同,构造器没有返回类型,不会被继承,且可以有范围修饰符。构造器的函数名称必须和它所属的类的名称相同。 它承担着初始化对象数据成员的任务。
+ /**
+ * 矩形
+ */
+ class Rectangle {
+
+ /**
+ * 构造函数
+ */
+ public Rectangle(int length, int width) {
+ this.length = length;
+ this.width = width;
+ }
+
+ public static void main (String []args){
+ //使用构造函数创建对象
+ Rectangle rectangle = new Rectangle(10,5);
+
+ }
+ }
+
+
+
+特别的一个类可以有多个构造函数,可根据其参数个数的不同或参数类型的不同来区分它们即构造函数的重载。
+
+
+构造函数跟一般的实例方法十分相似;但是与其它方法不同,构造器没有返回类型,不会被继承,且可以有范围修饰符。
+
+构造器的函数名称必须和它所属的类的名称相同。它承担着初始化对象数据成员的任务。
如果在编写一个可实例化的类时没有专门编写构造函数,多数编程语言会自动生成缺省构造器(默认构造函数)。默认构造函数一般会把成员变量的值初始化为默认值,如int -> 0,Integer -> null。
+
+
+如果在编写一个可实例化的类时没有专门编写构造函数,默认情况下,一个Java类中会自动生成一个默认无参构造函数。默认构造函数一般会把成员变量的值初始化为默认值,如int -> 0,Integer -> null。
+
+但是,如果我们手动在某个类中定义了一个有参数的构造函数,那么这个默认的无参构造函数就不会自动添加了。需要手动创建!
+
+ /**
+ * 矩形
+ */
+ class Rectangle {
+
+ /**
+ * 构造函数
+ */
+ public Rectangle(int length, int width) {
+ this.length = length;
+ this.width = width;
+ }
+
+ /**
+ * 无参构造函数
+ */
+ public Rectangle() {
+
+ }
+ }
diff --git a/docs/basics/object-oriented/extends-implement.md b/docs/basics/object-oriented/extends-implement.md
index 0d395526..9594178b 100644
--- a/docs/basics/object-oriented/extends-implement.md
+++ b/docs/basics/object-oriented/extends-implement.md
@@ -1,13 +1,14 @@
-面向对象有三个特征:封装、继承、多态。
+前面的章节我们提到过面向对象有三个特征:封装、继承、多态。
-其中继承和实现都体现了`传递性`。而且明确定义如下:
+继承可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。这种派生方式提现了*传递性*,在Java中,除了继承,还有一种提现传递性的方式叫做实现。
->继承:如果多个类的某个部分的功能相同,那么可以抽象出一个类出来,把他们的相同部分都放到父类里,让他们都继承这个类。
->
->实现:如果多个类处理的目标是一样的,但是处理的方法方式不同,那么就定义一个接口,也就是一个标准,让他们的实现这个接口,各自实现自己具体的处理方法来处理那个目标
+继承和实现两者的明确定义和区别如下:
+>继承(Inheritance):如果多个类的某个部分的功能相同,那么可以抽象出一个类出来,把他们的相同部分都放到父类里,让他们都继承这个类。
+>
+>实现(Implement):如果多个类处理的目标是一样的,但是处理的方法方式不同,那么就定义一个接口,也就是一个标准,让他们的实现这个接口,各自实现自己具体的处理方法来处理那个目标
-所以,继承的根本原因是因为要*复用*,而实现的根本原因是需要定义一个*标准*。
+继承指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力。所以,继承的根本原因是因为要*复用*,而实现的根本原因是需要定义一个*标准*。
在Java中,继承使用`extends`关键字实现,而实现通过`implements`关键字。
@@ -20,8 +21,8 @@ class Car extends Benz implements GasolineCar, ElectroCar{
}
-
```
-在接口中只能定义全局常量(static final)和无实现的方法(Java 8以后可以有defult方法);而在继承中可以定义属性方法,变量,常量等。
+以上,我们定义了一辆汽车,他实现了电动车和汽油车两个标准,但是他属于奔驰这个品牌。像上面这样定义,我们可以最大程度的遵守标准,并且复用奔驰车所有已有的一些功能组件。
+另外,在接口中只能定义全局常量(static final)和无实现的方法(Java 8以后可以有defult方法);而在继承中可以定义属性方法,变量,常量等。
\ No newline at end of file
diff --git a/docs/basics/object-oriented/inheritance-composition.md b/docs/basics/object-oriented/inheritance-composition.md
index 5eff0d32..e2869f3a 100644
--- a/docs/basics/object-oriented/inheritance-composition.md
+++ b/docs/basics/object-oriented/inheritance-composition.md
@@ -1,6 +1,10 @@
-Java是一个面向对象的语言。每一个学习过Java的人都知道,封装、继承、多态是面向对象的三个特征。每个人在刚刚学习继承的时候都会或多或少的有这样一个印象:继承可以帮助我实现类的复用。所以,很多开发人员在需要复用一些代码的时候会很自然的使用类的继承的方式,因为书上就是这么写的(老师就是这么教的)。但是,其实这样做是不对的。长期大量的使用继承会给代码带来很高的维护成本。
+在前面几篇文章中,我们了解了封装、继承、多态是面向对象的三个特征。并且通过对继承和实现的学习,了解到继承可以帮助我实现类的复用。
-本文将介绍组合和继承的概念及区别,并从多方面分析在写代码时如何进行选择。
+所以,很多开发人员在需要复用一些代码的时候会很自然的使用类的继承的方式。
+
+但是,遇到想要复用的场景就直接使用继承,这样做是不对的。长期大量的使用继承会给代码带来很高的维护成本。
+
+本文将介绍一种可以帮助我们复用的新的概念——组合,通过学习组合和继承的概念及区别,并从多方面帮大家分析在写代码时如何进行选择。
## 面向对象的复用技术
@@ -12,7 +16,9 @@ Java是一个面向对象的语言。每一个学习过Java的人都知道,封
## 继承
-继承(Inheritance)是一种联结类与类的层次模型。指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系;继承是一种[`is-a`][1]关系。(图片来自网络,侵删。)
+前面的章节中重点介绍过继承,我们说继承是类与类或者接口与接口之间最常见的一种关系;继承是一种[`is-a`][1]关系。
+
+> is-a:表示"是一个"的关系,如狗是一个动物
![Inheritance][2]
@@ -20,6 +26,8 @@ Java是一个面向对象的语言。每一个学习过Java的人都知道,封
组合(Composition)体现的是整体与部分、拥有的关系,即[`has-a`][3]的关系。
+> is-a:表示"有一个"的关系,如狗有一个尾巴
+
![Composition][4]
## 组合与继承的区别和联系
diff --git a/docs/basics/object-oriented/polymorphism.md b/docs/basics/object-oriented/polymorphism.md
index 3fe9c92b..5289d367 100644
--- a/docs/basics/object-oriented/polymorphism.md
+++ b/docs/basics/object-oriented/polymorphism.md
@@ -5,9 +5,12 @@
如果按照这个概念来定义的话,那么多态应该是一种运行期的状态。
### 多态的必要条件
-为了实现运行期的多态,或者说是动态绑定,需要满足三个条件。
-即有类继承或者接口实现、子类要重写父类的方法、父类的引用指向子类的对象。
+为了实现运行期的多态,或者说是动态绑定,需要满足三个条件:
+
+* 有类继承或者接口实现
+* 子类要重写父类的方法
+* 父类的引用指向子类的对象
简单来一段代码解释下:
@@ -44,19 +47,16 @@
比如Spring 中的IOC出来的对象,你在使用的时候就不知道他是谁,或者说你可以不用关心他是谁。根据具体情况而定。
+> IOC,是Ioc—Inversion of Control 的缩写,中文翻译成“控制反转”,它是一种设计思想,意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
+>
+> 换句话说当我们使用Spring框架的时候,对象是Spring容器创建出来并由容器进行管理,我们只需要使用就行了。
+
### 静态多态
-另外,还有一种说法,包括维基百科也说明,多态还分为动态多态和静态多态。
+上面我们说的多态,是一种运行期的概念。另外,还有一种说法,包括维基百科也说明,多态还分为动态多态和静态多态。
上面提到的那种动态绑定认为是动态多态,因为只有在运行期才能知道真正调用的是哪个类的方法。
-还有一种静态多态,一般认为Java中的函数重载是一种静态多态,因为他需要在编译期决定具体调用哪个方法
-
-关于这个动态静态的说法,我更偏向于重载和多态其实是无关的。
-
-但是也要看情况,普通场合,我会认为只有方法的重写算是多态,毕竟这是我的观点。
-
-但是如果在面试的时候,我“可能”会认为重载也算是多态,毕竟面试官也有他的观点。我会和面试官说:我认为,多态应该是一种运行期特性,Java中的重写是多态的体现。不过也有人提出重载是一种静态多态的想法,这个问题在StackOverflow等网站上有很多人讨论,但是并没有什么定论。我更加倾向于重载不是多态。
-
-这样沟通,既能体现出你了解的多,又能表现出你有自己的思维,不是那种别人说什么就是什么的。
+很多人认为,还有一种静态多态,一般认为Java中的函数重载是一种静态多态,因为他需要在编译期决定具体调用哪个方法。
+但是,其实作者认为,多态应该是一种运行期特性,Java中的方法重写是多态的体现。虽然也有人提出重载是一种静态多态的想法,这个问题在StackOverflow等网站上有很多人讨论,但是并没有什么定论。我更加倾向于重载不是多态。
From b49a09320390ca9466418be568e9a11a4345858e Mon Sep 17 00:00:00 2001
From: "hollis.zhl"
Date: Sun, 28 Feb 2021 17:22:16 +0800
Subject: [PATCH 60/84] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=A4=9A=E7=BB=A7?=
=?UTF-8?q?=E6=89=BF=E7=9F=A5=E8=AF=86=E7=82=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/_sidebar.md | 2 +
.../object-oriented/extends-implement.md | 21 +++++----
.../object-oriented/multiple-inheritance.md | 47 +++++++++++++++++++
docs/menu.md | 2 +
4 files changed, 62 insertions(+), 10 deletions(-)
create mode 100644 docs/basics/object-oriented/multiple-inheritance.md
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index 282bbc6a..9ae46b74 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -17,6 +17,8 @@
* [方法重写与重载](/basics/object-oriented/overloading-vs-overriding.md)
* [Java的继承与实现](/basics/object-oriented/extends-implement.md)
+
+ * [Java为什么不支持多继承](/basics/object-oriented/multiple-inheritance.md)
* [Java的继承与组合](/basics/object-oriented/inheritance-composition.md)
diff --git a/docs/basics/object-oriented/extends-implement.md b/docs/basics/object-oriented/extends-implement.md
index 9594178b..536c5901 100644
--- a/docs/basics/object-oriented/extends-implement.md
+++ b/docs/basics/object-oriented/extends-implement.md
@@ -9,20 +9,21 @@
>实现(Implement):如果多个类处理的目标是一样的,但是处理的方法方式不同,那么就定义一个接口,也就是一个标准,让他们的实现这个接口,各自实现自己具体的处理方法来处理那个目标
继承指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力。所以,继承的根本原因是因为要*复用*,而实现的根本原因是需要定义一个*标准*。
-
+
在Java中,继承使用`extends`关键字实现,而实现通过`implements`关键字。
+
+特别需要注意的是,Java中支持一个类同时实现多个接口,但是不支持同时继承多个类。但是这个问题在Java 8之后也不绝对了。
-Java中支持一个类同时实现多个接口,但是不支持同时继承多个类。
-
->简单点说,就是同样是一台汽车,既可以是电动车,也可以是汽油车,也可以是油电混合的,只要实现不同的标准就行了,但是一台车只能属于一个品牌,一个厂商。
-
-```
-class Car extends Benz implements GasolineCar, ElectroCar{
-
-}
+
+ >简单点说,就是同样是一台汽车,既可以是电动车,也可以是汽油车,也可以是油电混合的,只要实现不同的标准就行了,但是一台车只能属于一个品牌,一个厂商。
+
+ ```
+ class Car extends Benz implements GasolineCar, ElectroCar{
+
+ }
```
以上,我们定义了一辆汽车,他实现了电动车和汽油车两个标准,但是他属于奔驰这个品牌。像上面这样定义,我们可以最大程度的遵守标准,并且复用奔驰车所有已有的一些功能组件。
-另外,在接口中只能定义全局常量(static final)和无实现的方法(Java 8以后可以有defult方法);而在继承中可以定义属性方法,变量,常量等。
\ No newline at end of file
+另外,在接口中只能定义全局常量(static final)和无实现的方法(Java 8以后可以有defult方法);而在继承中可以定义属性方法,变量,常量等。
diff --git a/docs/basics/object-oriented/multiple-inheritance.md b/docs/basics/object-oriented/multiple-inheritance.md
new file mode 100644
index 00000000..c7ab939e
--- /dev/null
+++ b/docs/basics/object-oriented/multiple-inheritance.md
@@ -0,0 +1,47 @@
+前面我们提到过:"Java中支持一个类同时实现多个接口,但是不支持同时继承多个类。但是这个问题在Java 8之后也不绝对了。"
+
+那么,是不是又很很想知道,为什么Java中不支持同时继承多个类呢?
+### 多继承
+
+一个类,只有一个父类的情况,我们叫做单继承。而一个类,同时有多个父类的情况,叫做多继承。
+
+在Java中,一个类,只能通过extends关键字继承一个类,不允许多继承。但是,多继承在其他的面向对象语言中是有可能支持的。
+
+像C++就是支持多继承的,主要是因为编程的过程是对现实世界的一种抽象,而现实世界中,确实存在着需要多继承的情况。比如维基百科中关于多继承举了一个例子:
+
+例如,可以创造一个“哺乳类动物”类别,拥有进食、繁殖等的功能;然后定义一个子类型“猫”,它可以从父类继承上述功能。
+
+但是,"猫"还可以作为"宠物"的子类,拥有一些宠物独有的能力。
+
+所以,有些面向对象语言是支持多重继承的。
+
+但是,多年以来,多重继承一直都是一个敏感的话题,反对者指它增加了程序的复杂性与含糊性。
+
+### 菱形继承问题
+
+
+假设我们有类B和类C,它们都继承了相同的类A。另外我们还有类D,类D通过多重继承机制继承了类B和类C。
+
+![][1]
+
+这时候,因为D同时继承了B和C,并且B和C又同时继承了A,那么,D中就会因为多重继承,继承到两份来自A中的属性和方法。
+
+这时候,在使用D的时候,如果想要调用一个定义在A中的方法时,就会出现歧义。
+
+因为这样的继承关系的形状类似于菱形,因此这个问题被形象地称为菱形继承问题。
+
+而C++为了解决菱形继承问题,又引入了**虚继承**。
+
+因为支持多继承,引入了菱形继承问题,又因为要解决菱形继承问题,引入了虚继承。而经过分析,人们发现我们其实真正想要使用多继承的情况并不多。
+
+所以,在 Java 中,不允许“实现多继承”,即一个类不允许继承多个父类。但是 Java 允许“声明多继承”,即一个类可以实现多个接口,一个接口也可以继承多个父接口。由于接口只允许有方法声明而不允许有方法实现,这就避免了 C++ 中多继承的歧义问题。
+
+
+但是,Java不支持多继承,在Java 8中支持了默认函数(default method )之后就不那么绝对了。
+
+虽然我们还是没办法使用extends同时继承多个类,但是因为有了默认函数,我们有可能通过implements从多个接口中继承到多个默认函数,那么,又如何解决这种情况带来的菱形继承问题呢?
+
+这个问题,我们在后面的Java 8部分单独介绍。
+
+
+ [1]: https://www.hollischuang.com/wp-content/uploads/2021/02/16145019571199.jpg
\ No newline at end of file
diff --git a/docs/menu.md b/docs/menu.md
index bfd90c1d..b309a49d 100644
--- a/docs/menu.md
+++ b/docs/menu.md
@@ -74,6 +74,8 @@ Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer)
* [方法重写与重载](/basics/object-oriented/overloading-vs-overriding.md)
* [Java的继承与实现](/basics/object-oriented/extends-implement.md)
+
+ * [Java为什么不支持多继承](/basics/object-oriented/multiple-inheritance.md)
* [Java的继承与组合](/basics/object-oriented/inheritance-composition.md)
From 7cb4a1a0f9c3f75e8277cb27bbf626ea19ca6f02 Mon Sep 17 00:00:00 2001
From: jihch
Date: Sat, 6 Mar 2021 19:43:19 +0800
Subject: [PATCH 61/84] Update volatile.md
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
线程的工作内存中保存了该线程中“是”用到的变量的主内存副本拷贝 -》 线程的工作内存中保存了该线程中“使”用到的变量的主内存副本拷贝
---
docs/basics/concurrent-coding/volatile.md | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/docs/basics/concurrent-coding/volatile.md b/docs/basics/concurrent-coding/volatile.md
index 1a1a0ff5..1bb32ce9 100644
--- a/docs/basics/concurrent-coding/volatile.md
+++ b/docs/basics/concurrent-coding/volatile.md
@@ -24,7 +24,7 @@
return singleton;
}
}
-
+
如以上代码,是一个比较典型的使用双重锁校验的形式实现单例的,其中使用`volatile`关键字修饰可能被多个线程同时访问到的singleton。
@@ -44,7 +44,7 @@
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
-我们在[再有人问你Java内存模型是什么,就把这篇文章发给他][1]中分析过:Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。所以,就可能出现线程1改了某个变量的值,但是线程2不可见的情况。
+我们在[再有人问你Java内存模型是什么,就把这篇文章发给他][1]中分析过:Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。所以,就可能出现线程1改了某个变量的值,但是线程2不可见的情况。
前面的关于`volatile`的原理中介绍过了,Java中的`volatile`关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次使用之前都从主内存刷新。因此,可以使用`volatile`来保证多线程操作时变量的可见性。
@@ -103,7 +103,7 @@ volatile可以禁止指令重排,这就保证了代码的程序会严格按照
System.out.println(test.inc);
}
}
-
+
以上代码比较简单,就是创建10个线程,然后分别执行1000次`i++`操作。正常情况下,程序的输出结果应该是10000,但是,多次执行的结果都小于10000。这其实就是`volatile`无法满足原子性的原因。
@@ -129,10 +129,10 @@ volatile可以禁止指令重排,这就保证了代码的程序会严格按照
return singleton;
}
}
-
+
答案,我们在下一篇文章:既生synchronized,何生亮volatile中介绍,敬请关注我的博客(http://47.103.216.138)和公众号(Hollis)。
- [1]: http://47.103.216.138/archives/2550
- [2]: http://47.103.216.138/archives/2637
- [3]: http://47.103.216.138/archives/2618
\ No newline at end of file
+[1]: http://47.103.216.138/archives/2550
+[2]: http://47.103.216.138/archives/2637
+[3]: http://47.103.216.138/archives/2618
\ No newline at end of file
From 3c2eb60cc3a8fe9e78f0ede8779408b9f191c577 Mon Sep 17 00:00:00 2001
From: "hollis.zhl"
Date: Mon, 15 Mar 2021 10:30:56 +0800
Subject: [PATCH 62/84] fix some typos
---
docs/basics/java-basic/hashmap-capacity.md | 2 +-
docs/basics/java-basic/length-of-string.md | 2 +-
docs/basics/object-oriented/multiple-inheritance.md | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/basics/java-basic/hashmap-capacity.md b/docs/basics/java-basic/hashmap-capacity.md
index 31042370..e6fb9eef 100644
--- a/docs/basics/java-basic/hashmap-capacity.md
+++ b/docs/basics/java-basic/hashmap-capacity.md
@@ -71,7 +71,7 @@ HashMap中的size和capacity之间的区别其实解释起来也挺简单的。
System.out.println("capacity : " + capacity.invoke(map));
-分别执行以上3段代码,分别输出:**capacity : 2、capacity : 8、capacity : 16**。
+分别执行以上3段代码,分别输出:**capacity : 1、capacity : 8、capacity : 16**。
也就是说,默认情况下HashMap的容量是16,但是,如果用户通过构造函数指定了一个数字作为容量,那么Hash会选择大于该数字的第一个2的幂作为容量。(1->1、7->8、9->16)
diff --git a/docs/basics/java-basic/length-of-string.md b/docs/basics/java-basic/length-of-string.md
index 3bee1665..f90fbbe1 100644
--- a/docs/basics/java-basic/length-of-string.md
+++ b/docs/basics/java-basic/length-of-string.md
@@ -124,7 +124,7 @@ int 是一个 32 位变量类型,取正数部分来算的话,他们最长可
得到的字符串长度就有10万,另外我之前在实际应用中遇到过这个问题。
-之前一次系统对接,需要传输高清图片,约定的传输方式是对方将图片转成BASE6编码,我们接收到之后再转成图片。
+之前一次系统对接,需要传输高清图片,约定的传输方式是对方将图片转成BASE64编码,我们接收到之后再转成图片。
在将BASE64编码后的内容赋值给字符串的时候就抛了异常。
diff --git a/docs/basics/object-oriented/multiple-inheritance.md b/docs/basics/object-oriented/multiple-inheritance.md
index c7ab939e..b6bcb235 100644
--- a/docs/basics/object-oriented/multiple-inheritance.md
+++ b/docs/basics/object-oriented/multiple-inheritance.md
@@ -34,7 +34,7 @@
因为支持多继承,引入了菱形继承问题,又因为要解决菱形继承问题,引入了虚继承。而经过分析,人们发现我们其实真正想要使用多继承的情况并不多。
-所以,在 Java 中,不允许“实现多继承”,即一个类不允许继承多个父类。但是 Java 允许“声明多继承”,即一个类可以实现多个接口,一个接口也可以继承多个父接口。由于接口只允许有方法声明而不允许有方法实现,这就避免了 C++ 中多继承的歧义问题。
+所以,在 Java 中,不允许“实现多继承”,即一个类不允许继承多个父类。但是 Java 允许“声明多继承”,即一个类可以实现多个接口,一个接口也可以继承多个父接口。由于接口只允许有方法声明而不允许有方法实现(Java 8以前),这就避免了 C++ 中多继承的歧义问题。
但是,Java不支持多继承,在Java 8中支持了默认函数(default method )之后就不那么绝对了。
From 88a1ceaf8e59c3502412d62ab7237d8c569d2e7c Mon Sep 17 00:00:00 2001
From: "hollis.zhl"
Date: Mon, 15 Mar 2021 17:43:27 +0800
Subject: [PATCH 63/84] =?UTF-8?q?=E5=AE=8C=E5=96=84=E9=83=A8=E5=88=86?=
=?UTF-8?q?=E7=9F=A5=E8=AF=86=E7=82=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/_sidebar.md | 6 +-
.../basics/java-basic/stop-use-enum-in-api.md | 175 ++++++++++++++++++
.../stop-using-equlas-in-bigdecimal.md | 174 +++++++++++++++++
docs/menu.md | 6 +-
4 files changed, 357 insertions(+), 4 deletions(-)
create mode 100644 docs/basics/java-basic/stop-use-enum-in-api.md
create mode 100644 docs/basics/java-basic/stop-using-equlas-in-bigdecimal.md
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index 9ae46b74..7f1def2a 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -172,7 +172,7 @@
* [枚举的线程安全性问题](/basics/java-basic/enum-thread-safe.md)
- * 为什么不建议在对外接口中使用枚举
+ * [为什么不建议在对外接口中使用枚举](/basics/java-basic/stop-use-enum-in-api.md)
* IO
@@ -396,7 +396,9 @@
* 为什么0.1+0.2不等于0.3
- * 为什么不能使用BigDecimal的equals比较大小
+ * [为什么不能使用BigDecimal的equals比较大小](/basics/java-basic/stop-using-equlas-in-bigdecimal.md)
+
+ * 为什么不能直接使用double创建一个BigDecimal
* Java 8
diff --git a/docs/basics/java-basic/stop-use-enum-in-api.md b/docs/basics/java-basic/stop-use-enum-in-api.md
new file mode 100644
index 00000000..95f54aff
--- /dev/null
+++ b/docs/basics/java-basic/stop-use-enum-in-api.md
@@ -0,0 +1,175 @@
+最近,我们的线上环境出现了一个问题,线上代码在执行过程中抛出了一个IllegalArgumentException,分析堆栈后,发现最根本的的异常是以下内容:
+
+ java.lang.IllegalArgumentException:
+ No enum constant com.a.b.f.m.a.c.AType.P_M
+
+
+大概就是以上的内容,看起来还是很简单的,提示的错误信息就是在AType这个枚举类中没有找到P_M这个枚举项。
+
+于是经过排查,我们发现,在线上开始有这个异常之前,该应用依赖的一个下游系统有发布,而发布过程中是一个API包发生了变化,主要变化内容是在一个RPC接口的Response返回值类中的一个枚举参数AType中增加了P_M这个枚举项。
+
+但是下游系统发布时,并未通知到我们负责的这个系统进行升级,所以就报错了。
+
+我们来分析下为什么会发生这样的情况。
+
+### 问题重现
+
+首先,下游系统A提供了一个二方库的某一个接口的返回值中有一个参数类型是枚举类型。
+
+> 一方库指的是本项目中的依赖 二方库指的是公司内部其他项目提供的依赖 三方库指的是其他组织、公司等来自第三方的依赖
+
+ public interface AFacadeService {
+
+ public AResponse doSth(ARequest aRequest);
+ }
+
+ public Class AResponse{
+
+ private Boolean success;
+
+ private AType aType;
+ }
+
+ public enum AType{
+
+ P_T,
+
+ A_B
+ }
+
+
+然后B系统依赖了这个二方库,并且会通过RPC远程调用的方式调用AFacadeService的doSth方法。
+
+ public class BService {
+
+ @Autowired
+ AFacadeService aFacadeService;
+
+ public void doSth(){
+ ARequest aRequest = new ARequest();
+
+ AResponse aResponse = aFacadeService.doSth(aRequest);
+
+ AType aType = aResponse.getAType();
+ }
+ }
+
+
+这时候,如果A和B系统依赖的都是同一个二方库的话,两者使用到的枚举AType会是同一个类,里面的枚举项也都是一致的,这种情况不会有什么问题。
+
+但是,如果有一天,这个二方库做了升级,在AType这个枚举类中增加了一个新的枚举项P_M,这时候只有系统A做了升级,但是系统B并没有做升级。
+
+那么A系统依赖的的AType就是这样的:
+
+ public enum AType{
+
+ P_T,
+
+ A_B,
+
+ P_M
+ }
+
+
+而B系统依赖的AType则是这样的:
+
+ public enum AType{
+
+ P_T,
+
+ A_B
+ }
+
+
+这种情况下**,在B系统通过RPC调用A系统的时候,如果A系统返回的AResponse中的aType的类型位新增的P_M时候,B系统就会无法解析。一般在这种时候,RPC框架就会发生反序列化异常。导致程序被中断。**
+
+### 原理分析
+
+这个问题的现象我们分析清楚了,那么再来看下原理是怎样的,为什么出现这样的异常呢。
+
+其实这个原理也不难,这类**RPC框架大多数会采用JSON的格式进行数据传输**,也就是客户端会将返回值序列化成JSON字符串,而服务端会再将JSON字符串反序列化成一个Java对象。
+
+而JSON在反序列化的过程中,对于一个枚举类型,会尝试调用对应的枚举类的valueOf方法来获取到对应的枚举。
+
+而我们查看枚举类的valueOf方法的实现时,就可以发现,**如果从枚举类中找不到对应的枚举项的时候,就会抛出IllegalArgumentException**:
+
+ public static > T valueOf(Class enumType,
+ String name) {
+ T result = enumType.enumConstantDirectory().get(name);
+ if (result != null)
+ return result;
+ if (name == null)
+ throw new NullPointerException("Name is null");
+ throw new IllegalArgumentException(
+ "No enum constant " + enumType.getCanonicalName() + "." + name);
+ }
+
+
+关于这个问题,其实在《阿里巴巴Java开发手册》中也有类似的约定:
+
+![-w1538][1]
+
+这里面规定"**对于二方库的参数可以使用枚举,但是返回值不允许使用枚举**"。这背后的思考就是本文上面提到的内容。
+
+### 扩展思考
+
+**为什么参数中可以有枚举?**
+
+不知道大家有没有想过这个问题,其实这个就和二方库的职责有点关系了。
+
+一般情况下,A系统想要提供一个远程接口给别人调用的时候,就会定义一个二方库,告诉其调用方如何构造参数,调用哪个接口。
+
+而这个二方库的调用方会根据其中定义的内容来进行调用。而参数的构造过程是由B系统完成的,如果B系统使用到的是一个旧的二方库,使用到的枚举自然是已有的一些,新增的就不会被用到,所以这样也不会出现问题。
+
+比如前面的例子,B系统在调用A系统的时候,构造参数的时候使用到AType的时候就只有P_T和A_B两个选项,虽然A系统已经支持P_M了,但是B系统并没有使用到。
+
+如果B系统想要使用P_M,那么就需要对该二方库进行升级。
+
+但是,返回值就不一样了,返回值并不受客户端控制,服务端返回什么内容是根据他自己依赖的二方库决定的。
+
+但是,其实相比较于手册中的规定,**我更加倾向于,在RPC的接口中入参和出参都不要使用枚举。**
+
+一般,我们要使用枚举都是有几个考虑:
+
+* 1、枚举严格控制下游系统的传入内容,避免非法字符。
+
+* 2、方便下游系统知道都可以传哪些值,不容易出错。
+
+不可否认,使用枚举确实有一些好处,但是我不建议使用主要有以下原因:
+
+* 1、如果二方库升级,并且删除了一个枚举中的部分枚举项,那么入参中使用枚举也会出现问题,调用方将无法识别该枚举项。
+
+* 2、有的时候,上下游系统有多个,如C系统通过B系统间接调用A系统,A系统的参数是由C系统传过来的,B系统只是做了一个参数的转换与组装。这种情况下,一旦A系统的二方库升级,那么B和C都要同时升级,任何一个不升级都将无法兼容。
+
+**我其实建议大家在接口中使用字符串代替枚举**,相比较于枚举这种强类型,字符串算是一种弱类型。
+
+如果使用字符串代替RPC接口中的枚举,那么就可以避免上面我们提到的两个问题,上游系统只需要传递字符串就行了,而具体的值的合法性,只需要在A系统内自己进行校验就可以了。
+
+**为了方便调用者使用,可以使用javadoc的@see注解表明这个字符串字段的取值从那个枚举中获取。**
+
+ public Class AResponse{
+
+ private Boolean success;
+
+ /**
+ * @see AType
+ */
+ private String aType;
+ }
+
+
+对于像阿里这种比较庞大的互联网公司,**随便提供出去的一个接口,可能有上百个调用方**,而接口升级也是常态,**我们根本做不到每次二方库升级之后要求所有调用者跟着一起升级**,这是完全不现实的,并且对于有些调用者来说,他用不到新特性,完全没必要做升级。
+
+还有一种看起来比较特殊,但是实际上比较常见的情况,就是有的时候一个接口的声明在A包中,而一些枚举常量定义在B包中,比较常见的就是阿里的交易相关的信息,订单分很多层次,每次引入一个包的同时都需要引入几十个包。
+
+对于调用者来说,我肯定是不希望我的系统引入太多的依赖的,**一方面依赖多了会导致应用的编译过程很慢,并且很容易出现依赖冲突问题。**
+
+所以,在调用下游接口的时候,如果参数中字段的类型是枚举的话,那我没办法,必须得依赖他的二方库。但是如果不是枚举,只是一个字符串,那我就可以选择不依赖。
+
+所以,我们在定义接口的时候,会尽量避免使用枚举这种强类型。规范中规定在返回值中不允许使用,而我自己要求更高,就是即使在接口的入参中我也很少使用。
+
+最后,我只是不建议在对外提供的接口的出入参中使用枚举,并不是说彻底不要用枚举,我之前很多文章也提到过,枚举有很多好处,我在代码中也经常使用。所以,切不可因噎废食。
+
+当然,文中的观点仅代表我个人,具体是是不是适用其他人,其他场景或者其他公司的实践,需要读者们自行分辨下,建议大家在使用的时候可以多思考一下。
+
+ [1]: https://www.hollischuang.com/wp-content/uploads/2020/11/16066271055035-scaled.jpg
\ No newline at end of file
diff --git a/docs/basics/java-basic/stop-using-equlas-in-bigdecimal.md b/docs/basics/java-basic/stop-using-equlas-in-bigdecimal.md
new file mode 100644
index 00000000..15895f69
--- /dev/null
+++ b/docs/basics/java-basic/stop-using-equlas-in-bigdecimal.md
@@ -0,0 +1,174 @@
+BigDecimal,相信对于很多人来说都不陌生,很多人都知道他的用法,这是一种java.math包中提供的一种可以用来进行精确运算的类型。
+
+很多人都知道,在进行金额表示、金额计算等场景,不能使用double、float等类型,而是要使用对精度支持的更好的BigDecimal。
+
+所以,很多支付、电商、金融等业务中,BigDecimal的使用非常频繁。而且不得不说这是一个非常好用的类,其内部自带了很多方法,如加,减,乘,除等运算方法都是可以直接调用的。
+
+除了需要用BigDecimal表示数字和进行数字运算以外,代码中还经常需要对于数字进行相等判断。
+
+关于这个知识点,在最新版的《阿里巴巴Java开发手册》中也有说明:
+
+![][1]
+
+这背后的思考是什么呢?
+
+我在之前的CodeReview中,看到过以下这样的低级错误:
+
+ if(bigDecimal == bigDecimal1){
+ // 两个数相等
+ }
+
+
+这种错误,相信聪明的读者一眼就可以看出问题,**因为BigDecimal是对象,所以不能用`==`来判断两个数字的值是否相等。**
+
+以上这种问题,在有一定的经验之后,还是可以避免的,但是聪明的读者,看一下以下这行代码,你觉得他有问题吗:
+
+ if(bigDecimal.equals(bigDecimal1)){
+ // 两个数相等
+ }
+
+
+可以明确的告诉大家,以上这种写法,可能得到的结果和你预想的不一样!
+
+先来做个实验,运行以下代码:
+
+ BigDecimal bigDecimal = new BigDecimal(1);
+ BigDecimal bigDecimal1 = new BigDecimal(1);
+ System.out.println(bigDecimal.equals(bigDecimal1));
+
+
+ BigDecimal bigDecimal2 = new BigDecimal(1);
+ BigDecimal bigDecimal3 = new BigDecimal(1.0);
+ System.out.println(bigDecimal2.equals(bigDecimal3));
+
+
+ BigDecimal bigDecimal4 = new BigDecimal("1");
+ BigDecimal bigDecimal5 = new BigDecimal("1.0");
+ System.out.println(bigDecimal4.equals(bigDecimal5));
+
+
+以上代码,输出结果为:
+
+ true
+ true
+ false
+
+
+### BigDecimal的equals原理
+
+通过以上代码示例,我们发现,在使用BigDecimal的equals方法对1和1.0进行比较的时候,有的时候是true(当使用int、double定义BigDecimal时),有的时候是false(当使用String定义BigDecimal时)。
+
+那么,为什么会出现这样的情况呢,我们先来看下BigDecimal的equals方法。
+
+在BigDecimal的JavaDoc中其实已经解释了其中原因:
+
+ Compares this BigDecimal with the specified Object for equality. Unlike compareTo, this method considers two BigDecimal objects equal only if they are equal in value and scale (thus 2.0 is not equal to 2.00 when compared by this method)
+
+
+大概意思就是,**equals方法和compareTo并不一样,equals方法会比较两部分内容,分别是值(value)和标度(scale)**
+
+
+对应的代码如下:
+
+![][2]
+
+所以,我们以上代码定义出来的两个BigDecimal对象(bigDecimal4和bigDecimal5)的标度是不一样的,所以使用equals比较的结果就是false了。
+
+尝试着对代码进行debug,在debug的过程中我们也可以看到bigDecimal4的标度时0,而bigDecimal5的标度是1。
+
+![][3]
+
+到这里,我们大概解释清楚了,之所以equals比较bigDecimal4和bigDecimal5的结果是false,是因为标度不同。
+
+那么,为什么标度不同呢?为什么bigDecimal2和bigDecimal3的标度是一样的(当使用int、double定义BigDecimal时),而bigDecimal4和bigDecimal5却不一样(当使用String定义BigDecimal时)呢?
+
+### 为什么标度不同
+
+这个就涉及到BigDecimal的标度问题了,这个问题其实是比较复杂的,由于不是本文的重点,这里面就简单介绍一下吧。大家感兴趣的话,后面单独讲。
+
+首先,BigDecimal一共有以下4个构造方法:
+
+ BigDecimal(int)
+ BigDecimal(double)
+ BigDecimal(long)
+ BigDecimal(String)
+
+
+以上四个方法,创建出来的的BigDecimal的标度是不同的。
+
+#### BigDecimal(long) 和BigDecimal(int)
+
+首先,最简单的就是**BigDecimal(long) 和BigDecimal(int),因为是整数,所以标度就是0** :
+
+ public BigDecimal(int val) {
+ this.intCompact = val;
+ this.scale = 0;
+ this.intVal = null;
+ }
+
+ public BigDecimal(long val) {
+ this.intCompact = val;
+ this.intVal = (val == INFLATED) ? INFLATED_BIGINT : null;
+ this.scale = 0;
+ }
+
+
+#### BigDecimal(double)
+
+而对于BigDecimal(double) ,**当我们使用new BigDecimal(0.1)创建一个BigDecimal 的时候,其实创建出来的值并不是整好等于0.1的,而是0.1000000000000000055511151231257827021181583404541015625 。这是因为doule自身表示的只是一个近似值。**
+
+那么,无论我们使用new BigDecimal(0.1)还是new BigDecimal(0.10)定义,他的近似值都是0.1000000000000000055511151231257827021181583404541015625这个,那么他的标度就是这个数字的位数,即55。
+
+![][4]
+
+其他的浮点数也同样的道理。对于new BigDecimal(1.0)这样的形式来说,因为他本质上也是个整数,所以他创建出来的数字的标度就是0。
+
+所以,因为BigDecimal(1.0)和BigDecimal(1.00)的标度是一样的,所以在使用equals方法比较的时候,得到的结果就是true。
+
+#### BigDecimal(string)
+
+而对于BigDecimal(double) ,**当我们使用new BigDecimal("0.1")创建一个BigDecimal 的时候,其实创建出来的值正好就是等于0.1的。那么他的标度也就是1。**
+
+如果使用new BigDecimal("0.10000"),那么创建出来的数就是0.10000,标度也就是5。
+
+所以,因为BigDecimal("1.0")和BigDecimal("1.00")的标度不一样,所以在使用equals方法比较的时候,得到的结果就是false。
+
+### 如何比较BigDecimal
+
+前面,我们解释了BigDecimal的equals方法,其实不只是会比较数字的值,还会对其标度进行比较。
+
+所以,当我们使用equals方法判断判断两个数是否相等的时候,是极其严格的。
+
+那么,如果我们只想判断两个BigDecimal的值是否相等,那么该如何判断呢?
+
+**BigDecimal中提供了compareTo方法,这个方法就可以只比较两个数字的值,如果两个数相等,则返回0。**
+
+ BigDecimal bigDecimal4 = new BigDecimal("1");
+ BigDecimal bigDecimal5 = new BigDecimal("1.0000");
+ System.out.println(bigDecimal4.compareTo(bigDecimal5));
+
+
+以上代码,输出结果:
+
+ 0
+
+
+其源码如下:
+
+![][5]
+
+### 总结
+
+BigDecimal是一个非常好用的表示高精度数字的类,其中提供了很多丰富的方法。
+
+但是,他的equals方法使用的时候需要谨慎,因为他在比较的时候,不仅比较两个数字的值,还会比较他们的标度,只要这两个因素有一个是不相等的,那么结果也是false、
+
+如果读者想要对两个BigDecimal的数值进行比较的话,可以使用compareTo方法。
+
+
+ [1]: https://www.hollischuang.com/wp-content/uploads/2020/09/16004945569932.jpg
+ [2]: https://www.hollischuang.com/wp-content/uploads/2020/09/16004955317132.jpg
+ [3]: https://www.hollischuang.com/wp-content/uploads/2020/09/16004956382289.jpg
+ [4]: https://www.hollischuang.com/wp-content/uploads/2020/09/16004965161081.jpg
+ [5]: https://www.hollischuang.com/wp-content/uploads/2020/09/16004972460075.jpg
+ [6]: https://www.hollischuang.com/wp-content/uploads/2020/09/16004976158870.jpg
\ No newline at end of file
diff --git a/docs/menu.md b/docs/menu.md
index b309a49d..a5569048 100644
--- a/docs/menu.md
+++ b/docs/menu.md
@@ -229,7 +229,7 @@ Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer)
* [枚举的线程安全性问题](/basics/java-basic/enum-thread-safe.md)
- * 为什么不建议在对外接口中使用枚举
+ * [为什么不建议在对外接口中使用枚举](/basics/java-basic/stop-use-enum-in-api.md)
* IO
@@ -453,7 +453,9 @@ Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer)
* 为什么0.1+0.2不等于0.3
- * 为什么不能使用BigDecimal的equals比较大小
+ * [为什么不能使用BigDecimal的equals比较大小](/basics/java-basic/stop-using-equlas-in-bigdecimal.md)
+
+ * 为什么不能直接使用double创建一个BigDecimal
* Java 8
From 1bc5f7c364aa20f9889403b719f9cceb47e6ca97 Mon Sep 17 00:00:00 2001
From: Wenming Fu
Date: Sun, 21 Mar 2021 21:55:47 +0800
Subject: [PATCH 64/84] fix bad url - 404 reported by bilibili
---
docs/basics/java-basic/length-of-string.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/basics/java-basic/length-of-string.md b/docs/basics/java-basic/length-of-string.md
index f90fbbe1..7c3fcceb 100644
--- a/docs/basics/java-basic/length-of-string.md
+++ b/docs/basics/java-basic/length-of-string.md
@@ -2,7 +2,7 @@
这次在之前那篇文章的基础上除了增加了一些验证过程外,还有些错误内容的修正。我这次在分析过程中会尝试对Jdk的编译过程进行debug,并且会参考一些JVM规范等全方面的介绍下这个知识点。
-因为这个问题涉及到Java的编译原理相关的知识,所以通过视频的方式讲解会更加容易理解一些,视频我上传到了B站:https://www.bilibili.com/video/BV1uK4y1t7H1/。
+因为这个问题涉及到Java的编译原理相关的知识,所以通过视频的方式讲解会更加容易理解一些,视频我上传到了B站:[【灵魂拷问】Java中的String到底有没有长度限制?](https://www.bilibili.com/video/BV1uK4y1t7H1/)
### String的长度限制
From a4282f947a6ad44b576dd537b3d37f25f66f789c Mon Sep 17 00:00:00 2001
From: "hollis.zhl"
Date: Sat, 24 Apr 2021 16:39:58 +0800
Subject: [PATCH 65/84] =?UTF-8?q?=E9=83=A8=E5=88=86=E7=9F=A5=E8=AF=86?=
=?UTF-8?q?=E5=86=85=E5=AE=B9=E4=BF=AE=E6=94=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/_sidebar.md | 2 +
docs/basics/java-basic/integer-scope.md | 4 +-
docs/basics/java-basic/replace-in-string.md | 83 +-----
docs/basics/java-basic/string-append.md | 30 +-
docs/basics/java-basic/string-concat.md | 257 ++++++++----------
.../java-basic/stringjoiner-in-java8.md | 158 +++++++++++
docs/menu.md | 2 +
7 files changed, 322 insertions(+), 214 deletions(-)
create mode 100644 docs/basics/java-basic/stringjoiner-in-java8.md
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index 7f1def2a..fb2a2b59 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -73,6 +73,8 @@
* [String对“+”的重载](/basics/java-basic/string-append.md)
* [字符串拼接的几种方式和区别](/basics/java-basic/string-concat.md)
+
+ * [Java 8中的StringJoiner](/basics/java-basic/stringjoiner-in-java8.md)
* [String.valueOf和Integer.toString的区别](/basics/java-basic/value-of-vs-to-string.md)
diff --git a/docs/basics/java-basic/integer-scope.md b/docs/basics/java-basic/integer-scope.md
index 9edbf3b2..6fc1c9e3 100644
--- a/docs/basics/java-basic/integer-scope.md
+++ b/docs/basics/java-basic/integer-scope.md
@@ -3,9 +3,11 @@ Java中的整型主要包含byte、short、int和long这四种,表示的数字
先来个简单的科普,1字节=8位(bit)。java中的整型属于有符号数。
先来看计算中8bit可以表示的数字:
+
最小值:10000000 (-128)(-2^7)
+
最大值:01111111(127)(2^7-1)
-具体计算方式参考:[Java中,为什么byte类型的取值范围为-128~127? - CSDN博客](https://blog.csdn.net/qq_23418393/article/details/57421688)
+
整型的这几个类型中,
diff --git a/docs/basics/java-basic/replace-in-string.md b/docs/basics/java-basic/replace-in-string.md
index 4cb36dd1..3b66cfaf 100644
--- a/docs/basics/java-basic/replace-in-string.md
+++ b/docs/basics/java-basic/replace-in-string.md
@@ -12,72 +12,17 @@ replaceAll和replaceFirst的区别主要是替换的内容不同,replaceAll是
### 用法例子
-一以下例子参考:http://www.51gjie.com/java/771.html
-
-1. replaceAll() 替换符合正则的所有文字
-
-```
-//文字替换(全部)
-Pattern pattern = Pattern.compile("正则表达式");
-Matcher matcher = pattern.matcher("正则表达式 Hello World,正则表达式 Hello World");
-//替换所有符合正则的数据
-System.out.println(matcher.replaceAll("Java"));
-
-```
-
-
-2. replaceFirst() 替换第一个符合正则的数据
-
-```
-//文字替换(首次出现字符)
-Pattern pattern = Pattern.compile("正则表达式");
-Matcher matcher = pattern.matcher("正则表达式 Hello World,正则表达式 Hello World");
-//替换第一个符合正则的数据
-System.out.println(matcher.replaceFirst("Java"));
-
-```
-
-3. replaceAll()替换所有html标签
-
-```
-//去除html标记
-Pattern pattern = Pattern.compile("<.+?>", Pattern.DOTALL);
-Matcher matcher = pattern.matcher("主页");
-String string = matcher.replaceAll("");
-System.out.println(string);
-
-```
-
-4. replaceAll() 替换指定文字
-```
-//替换指定{}中文字
-String str = "Java目前的发展史是由{0}年-{1}年";
-String[][] object = {
- new String[] {
- "\\{0\\}",
- "1995"
- },
- new String[] {
- "\\{1\\}",
- "2007"
- }
-};
-System.out.println(replace(str, object));
-public static String replace(final String sourceString, Object[] object) {
- String temp = sourceString;
- for (int i = 0; i < object.length; i++) {
- String[] result = (String[]) object[i];
- Pattern pattern = Pattern.compile(result[0]);
- Matcher matcher = pattern.matcher(temp);
- temp = matcher.replaceAll(result[1]);
- }
- return temp;
-}
-
-```
-
-5. replace()替换字符串
-
-```
-System.out.println("abac".replace("a", "\\a")); //\ab\ac
-```
+ String string = "abc123adb23456aa";
+ System.out.println(string);//abc123adb23456aa
+
+ //使用replace将a替换成H
+ System.out.println(string.replace("a","H"));//Hbc123Hdb23456HH
+ //使用replaceFirst将第一个a替换成H
+ System.out.println(string.replaceFirst("a","H"));//Hbc123adb23456aa
+ //使用replace将a替换成H
+ System.out.println(string.replaceAll("a","H"));//Hbc123Hdb23456HH
+
+ //使用replaceFirst将第一个数字替换成H
+ System.out.println(string.replaceFirst("\\d","H"));//abcH23adb23456aa
+ //使用replaceFirst将所有数字替换成H
+ System.out.println(string.replaceAll("\\d","H"));//abcHHHadbHHHHHaa
\ No newline at end of file
diff --git a/docs/basics/java-basic/string-append.md b/docs/basics/java-basic/string-append.md
index 6d908651..fea68cef 100644
--- a/docs/basics/java-basic/string-append.md
+++ b/docs/basics/java-basic/string-append.md
@@ -1,3 +1,29 @@
-1. String s = "a" + "b",编译器会进行常量折叠(因为两个都是编译期常量,编译期可知),即变成 String s = "ab"
+有人把Java中使用+拼接字符串的功能理解为运算符重载。其实并不是,Java是不支持运算符重载的。这其实只是Java提供的一个语法糖。
-2. 对于能够进行优化的(String s = "a" + 变量 等)用 StringBuilder 的 append() 方法替代,最后调用 toString() 方法 (底层就是一个 new String())
\ No newline at end of file
+>运算符重载:在计算机程序设计中,运算符重载(英语:operator overloading)是多态的一种。运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
+
+>语法糖:语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。
+
+前面提到过,使用+拼接字符串,其实只是Java提供的一个语法糖, 那么,我们就来解一解这个语法糖,看看他的内部原理到底是如何实现的。
+
+还是这样一段代码。我们把他生成的字节码进行反编译,看看结果。
+
+ String wechat = "Hollis";
+ String introduce = "每日更新Java相关技术文章";
+ String hollis = wechat + "," + introduce;
+
+反编译后的内容如下,反编译工具为jad。
+
+ String wechat = "Hollis";
+ String introduce = "\u6BCF\u65E5\u66F4\u65B0Java\u76F8\u5173\u6280\u672F\u6587\u7AE0";//每日更新Java相关技术文章
+ String hollis = (new StringBuilder()).append(wechat).append(",").append(introduce).toString();
+
+通过查看反编译以后的代码,我们可以发现,原来字符串常量在拼接过程中,是将String转成了StringBuilder后,使用其append方法进行处理的。
+
+那么也就是说,Java中的+对字符串的拼接,其实现原理是使用StringBuilder.append。
+
+但是,String的使用+字符串拼接也不全都是基于StringBuilder.append,还有种特殊情况,那就是如果是两个固定的字面量拼接,如:
+
+ String s = "a" + "b"
+
+编译器会进行常量折叠(因为两个都是编译期常量,编译期可知),直接变成 String s = "ab"。
diff --git a/docs/basics/java-basic/string-concat.md b/docs/basics/java-basic/string-concat.md
index 9fa634ff..0b7f73d5 100644
--- a/docs/basics/java-basic/string-concat.md
+++ b/docs/basics/java-basic/string-concat.md
@@ -16,9 +16,8 @@
其实,所有的所谓字符串拼接,都是重新生成了一个新的字符串。下面一段字符串拼接代码:
-String s = "abcd";
-s = s.concat("ef");
-
+ String s = "abcd";
+ s = s.concat("ef");
其实最后我们得到的s已经是一个新的字符串了。如下图
@@ -32,24 +31,17 @@ s中保存的是一个重新创建出来的String对象的引用。
在Java中,拼接字符串最简单的方式就是直接使用符号`+`来拼接。如:
-String wechat = "Hollis";
-String introduce = "每日更新Java相关技术文章";
-String hollis = wechat + "," + introduce;
-
-
-这里要特别说明一点,有人把Java中使用`+`拼接字符串的功能理解为**运算符重载**。其实并不是,**Java是不支持运算符重载的**。这其实只是Java提供的一个**语法糖**。后面再详细介绍。
-
-> 运算符重载:在计算机程序设计中,运算符重载(英语:operator overloading)是多态的一种。运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
->
-> 语法糖:语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。
+ String wechat = "Hollis";
+ String introduce = "每日更新Java相关技术文章";
+ String hollis = wechat + "," + introduce;
**concat**
除了使用`+`拼接字符串之外,还可以使用String类中的方法concat方法来拼接字符串。如:
-String wechat = "Hollis";
-String introduce = "每日更新Java相关技术文章";
-String hollis = wechat.concat(",").concat(introduce);
-
+ String wechat = "Hollis";
+ String introduce = "每日更新Java相关技术文章";
+ String hollis = wechat.concat(",").concat(introduce);
+
**StringBuffer**
@@ -57,26 +49,25 @@ String hollis = wechat.concat(",").concat(introduce);
使用`StringBuffer`可以方便的对字符串进行拼接。如:
-StringBuffer wechat = new StringBuffer("Hollis");
-String introduce = "每日更新Java相关技术文章";
-StringBuffer hollis = wechat.append(",").append(introduce);
-
+ StringBuffer wechat = new StringBuffer("Hollis");
+ String introduce = "每日更新Java相关技术文章";
+ StringBuffer hollis = wechat.append(",").append(introduce);
+
**StringBuilder**
除了`StringBuffer`以外,还有一个类`StringBuilder`也可以使用,其用法和`StringBuffer`类似。如:
-StringBuilder wechat = new StringBuilder("Hollis");
-String introduce = "每日更新Java相关技术文章";
-StringBuilder hollis = wechat.append(",").append(introduce);
-
+ StringBuilder wechat = new StringBuilder("Hollis");
+ String introduce = "每日更新Java相关技术文章";
+ StringBuilder hollis = wechat.append(",").append(introduce);
**StringUtils.join**
除了JDK中内置的字符串拼接方法,还可以使用一些开源类库中提供的字符串拼接方法名,如`apache.commons中`提供的`StringUtils`类,其中的`join`方法可以拼接字符串。
-String wechat = "Hollis";
-String introduce = "每日更新Java相关技术文章";
-System.out.println(StringUtils.join(wechat, ",", introduce));
-
+ String wechat = "Hollis";
+ String introduce = "每日更新Java相关技术文章";
+ System.out.println(StringUtils.join(wechat, ",", introduce));
+
这里简单说一下,StringUtils中提供的join方法,最主要的功能是:将数组或集合以某拼接符拼接到一起形成新的字符串,如:
@@ -96,41 +87,23 @@ System.out.println(StringUtils.join(wechat, ",", introduce));
### 使用`+`拼接字符串的实现原理
-前面提到过,使用`+`拼接字符串,其实只是Java提供的一个语法糖, 那么,我们就来解一解这个语法糖,看看他的内部原理到底是如何实现的。
-
-还是这样一段代码。我们把他生成的字节码进行反编译,看看结果。
-
-String wechat = "Hollis";
-String introduce = "每日更新Java相关技术文章";
-String hollis = wechat + "," + introduce;
-
-
-反编译后的内容如下,反编译工具为jad。
-
-String wechat = "Hollis";
-String introduce = "\u6BCF\u65E5\u66F4\u65B0Java\u76F8\u5173\u6280\u672F\u6587\u7AE0";//每日更新Java相关技术文章
-String hollis = (new StringBuilder()).append(wechat).append(",").append(introduce).toString();
-
-
-通过查看反编译以后的代码,我们可以发现,原来字符串常量在拼接过程中,是将String转成了StringBuilder后,使用其append方法进行处理的。
-
-那么也就是说,Java中的`+`对字符串的拼接,其实现原理是使用`StringBuilder.append`。
+关于这个知识点,前面的章节介绍过,主要是通过StringBuilder的append方法实现的。
### concat是如何实现的
我们再来看一下concat方法的源代码,看一下这个方法又是如何实现的。
-public String concat(String str) {
- int otherLen = str.length();
- if (otherLen == 0) {
- return this;
+ public String concat(String str) {
+ int otherLen = str.length();
+ if (otherLen == 0) {
+ return this;
+ }
+ int len = value.length;
+ char buf[] = Arrays.copyOf(value, len + otherLen);
+ str.getChars(buf, len);
+ return new String(buf, true);
}
- int len = value.length;
- char buf[] = Arrays.copyOf(value, len + otherLen);
- str.getChars(buf, len);
- return new String(buf, true);
-}
-
+
这段代码首先创建了一个字符数组,长度是已有字符串和待拼接字符串的长度之和,再把两个字符串的值复制到新的字符数组中,并使用这个字符数组创建一个新的String对象并返回。
@@ -142,45 +115,45 @@ String hollis = (new StringBuilder()).append(wechat).append(",").append(introduc
和`String`类类似,`StringBuilder`类也封装了一个字符数组,定义如下:
-char[] value;
-
+ char[] value;
+
与`String`不同的是,它并不是`final`的,所以他是可以修改的。另外,与`String`不同,字符数组中不一定所有位置都已经被使用,它有一个实例变量,表示数组中已经使用的字符个数,定义如下:
-int count;
-
+ int count;
+
其append源码如下:
-public StringBuilder append(String str) {
- super.append(str);
- return this;
-}
-
+ public StringBuilder append(String str) {
+ super.append(str);
+ return this;
+ }
+
该类继承了`AbstractStringBuilder`类,看下其`append`方法:
-public AbstractStringBuilder append(String str) {
- if (str == null)
- return appendNull();
- int len = str.length();
- ensureCapacityInternal(count + len);
- str.getChars(0, len, value, count);
- count += len;
- return this;
-}
-
+ public AbstractStringBuilder append(String str) {
+ if (str == null)
+ return appendNull();
+ int len = str.length();
+ ensureCapacityInternal(count + len);
+ str.getChars(0, len, value, count);
+ count += len;
+ return this;
+ }
+
append会直接拷贝字符到内部的字符数组中,如果字符数组长度不够,会进行扩展。
`StringBuffer`和`StringBuilder`类似,最大的区别就是`StringBuffer`是线程安全的,看一下`StringBuffer`的`append`方法。
-public synchronized StringBuffer append(String str) {
- toStringCache = null;
- super.append(str);
- return this;
-}
-
+ public synchronized StringBuffer append(String str) {
+ toStringCache = null;
+ super.append(str);
+ return this;
+ }
+
该方法使用`synchronized`进行声明,说明是一个线程安全的方法。而`StringBuilder`则不是线程安全的。
@@ -188,56 +161,56 @@ append会直接拷贝字符到内部的字符数组中,如果字符数组长
通过查看`StringUtils.join`的源代码,我们可以发现,其实他也是通过`StringBuilder`来实现的。
-public static String join(final Object[] array, String separator, final int startIndex, final int endIndex) {
- if (array == null) {
- return null;
- }
- if (separator == null) {
- separator = EMPTY;
- }
-
- // endIndex - startIndex > 0: Len = NofStrings *(len(firstString) + len(separator))
- // (Assuming that all Strings are roughly equally long)
- final int noOfItems = endIndex - startIndex;
- if (noOfItems <= 0) {
- return EMPTY;
- }
-
- final StringBuilder buf = new StringBuilder(noOfItems * 16);
-
- for (int i = startIndex; i < endIndex; i++) {
- if (i > startIndex) {
- buf.append(separator);
+ public static String join(final Object[] array, String separator, final int startIndex, final int endIndex) {
+ if (array == null) {
+ return null;
+ }
+ if (separator == null) {
+ separator = EMPTY;
+ }
+
+ // endIndex - startIndex > 0: Len = NofStrings *(len(firstString) + len(separator))
+ // (Assuming that all Strings are roughly equally long)
+ final int noOfItems = endIndex - startIndex;
+ if (noOfItems <= 0) {
+ return EMPTY;
}
- if (array[i] != null) {
- buf.append(array[i]);
+
+ final StringBuilder buf = new StringBuilder(noOfItems * 16);
+
+ for (int i = startIndex; i < endIndex; i++) {
+ if (i > startIndex) {
+ buf.append(separator);
+ }
+ if (array[i] != null) {
+ buf.append(array[i]);
+ }
}
+ return buf.toString();
}
- return buf.toString();
-}
-
+
### 效率比较
既然有这么多种字符串拼接的方法,那么到底哪一种效率最高呢?我们来简单对比一下。
-long t1 = System.currentTimeMillis();
-//这里是初始字符串定义
-for (int i = 0; i < 50000; i++) {
- //这里是字符串拼接代码
-}
-long t2 = System.currentTimeMillis();
-System.out.println("cost:" + (t2 - t1));
-
+ long t1 = System.currentTimeMillis();
+ //这里是初始字符串定义
+ for (int i = 0; i < 50000; i++) {
+ //这里是字符串拼接代码
+ }
+ long t2 = System.currentTimeMillis();
+ System.out.println("cost:" + (t2 - t1));
+
我们使用形如以上形式的代码,分别测试下五种字符串拼接代码的运行时间。得到结果如下:
-+ cost:5119
-StringBuilder cost:3
-StringBuffer cost:4
-concat cost:3623
-StringUtils.join cost:25726
-
+ + cost:5119
+ StringBuilder cost:3
+ StringBuffer cost:4
+ concat cost:3623
+ StringUtils.join cost:25726
+
从结果可以看出,用时从短到长的对比是:
@@ -251,29 +224,29 @@ StringUtils.join也是使用了StringBuilder,并且其中还是有很多其他
我们再把以下代码反编译下:
-long t1 = System.currentTimeMillis();
-String str = "hollis";
-for (int i = 0; i < 50000; i++) {
- String s = String.valueOf(i);
- str += s;
-}
-long t2 = System.currentTimeMillis();
-System.out.println("+ cost:" + (t2 - t1));
-
+ long t1 = System.currentTimeMillis();
+ String str = "hollis";
+ for (int i = 0; i < 50000; i++) {
+ String s = String.valueOf(i);
+ str += s;
+ }
+ long t2 = System.currentTimeMillis();
+ System.out.println("+ cost:" + (t2 - t1));
+
反编译后代码如下:
-long t1 = System.currentTimeMillis();
-String str = "hollis";
-for(int i = 0; i < 50000; i++)
-{
- String s = String.valueOf(i);
- str = (new StringBuilder()).append(str).append(s).toString();
-}
-
-long t2 = System.currentTimeMillis();
-System.out.println((new StringBuilder()).append("+ cost:").append(t2 - t1).toString());
-
+ long t1 = System.currentTimeMillis();
+ String str = "hollis";
+ for(int i = 0; i < 50000; i++)
+ {
+ String s = String.valueOf(i);
+ str = (new StringBuilder()).append(str).append(s).toString();
+ }
+
+ long t2 = System.currentTimeMillis();
+ System.out.println((new StringBuilder()).append("+ cost:").append(t2 - t1).toString());
+
我们可以看到,反编译后的代码,在`for`循环中,每次都是`new`了一个`StringBuilder`,然后再把`String`转成`StringBuilder`,再进行`append`。
diff --git a/docs/basics/java-basic/stringjoiner-in-java8.md b/docs/basics/java-basic/stringjoiner-in-java8.md
new file mode 100644
index 00000000..36b82cbe
--- /dev/null
+++ b/docs/basics/java-basic/stringjoiner-in-java8.md
@@ -0,0 +1,158 @@
+在上一节中,我们介绍了几种Java中字符串拼接的方式,以及优缺点。其中还有一个重要的拼接方式我没有介绍,那就是Java 8中提供的StringJoiner ,本文就来介绍一下这个字符串拼接的新兵。
+
+如果你想知道一共有多少种方法可以进行字符串拼接,教你一个简单的办法,在Intellij IDEA中,定义一个Java Bean,然后尝试使用快捷键自动生成一个toString方法,IDEA会提示多种toString生成策略可供选择。
+
+![][2]
+
+目前我使用的IDEA的toString生成策略默认的是使用JDK 1.8提供的StringJoiner。
+
+### 介绍
+
+StringJoiner是java.util包中的一个类,用于构造一个由分隔符分隔的字符序列(可选),并且可以从提供的前缀开始并以提供的后缀结尾。虽然这也可以在StringBuilder类的帮助下在每个字符串之后附加分隔符,但StringJoiner提供了简单的方法来实现,而无需编写大量代码。
+
+StringJoiner类共有2个构造函数,5个公有方法。其中最常用的方法就是add方法和toString方法,类似于StringBuilder中的append方法和toString方法。
+
+### 用法
+
+StringJoiner的用法比较简单,下面的代码中,我们使用StringJoiner进行了字符串拼接。
+
+ public class StringJoinerTest {
+
+ public static void main(String[] args) {
+ StringJoiner sj = new StringJoiner("Hollis");
+
+ sj.add("hollischuang");
+ sj.add("Java干货");
+ System.out.println(sj.toString());
+
+ StringJoiner sj1 = new StringJoiner(":","[","]");
+
+ sj1.add("Hollis").add("hollischuang").add("Java干货");
+ System.out.println(sj1.toString());
+ }
+ }
+
+
+以上代码输出结果:
+
+ hollischuangHollisJava干货
+ [Hollis:hollischuang:Java干货]
+
+
+值得注意的是,当我们`StringJoiner(CharSequence delimiter)`初始化一个`StringJoiner`的时候,这个`delimiter`其实是分隔符,并不是可变字符串的初始值。
+
+`StringJoiner(CharSequence delimiter,CharSequence prefix,CharSequence suffix)`的第二个和第三个参数分别是拼接后的字符串的前缀和后缀。
+
+### 原理
+
+介绍了简单的用法之后,我们再来看看这个StringJoiner的原理,看看他到底是如何实现的。主要看一下add方法:
+
+ public StringJoiner add(CharSequence newElement) {
+ prepareBuilder().append(newElement);
+ return this;
+ }
+
+ private StringBuilder prepareBuilder() {
+ if (value != null) {
+ value.append(delimiter);
+ } else {
+ value = new StringBuilder().append(prefix);
+ }
+ return value;
+ }
+
+
+看到了一个熟悉的身影——StringBuilder ,没错,StringJoiner其实就是依赖StringBuilder实现的。
+
+当我们发现StringJoiner其实是通过StringBuilder实现之后,我们大概就可以猜到,**他的性能损耗应该和直接使用StringBuilder差不多**!
+
+### 为什么需要StringJoiner
+
+在了解了StringJoiner的用法和原理后,可能很多读者就会产生一个疑问,明明已经有一个StringBuilder了,为什么Java 8中还要定义一个StringJoiner呢?到底有什么好处呢?
+
+如果读者足够了解Java 8的话,或许可以猜出个大概,这肯定和Stream有关。
+
+作者也在[Java doc][3]中找到了答案:
+
+> A StringJoiner may be employed to create formatted output from a Stream using Collectors.joining(CharSequence)
+
+试想,在Java中,如果我们有这样一个List:
+
+ List list = ImmutableList.of("Hollis","hollischuang","Java干货");
+
+
+如果我们想要把他拼接成一个以下形式的字符串:
+
+ Hollis,hollischuang,Java干货
+
+
+可以通过以下方式:
+
+ StringBuilder builder = new StringBuilder();
+
+ if (!list.isEmpty()) {
+ builder.append(list.get(0));
+ for (int i = 1, n = list.size(); i < n; i++) {
+ builder.append(",").append(list.get(i));
+ }
+ }
+ builder.toString();
+
+
+还可以使用:
+
+ list.stream().reduce(new StringBuilder(), (sb, s) -> sb.append(s).append(','), StringBuilder::append).toString();
+
+
+但是输出结果稍有些不同,需要进行二次处理:
+
+ Hollis,hollischuang,Java干货,
+
+
+还可以使用"+"进行拼接:
+
+ list.stream().reduce((a,b)->a + "," + b).toString();
+
+
+以上几种方式,要么是代码复杂,要么是性能不高,或者无法直接得到想要的结果。
+
+为了满足类似这样的需求,Java 8中提供的StringJoiner就派上用场了。以上需求只需要一行代码:
+
+ list.stream().collect(Collectors.joining(":"))
+
+
+即可。上面用的表达式中,Collectors.joining的源代码如下:
+
+ public static Collector joining(CharSequence delimiter,
+ CharSequence prefix,
+ CharSequence suffix) {
+ return new CollectorImpl<>(
+ () -> new StringJoiner(delimiter, prefix, suffix),
+ StringJoiner::add, StringJoiner::merge,
+ StringJoiner::toString, CH_NOID);
+ }
+
+
+其实现原理就是借助了StringJoiner。
+
+当然,或许在`Collector`中直接使用`StringBuilder`似乎也可以实现类似的功能,只不过稍微麻烦一些。所以,Java 8中提供了`StringJoiner`来丰富`Stream`的用法。
+
+而且`StringJoiner`也可以方便的增加前缀和后缀,比如我们希望得到的字符串是`[Hollis,hollischuang,Java干货]`而不是`Hollis,hollischuang,Java`干货的话,StringJoiner的优势就更加明显了。
+
+### 总结
+
+本文介绍了Java 8中提供的可变字符串类——StringJoiner,可以用于字符串拼接。
+
+StringJoiner其实是通过StringBuilder实现的,所以他的性能和StringBuilder差不多,他也是非线程安全的。
+
+如果日常开发中中,需要进行字符串拼接,如何选择?
+
+1、如果只是简单的字符串拼接,考虑直接使用"+"即可。
+
+2、如果是在for循环中进行字符串拼接,考虑使用`StringBuilder`和`StringBuffer`。
+
+3、如果是通过一个`List`进行字符串拼接,则考虑使用`StringJoiner`。
+
+ [1]: http://www.hollischuang.com/archives/3186
+ [2]: http://www.hollischuang.com/wp-content/uploads/2019/02/15508994967943.jpg
+ [3]: https://docs.oracle.com/javase/8/docs/api/java/util/StringJoiner.html
\ No newline at end of file
diff --git a/docs/menu.md b/docs/menu.md
index a5569048..f034f328 100644
--- a/docs/menu.md
+++ b/docs/menu.md
@@ -130,6 +130,8 @@ Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer)
* [String对“+”的重载](/basics/java-basic/string-append.md)
* [字符串拼接的几种方式和区别](/basics/java-basic/string-concat.md)
+
+ * [Java 8中的StringJoiner](/basics/java-basic/stringjoiner-in-java8.md)
* [String.valueOf和Integer.toString的区别](/basics/java-basic/value-of-vs-to-string.md)
From fd8b6e68f9503e3be6e04f9b350bae848d20f9cb Mon Sep 17 00:00:00 2001
From: SunAo-Oct <51832839+SunAo-Oct@users.noreply.github.com>
Date: Tue, 25 May 2021 11:19:04 +0800
Subject: [PATCH 66/84] Update and rename type-erasue.md to type-erasure.md
---
docs/basics/java-basic/{type-erasue.md => type-erasure.md} | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
rename docs/basics/java-basic/{type-erasue.md => type-erasure.md} (99%)
diff --git a/docs/basics/java-basic/type-erasue.md b/docs/basics/java-basic/type-erasure.md
similarity index 99%
rename from docs/basics/java-basic/type-erasue.md
rename to docs/basics/java-basic/type-erasure.md
index 790685db..8e4e1dfd 100644
--- a/docs/basics/java-basic/type-erasue.md
+++ b/docs/basics/java-basic/type-erasure.md
@@ -12,13 +12,13 @@
`C++`和`C#`是使用`Code specialization`的处理机制,前面提到,他有一个缺点,那就是**会导致代码膨胀**。另外一个弊端是在引用类型系统中,浪费空间,因为引用类型集合中元素本质上都是一个指针。没必要为每个类型都产生一份执行代码。而这也是Java编译器中采用`Code sharing`方式处理泛型的主要原因。
-`Java`编译器通过`Code sharing`方式为每个泛型类型创建唯一的字节码表示,并且将该泛型类型的实例都映射到这个唯一的字节码表示上。将多种泛型类形实例映射到唯一的字节码表示是通过**类型擦除**(`type erasue`)实现的。
+`Java`编译器通过`Code sharing`方式为每个泛型类型创建唯一的字节码表示,并且将该泛型类型的实例都映射到这个唯一的字节码表示上。将多种泛型类形实例映射到唯一的字节码表示是通过**类型擦除**(`type erasure`)实现的。
* * *
### 二、什么是类型擦除
-前面我们多次提到这个词:**类型擦除**(`type erasue`),那么到底什么是类型擦除呢?
+前面我们多次提到这个词:**类型擦除**(`type erasure`),那么到底什么是类型擦除呢?
> 类型擦除指的是通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上。类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且再必要的时候添加类型检查和类型转换的方法。 类型擦除可以简单的理解为将泛型java代码转换为普通java代码,只不过编译器更直接点,将泛型java代码直接转换成普通java字节码。 类型擦除的主要过程如下: 1.将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。(这部分内容可以看:[Java泛型中extends和super的理解][2]) 2.移除所有的类型参数。
From 32087bb6b9a4870eb10fcc84ee060b5a9d2cea77 Mon Sep 17 00:00:00 2001
From: Hollis
Date: Thu, 10 Jun 2021 11:32:55 +0800
Subject: [PATCH 67/84] Update replace-in-string.md
---
docs/basics/java-basic/replace-in-string.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/basics/java-basic/replace-in-string.md b/docs/basics/java-basic/replace-in-string.md
index 3b66cfaf..fa452eff 100644
--- a/docs/basics/java-basic/replace-in-string.md
+++ b/docs/basics/java-basic/replace-in-string.md
@@ -24,5 +24,5 @@ replaceAll和replaceFirst的区别主要是替换的内容不同,replaceAll是
//使用replaceFirst将第一个数字替换成H
System.out.println(string.replaceFirst("\\d","H"));//abcH23adb23456aa
- //使用replaceFirst将所有数字替换成H
- System.out.println(string.replaceAll("\\d","H"));//abcHHHadbHHHHHaa
\ No newline at end of file
+ //使用replaceAll将所有数字替换成H
+ System.out.println(string.replaceAll("\\d","H"));//abcHHHadbHHHHHaa
From 215fa0e5f307ae93001c56cd57c7af1656041076 Mon Sep 17 00:00:00 2001
From: "hollis.zhl"
Date: Sun, 20 Jun 2021 18:15:16 +0800
Subject: [PATCH 68/84] =?UTF-8?q?=E7=9F=A5=E8=AF=86=E7=82=B9=E6=9B=B4?=
=?UTF-8?q?=E6=96=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 1 +
docs/basics/java-basic/constructor.md | 5 -
docs/basics/java-basic/final-string.md | 114 ++++++++++++++----
.../java-basic/inheritance-composition.md | 67 ----------
docs/basics/java-basic/replace-in-string.md | 2 +-
docs/basics/java-basic/scope.md | 7 --
docs/basics/java-basic/string-append.md | 2 +
docs/basics/java-basic/substring.md | 4 +-
docs/basics/java-basic/switch-string.md | 2 -
docs/basics/java-basic/variable.md | 29 -----
.../basics/object-oriented/characteristics.md | 15 ++-
docs/basics/object-oriented/constructor.md | 4 +-
.../object-oriented/extends-implement.md | 19 +--
.../inheritance-composition.md | 2 +-
docs/basics/object-oriented/java-pass-by.md | 101 +++++++++++-----
.../object-oriented/multiple-inheritance.md | 2 +-
.../object-oriented-vs-procedure-oriented.md | 38 +++---
.../overloading-vs-overriding.md | 106 ++++++----------
docs/basics/object-oriented/polymorphism.md | 52 ++++++--
docs/basics/object-oriented/principle.md | 46 +++++--
docs/basics/object-oriented/scope.md | 6 +-
docs/basics/object-oriented/variable.md | 8 ++
.../object-oriented/why-pass-by-reference.md | 93 +-------------
23 files changed, 349 insertions(+), 376 deletions(-)
delete mode 100644 docs/basics/java-basic/constructor.md
delete mode 100644 docs/basics/java-basic/inheritance-composition.md
delete mode 100644 docs/basics/java-basic/scope.md
delete mode 100644 docs/basics/java-basic/variable.md
diff --git a/.gitignore b/.gitignore
index 5927d83f..3587059e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -57,3 +57,4 @@ fabric.properties
# Editor-based Rest Client
.idea/httpRequests
+.DS_Store
\ No newline at end of file
diff --git a/docs/basics/java-basic/constructor.md b/docs/basics/java-basic/constructor.md
deleted file mode 100644
index a0b3c011..00000000
--- a/docs/basics/java-basic/constructor.md
+++ /dev/null
@@ -1,5 +0,0 @@
-构造函数,是一种特殊的方法。 主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。 特别的一个类可以有多个构造函数,可根据其参数个数的不同或参数类型的不同来区分它们即构造函数的重载。
-
-构造函数跟一般的实例方法十分相似;但是与其它方法不同,构造器没有返回类型,不会被继承,且可以有范围修饰符。构造器的函数名称必须和它所属的类的名称相同。 它承担着初始化对象数据成员的任务。
-
-如果在编写一个可实例化的类时没有专门编写构造函数,多数编程语言会自动生成缺省构造器(默认构造函数)。默认构造函数一般会把成员变量的值初始化为默认值,如int -> 0,Integer -> null。
diff --git a/docs/basics/java-basic/final-string.md b/docs/basics/java-basic/final-string.md
index 3f7aade7..920b8e0c 100644
--- a/docs/basics/java-basic/final-string.md
+++ b/docs/basics/java-basic/final-string.md
@@ -1,39 +1,109 @@
+String在Java中特别常用,而且我们经常要在代码中对字符串进行赋值和改变他的值,但是,为什么我们说字符串是不可变的呢?
-* * *
+首先,我们需要知道什么是不可变对象?
-## 定义一个字符串
+不可变对象是在完全创建后其内部状态保持不变的对象。这意味着,一旦对象被赋值给变量,我们既不能更新引用,也不能通过任何方式改变内部状态。
- String s = "abcd";
-
+可是有人会有疑惑,String为什么不可变,我的代码中经常改变String的值啊,如下:
-![String-Immutability-1][1]
+```
+String s = "abcd";
+s = s.concat("ef");
-`s`中保存了string对象的引用。下面的箭头可以理解为“存储他的引用”。
+```
-## 使用变量来赋值变量
- String s2 = s;
-
+这样,操作,不就将原本的"abcd"的字符串改变成"abcdef"了么?
-![String-Immutability-2][2]
+但是,虽然字符串内容看上去从"abcd"变成了"abcdef",但是实际上,我们得到的已经是一个新的字符串了。
-s2保存了相同的引用值,因为他们代表同一个对象。
+![][1]
-## 字符串连接
+如上图,在堆中重新创建了一个"abcdef"字符串,和"abcd"并不是同一个对象。
- s = s.concat("ef");
-
+所以,一旦一个string对象在内存(堆)中被创建出来,他就无法被修改。而且,String类的所有方法都没有改变字符串本身的值,都是返回了一个新的对象。
-![string-immutability][3]
+如果我们想要一个可秀改的字符串,可以选择StringBuffer 或者 StringBuilder这两个代替String。
-`s`中保存的是一个重新创建出来的string对象的引用。
+### 为什么String要设计成不可变
-## 总结
+在知道了"String是不可变"的之后,大家是不是一定都很疑惑:为什么要把String设计成不可变的呢?有什么好处呢?
-一旦一个string对象在内存(堆)中被创建出来,他就无法被修改。特别要注意的是,String类的所有方法都没有改变字符串本身的值,都是返回了一个新的对象。
+这个问题,困扰过很多人,甚至有人直接问过Java的创始人James Gosling。
-如果你需要一个可修改的字符串,应该使用StringBuffer 或者 StringBuilder。否则会有大量时间浪费在垃圾回收上,因为每次试图修改都有新的string对象被创建出来。
+在一次采访中James Gosling被问到什么时候应该使用不可变变量,他给出的回答是:
- [1]: http://www.programcreek.com/wp-content/uploads/2009/02/String-Immutability-1.jpeg
- [2]: http://www.programcreek.com/wp-content/uploads/2009/02/String-Immutability-2.jpeg
- [3]: http://www.programcreek.com/wp-content/uploads/2009/02/string-immutability-650x279.jpeg
\ No newline at end of file
+> I would use an immutable whenever I can.
+
+那么,他给出这个答案背后的原因是什么呢?是基于哪些思考的呢?
+
+其实,主要是从缓存、安全性、线程安全和性能等角度触发的。
+
+Q:缓存、安全性、线程安全和性能?这有都是啥
+A:你别急,听我一个一个给你讲就好了。
+
+#### 缓存
+
+字符串是使用最广泛的数据结构。大量的字符串的创建是非常耗费资源的,所以,Java提供了对字符串的缓存功能,可以大大的节省堆空间。
+
+JVM中专门开辟了一部分空间来存储Java字符串,那就是字符串池。
+
+通过字符串池,两个内容相同的字符串变量,可以从池中指向同一个字符串对象,从而节省了关键的内存资源。
+
+```
+String s = "abcd";
+String s2 = s;
+```
+
+
+对于这个例子,s和s2都表示"abcd",所以他们会指向字符串池中的同一个字符串对象:
+
+![][2]
+
+但是,之所以可以这么做,主要是因为字符串的不变性。试想一下,如果字符串是可变的,我们一旦修改了s的内容,那必然导致s2的内容也被动的改变了,这显然不是我们想看到的。
+
+#### 安全性
+
+字符串在Java应用程序中广泛用于存储敏感信息,如用户名、密码、连接url、网络连接等。JVM类加载器在加载类的时也广泛地使用它。
+
+因此,保护String类对于提升整个应用程序的安全性至关重要。
+
+当我们在程序中传递一个字符串的时候,如果这个字符串的内容是不可变的,那么我们就可以相信这个字符串中的内容。
+
+但是,如果是可变的,那么这个字符串内容就可能随时都被修改。那么这个字符串内容就完全可信了。这样整个系统就没有安全性可言了。
+
+#### 线程安全
+
+不可变会自动使字符串成为线程安全的,因为当从多个线程访问它们时,它们不会被更改。
+
+因此,一般来说,不可变对象可以在同时运行的多个线程之间共享。它们也是线程安全的,因为如果线程更改了值,那么将在字符串池中创建一个新的字符串,而不是修改相同的值。因此,字符串对于多线程来说是安全的。
+
+#### hashcode缓存
+
+由于字符串对象被广泛地用作数据结构,它们也被广泛地用于哈希实现,如HashMap、HashTable、HashSet等。在对这些散列实现进行操作时,经常调用hashCode()方法。
+
+不可变性保证了字符串的值不会改变。因此,hashCode()方法在String类中被重写,以方便缓存,这样在第一次hashCode()调用期间计算和缓存散列,并从那时起返回相同的值。
+
+在String类中,有以下代码:
+
+```
+private int hash;//this is used to cache hash code.
+```
+
+
+#### 性能
+
+前面提到了的字符串池、hashcode缓存等,都是提升性能的提现。
+
+因为字符串不可变,所以可以用字符串池缓存,可以大大节省堆内存。而且还可以提前对hashcode进行缓存,更加高效
+
+由于字符串是应用最广泛的数据结构,提高字符串的性能对提高整个应用程序的总体性能有相当大的影响。
+
+### 总结
+
+通过本文,我们可以得出这样的结论:字符串是不可变的,因此它们的引用可以被视为普通变量,可以在方法之间和线程之间传递它们,而不必担心它所指向的实际字符串对象是否会改变。
+
+我们还了解了促使Java语言设计人员将该类设置为不可变类的其他原因。主要考虑的是缓存、安全性、线程安全和性能等方面
+
+ [1]: https://www.hollischuang.com/wp-content/uploads/2021/03/16163108328434.jpg
+ [2]: https://www.hollischuang.com/wp-content/uploads/2021/03/16163114985563.jpg
\ No newline at end of file
diff --git a/docs/basics/java-basic/inheritance-composition.md b/docs/basics/java-basic/inheritance-composition.md
deleted file mode 100644
index 5eff0d32..00000000
--- a/docs/basics/java-basic/inheritance-composition.md
+++ /dev/null
@@ -1,67 +0,0 @@
-Java是一个面向对象的语言。每一个学习过Java的人都知道,封装、继承、多态是面向对象的三个特征。每个人在刚刚学习继承的时候都会或多或少的有这样一个印象:继承可以帮助我实现类的复用。所以,很多开发人员在需要复用一些代码的时候会很自然的使用类的继承的方式,因为书上就是这么写的(老师就是这么教的)。但是,其实这样做是不对的。长期大量的使用继承会给代码带来很高的维护成本。
-
-本文将介绍组合和继承的概念及区别,并从多方面分析在写代码时如何进行选择。
-
-## 面向对象的复用技术
-
-前面提到复用,这里就简单介绍一下面向对象的复用技术。
-
-复用性是面向对象技术带来的很棒的潜在好处之一。如果运用的好的话可以帮助我们节省很多开发时间,提升开发效率。但是,如果被滥用那么就可能产生很多难以维护的代码。
-
-作为一门面向对象开发的语言,代码复用是Java引人注意的功能之一。Java代码的复用有继承,组合以及代理三种具体的表现形式。本文将重点介绍继承复用和组合复用。
-
-## 继承
-
-继承(Inheritance)是一种联结类与类的层次模型。指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系;继承是一种[`is-a`][1]关系。(图片来自网络,侵删。)
-
-![Inheritance][2]
-
-## 组合
-
-组合(Composition)体现的是整体与部分、拥有的关系,即[`has-a`][3]的关系。
-
-![Composition][4]
-
-## 组合与继承的区别和联系
-
-> 在`继承`结构中,父类的内部细节对于子类是可见的。所以我们通常也可以说通过继承的代码复用是一种`白盒式代码复用`。(如果基类的实现发生改变,那么派生类的实现也将随之改变。这样就导致了子类行为的不可预知性;)
->
-> `组合`是通过对现有的对象进行拼装(组合)产生新的、更复杂的功能。因为在对象之间,各自的内部细节是不可见的,所以我们也说这种方式的代码复用是`黑盒式代码复用`。(因为组合中一般都定义一个类型,所以在编译期根本不知道具体会调用哪个实现类的方法)
->
-> `继承`,在写代码的时候就要指名具体继承哪个类,所以,在`编译期`就确定了关系。(从基类继承来的实现是无法在运行期动态改变的,因此降低了应用的灵活性。)
->
-> `组合`,在写代码的时候可以采用面向接口编程。所以,类的组合关系一般在`运行期`确定。
-
-## 优缺点对比
-
-| 组 合 关 系 | 继 承 关 系 |
-| -------------------------------- | -------------------------------------- |
-| 优点:不破坏封装,整体类与局部类之间松耦合,彼此相对独立 | 缺点:破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性 |
-| 优点:具有较好的可扩展性 | 缺点:支持扩展,但是往往以增加系统结构的复杂度为代价 |
-| 优点:支持动态组合。在运行时,整体对象可以选择不同类型的局部对象 | 缺点:不支持动态继承。在运行时,子类无法选择不同的父类 |
-| 优点:整体类可以对局部类进行包装,封装局部类的接口,提供新的接口 | 缺点:子类不能改变父类的接口 |
-| 缺点:整体类不能自动获得和局部类同样的接口 | 优点:子类能自动继承父类的接口 |
-| 缺点:创建整体类的对象时,需要创建所有局部类的对象 | 优点:创建子类的对象时,无须创建父类的对象 |
-
-## 如何选择
-
-相信很多人都知道面向对象中有一个比较重要的原则『多用组合、少用继承』或者说『组合优于继承』。从前面的介绍已经优缺点对比中也可以看出,组合确实比继承更加灵活,也更有助于代码维护。
-
-所以,
-
-> **`建议在同样可行的情况下,优先使用组合而不是继承。`**
->
-> **`因为组合更安全,更简单,更灵活,更高效。`**
-
-注意,并不是说继承就一点用都没有了,前面说的是【在同样可行的情况下】。有一些场景还是需要使用继承的,或者是更适合使用继承。
-
-> 继承要慎用,其使用场合仅限于你确信使用该技术有效的情况。一个判断方法是,问一问自己是否需要从新类向基类进行向上转型。如果是必须的,则继承是必要的。反之则应该好好考虑是否需要继承。《[Java编程思想][5]》
->
-> 只有当子类真正是超类的子类型时,才适合用继承。换句话说,对于两个类A和B,只有当两者之间确实存在[`is-a`][1]关系的时候,类B才应该继承类A。《[Effective Java][6]》
-
- [1]: https://zh.wikipedia.org/wiki/Is-a
- [2]: http://www.hollischuang.com/wp-content/uploads/2016/03/Generalization.jpg
- [3]: https://en.wikipedia.org/wiki/Has-a
- [4]: http://www.hollischuang.com/wp-content/uploads/2016/03/Composition.jpg
- [5]: http://s.click.taobao.com/t?e=m%3D2%26s%3DHzJzud6zOdocQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67vo5P8BMUBgoEC56fBbgyn5pS4hLH%2FP02ckKYNRBWOBBey11vvWwHXSniyi5vWXIZhtlrJbLMDAQihpQCXu2JnPFYKQlNeOGCsYMXU3NNCg%2F&pvid=10_125.119.86.125_222_1458652212179
- [6]: http://s.click.taobao.com/t?e=m%3D2%26s%3DwIPn8%2BNPqLwcQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67vo5P8BMUBgoUOZr0mLjusdpS4hLH%2FP02ckKYNRBWOBBey11vvWwHXSniyi5vWXIZvgXwmdyquYbNLnO%2BjzYQLqKnzbV%2FMLqnMYMXU3NNCg%2F&pvid=10_125.119.86.125_345_1458652241780
diff --git a/docs/basics/java-basic/replace-in-string.md b/docs/basics/java-basic/replace-in-string.md
index 3b66cfaf..9c9b31cc 100644
--- a/docs/basics/java-basic/replace-in-string.md
+++ b/docs/basics/java-basic/replace-in-string.md
@@ -24,5 +24,5 @@ replaceAll和replaceFirst的区别主要是替换的内容不同,replaceAll是
//使用replaceFirst将第一个数字替换成H
System.out.println(string.replaceFirst("\\d","H"));//abcH23adb23456aa
- //使用replaceFirst将所有数字替换成H
+ //使用replaceAll将所有数字替换成H
System.out.println(string.replaceAll("\\d","H"));//abcHHHadbHHHHHaa
\ No newline at end of file
diff --git a/docs/basics/java-basic/scope.md b/docs/basics/java-basic/scope.md
deleted file mode 100644
index fb44d530..00000000
--- a/docs/basics/java-basic/scope.md
+++ /dev/null
@@ -1,7 +0,0 @@
-### 对于成员变量和方法的作用域,public,protected,private以及不写之间的区别。
-
-
-- public :表明该成员变量或者方法是对所有类或者对象都是可见的,所有类或者对象都可以直接访问
-- private:表明该成员变量或者方法是私有的,只有当前类对其具有访问权限,除此之外其他类或者对象都没有访问权限.子类也没有访问权限.
-- protected:表明成员变量或者方法对类自身,与同在一个包中的其他类可见,其他包下的类不可访问,除非是他的子类
-- default:表明该成员变量或者方法只有自己和其位于同一个包的内可见,其他包内的类不能访问,即便是它的子类
\ No newline at end of file
diff --git a/docs/basics/java-basic/string-append.md b/docs/basics/java-basic/string-append.md
index fea68cef..eae5eb66 100644
--- a/docs/basics/java-basic/string-append.md
+++ b/docs/basics/java-basic/string-append.md
@@ -1,3 +1,5 @@
+Java中,想要拼接字符串,最简单的方式就是通过"+"连接两个字符串。
+
有人把Java中使用+拼接字符串的功能理解为运算符重载。其实并不是,Java是不支持运算符重载的。这其实只是Java提供的一个语法糖。
>运算符重载:在计算机程序设计中,运算符重载(英语:operator overloading)是多态的一种。运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
diff --git a/docs/basics/java-basic/substring.md b/docs/basics/java-basic/substring.md
index a017b164..101f97d2 100644
--- a/docs/basics/java-basic/substring.md
+++ b/docs/basics/java-basic/substring.md
@@ -1,4 +1,6 @@
-String是Java中一个比较基础的类,每一个开发人员都会经常接触到。而且,String也是面试中经常会考的知识点。String有很多方法,有些方法比较常用,有些方法不太常用。今天要介绍的substring就是一个比较常用的方法,而且围绕substring也有很多面试题。
+String是Java中一个比较基础的类,每一个开发人员都会经常接触到。而且,String也是面试中经常会考的知识点。
+
+String有很多方法,有些方法比较常用,有些方法不太常用。今天要介绍的substring就是一个比较常用的方法,而且围绕substring也有很多面试题。
`substring(int beginIndex, int endIndex)`方法在不同版本的JDK中的实现是不同的。了解他们的区别可以帮助你更好的使用他。为简单起见,后文中用`substring()`代表`substring(int beginIndex, int endIndex)`方法。
diff --git a/docs/basics/java-basic/switch-string.md b/docs/basics/java-basic/switch-string.md
index deac11de..067e7ad4 100644
--- a/docs/basics/java-basic/switch-string.md
+++ b/docs/basics/java-basic/switch-string.md
@@ -1,7 +1,5 @@
Java 7中,switch的参数可以是String类型了,这对我们来说是一个很方便的改进。到目前为止switch支持这样几种数据类型:`byte` `short` `int` `char` `String` 。但是,作为一个程序员我们不仅要知道他有多么好用,还要知道它是如何实现的,switch对整型的支持是怎么实现的呢?对字符型是怎么实现的呢?String类型呢?有一点Java开发经验的人这个时候都会猜测switch对String的支持是使用equals()方法和hashcode()方法。那么到底是不是这两个方法呢?接下来我们就看一下,switch到底是如何实现的。
-
-
### 一、switch对整型支持的实现
下面是一段很简单的Java代码,定义一个int型变量a,然后使用switch语句进行判断。执行这段代码输出内容为5,那么我们将下面这段代码反编译,看看他到底是怎么实现的。
diff --git a/docs/basics/java-basic/variable.md b/docs/basics/java-basic/variable.md
deleted file mode 100644
index 8e715c7c..00000000
--- a/docs/basics/java-basic/variable.md
+++ /dev/null
@@ -1,29 +0,0 @@
-### 类变量、成员变量和局部变量
-
-Java中共有三种变量,分别是类变量、成员变量和局部变量。他们分别存放在JVM的方法区、堆内存和栈内存中。
-```java
- /**
- * @author Hollis
- */
- public class Variables {
-
- /**
- * 类变量
- */
- private static int a;
-
- /**
- * 成员变量
- */
- private int b;
-
- /**
- * 局部变量
- * @param c
- */
- public void test(int c){
- int d;
- }
- }
-```
-上面定义的三个变量中,变量a就是类变量,变量b就是成员变量,而变量c和d是局部变量。
diff --git a/docs/basics/object-oriented/characteristics.md b/docs/basics/object-oriented/characteristics.md
index 7b1e4a53..0ac4f280 100644
--- a/docs/basics/object-oriented/characteristics.md
+++ b/docs/basics/object-oriented/characteristics.md
@@ -1,4 +1,4 @@
-
+我们说面向对象的开发范式,其实是对现实世界的理解和抽象的方法,那么,具体如何将现实世界抽象成代码呢?这就需要运用到面向对象的三大特性,分别是封装性、继承性和多态性。
### 封装(Encapsulation)
@@ -8,7 +8,7 @@
#### 封装举例
-如我们想要定义一个矩形,先定义一个Rectangle类,并其中通过封装的手段放入一些必备数据
+如我们想要定义一个矩形,先定义一个Rectangle类,并其中通过封装的手段放入一些必备数据。
/**
* 矩形
@@ -42,6 +42,8 @@
return this.length * this.width;
}
}
+
+我们通过封装的方式,给"矩形"定义了"长度"和"宽度",这就完成了对现实世界中的"矩形"的抽象的第一步。
### 继承(Inheritance)
@@ -69,6 +71,7 @@
}
}
+现实世界中,"正方形"是"矩形"的特例,或者说正方形是通过矩形派生出来的,这种派生关系,在面向对象中可以用继承来表达。
### 多态(Polymorphism)
@@ -78,4 +81,10 @@
最常见的多态就是将子类传入父类参数中,运行时调用父类方法时通过传入的子类决定具体的内部结构或行为。
-关于多态的例子,我们后面的章节中还会深入展开。
\ No newline at end of file
+关于多态的例子,我们第二章中深入开展介绍。
+
+在介绍了面向对象的封装、继承、多态的三个基本特征之后,我们基本掌握了对现实世界抽象的基本方法。
+
+莎士比亚说:"一千个读者眼里有一千个哈姆雷特",说到对现实世界的抽象,虽然方法相同,但是运用同样的方法,最终得到的结果可能千差万别,那么如何评价这个抽象的结果的好坏呢?
+
+这就要提到面喜爱那个对象的五大基本原则了,有了五大原则,我们参考他们来评价一个抽象的好坏。
\ No newline at end of file
diff --git a/docs/basics/object-oriented/constructor.md b/docs/basics/object-oriented/constructor.md
index 42f9dd74..894a6c26 100644
--- a/docs/basics/object-oriented/constructor.md
+++ b/docs/basics/object-oriented/constructor.md
@@ -1,4 +1,4 @@
-构造函数,是一种特殊的方法。 主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。
+构造函数,是一种特殊的方法。主要用来在创建对象时初始化对象,即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。
/**
* 矩形
@@ -19,8 +19,6 @@
}
}
-
-
特别的一个类可以有多个构造函数,可根据其参数个数的不同或参数类型的不同来区分它们即构造函数的重载。
diff --git a/docs/basics/object-oriented/extends-implement.md b/docs/basics/object-oriented/extends-implement.md
index 536c5901..ca79513b 100644
--- a/docs/basics/object-oriented/extends-implement.md
+++ b/docs/basics/object-oriented/extends-implement.md
@@ -1,20 +1,19 @@
-前面的章节我们提到过面向对象有三个特征:封装、继承、多态。
+前面的章节我们提到过面向对象有三个特征:封装、继承、多态。前面我们分别介绍过了这三个特性。
-继承可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。这种派生方式提现了*传递性*,在Java中,除了继承,还有一种提现传递性的方式叫做实现。
+我们知道,继承可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。这种派生方式体现了*传递性*。
+
+在Java中,除了继承,还有一种体现传递性的方式叫做实现。那么,这两者方式有什么区别呢?
继承和实现两者的明确定义和区别如下:
->继承(Inheritance):如果多个类的某个部分的功能相同,那么可以抽象出一个类出来,把他们的相同部分都放到父类里,让他们都继承这个类。
->
->实现(Implement):如果多个类处理的目标是一样的,但是处理的方法方式不同,那么就定义一个接口,也就是一个标准,让他们的实现这个接口,各自实现自己具体的处理方法来处理那个目标
+继承(Inheritance):如果多个类的某个部分的功能相同,那么可以抽象出一个类出来,把他们的相同部分都放到父类里,让他们都继承这个类。
+
+实现(Implement):如果多个类处理的目标是一样的,但是处理的方法方式不同,那么就定义一个接口,也就是一个标准,让他们的实现这个接口,各自实现自己具体的处理方法来处理那个目标
继承指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力。所以,继承的根本原因是因为要*复用*,而实现的根本原因是需要定义一个*标准*。
在Java中,继承使用`extends`关键字实现,而实现通过`implements`关键字。
-
-特别需要注意的是,Java中支持一个类同时实现多个接口,但是不支持同时继承多个类。但是这个问题在Java 8之后也不绝对了。
-
>简单点说,就是同样是一台汽车,既可以是电动车,也可以是汽油车,也可以是油电混合的,只要实现不同的标准就行了,但是一台车只能属于一个品牌,一个厂商。
```
@@ -26,4 +25,6 @@
以上,我们定义了一辆汽车,他实现了电动车和汽油车两个标准,但是他属于奔驰这个品牌。像上面这样定义,我们可以最大程度的遵守标准,并且复用奔驰车所有已有的一些功能组件。
-另外,在接口中只能定义全局常量(static final)和无实现的方法(Java 8以后可以有defult方法);而在继承中可以定义属性方法,变量,常量等。
+另外,在接口中只能定义全局常量(static final)和无实现的方法(Java 8以后可以有default方法);而在继承中可以定义属性方法,变量,常量等。
+
+*特别需要注意的是,Java中支持一个类同时实现多个接口,但是不支持同时继承多个类。* 但是这个问题在Java 8之后也不绝对了。关于多继承的问题,我们下一个章节中介绍。
\ No newline at end of file
diff --git a/docs/basics/object-oriented/inheritance-composition.md b/docs/basics/object-oriented/inheritance-composition.md
index e2869f3a..14b70340 100644
--- a/docs/basics/object-oriented/inheritance-composition.md
+++ b/docs/basics/object-oriented/inheritance-composition.md
@@ -26,7 +26,7 @@
组合(Composition)体现的是整体与部分、拥有的关系,即[`has-a`][3]的关系。
-> is-a:表示"有一个"的关系,如狗有一个尾巴
+> has-a:表示"有一个"的关系,如狗有一个尾巴
![Composition][4]
diff --git a/docs/basics/object-oriented/java-pass-by.md b/docs/basics/object-oriented/java-pass-by.md
index 895f2a2f..c05138bc 100644
--- a/docs/basics/object-oriented/java-pass-by.md
+++ b/docs/basics/object-oriented/java-pass-by.md
@@ -1,57 +1,98 @@
+关于Java中方法间的参数传递到底是怎样的、为什么很多人说Java只有值传递等问题,一直困惑着很多人,甚至我在面试的时候问过很多有丰富经验的开发者,他们也很难解释的很清楚。
+
+我很久也写过一篇文章,我当时认为我把这件事说清楚了,但是,最近在整理这部分知识点的时候,我发现我当时理解的还不够透彻,于是我想着通过Google看看其他人怎么理解的,但是遗憾的是没有找到很好的资料可以说的很清楚。
+
+于是,我决定尝试着把这个话题总结一下,重新理解一下这个问题。
+
+### 辟谣时间
+
+关于这个问题,在StackOverflow上也引发过广泛的讨论,看来很多程序员对于这个问题的理解都不尽相同,甚至很多人理解的是错误的。还有的人可能知道Java中的参数传递是值传递,但是说不出来为什么。
+
+在开始深入讲解之前,有必要纠正一下大家以前的那些错误看法了。如果你有以下想法,那么你有必要好好阅读本文。
+
+> 错误理解一:值传递和引用传递,区分的条件是传递的内容,如果是个值,就是值传递。如果是个引用,就是引用传递。
+>
+> 错误理解二:Java是引用传递。
+>
+> 错误理解三:传递的参数如果是普通类型,那就是值传递,如果是对象,那就是引用传递。
### 实参与形参
我们都知道,在Java中定义方法的时候是可以定义参数的。比如Java中的main方法,`public static void main(String[] args)`,这里面的args就是参数。参数在程序语言中分为形式参数和实际参数。
> 形式参数:是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。
->
+>
> 实际参数:在调用有参函数时,主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。
简单举个例子:
-```
-public static void main(String[] args) {
- ParamTest pt = new ParamTest();
- pt.sout("Hollis");//实际参数为 Hollis
-}
-
-public void sout(String name) { //形式参数为 name
- System.out.println(name);
-}
-```
+ public static void main(String[] args) {
+ ParamTest pt = new ParamTest();
+ pt.sout("Hollis");//实际参数为 Hollis
+ }
+
+ public void sout(String name) { //形式参数为 name
+ System.out.println(name);
+ }
+
实际参数是调用有参方法的时候真正传递的内容,而形式参数是用于接收实参内容的参数。
-### 值传递与引用传递
+### 求值策略
+
+我们说当进行方法调用的时候,需要把实际参数传递给形式参数,那么传递的过程中到底传递的是什么东西呢?
+
+这其实是程序设计中**求值策略(Evaluation strategies)**的概念。
+
+在计算机科学中,求值策略是确定编程语言中表达式的求值的一组(通常确定性的)规则。求值策略定义何时和以何种顺序求值给函数的实际参数、什么时候把它们代换入函数、和代换以何种形式发生。
+
+求值策略分为两大基本类,基于如何处理给函数的实际参数,分位严格的和非严格的。
+
+#### 严格求值
+
+在“严格求值”中,函数调用过程中,给函数的实际参数总是在应用这个函数之前求值。多数现存编程语言对函数都使用严格求值。所以,我们本文只关注严格求值。
+
+在严格求值中有几个关键的求值策略是我们比较关心的,那就是**传值调用**(Call by value)、**传引用调用**(Call by reference)以及**传共享对象调用**(Call by sharing)。
+
+* 传值调用(值传递)
+ * 在传值调用中,实际参数先被求值,然后其值通过复制,被传递给被调函数的形式参数。因为形式参数拿到的只是一个"局部拷贝",所以如果在被调函数中改变了形式参数的值,并不会改变实际参数的值。
+* 传引用调用(应用传递)
+ * 在传引用调用中,传递给函数的是它的实际参数的隐式引用而不是实参的拷贝。因为传递的是引用,所以,如果在被调函数中改变了形式参数的值,改变对于调用者来说是可见的。
+* 传共享对象调用(共享对象传递)
+ * 传共享对象调用中,先获取到实际参数的地址,然后将其复制,并把该地址的拷贝传递给被调函数的形式参数。因为参数的地址都指向同一个对象,所以我们称也之为"传共享对象",所以,如果在被调函数中改变了形式参数的值,调用者是可以看到这种变化的。
+
+不知道大家有没有发现,其实传共享对象调用和传值调用的过程几乎是一样的,都是进行"求值"、"拷贝"、"传递"。你品,你细品。
+
+![][1]
+
+但是,传共享对象调用和内传引用调用的结果又是一样的,都是在被调函数中如果改变参数的内容,那么这种改变也会对调用者有影响。你再品,你再细品。
+
+那么,共享对象传递和值传递以及引用传递之间到底有很么关系呢?
-上面提到了,当我们调用一个有参函数的时候,会把实际参数传递给形式参数。但是,在程序语言中,这个传递过程中传递的两种情况,即值传递和引用传递。我们来看下程序语言中是如何定义和区分值传递和引用传递的。
+对于这个问题,我们应该关注过程,而不是结果,**因为传共享对象调用的过程和传值调用的过程是一样的,而且都有一步关键的操作,那就是"复制",所以,通常我们认为传共享对象调用是传值调用的特例**
-> 值传递(pass by value)是指在调用函数时将实际参数`复制`一份传递到函数中,这样在函数中如果对`参数`进行修改,将不会影响到实际参数。
->
-> 引用传递(pass by reference)是指在调用函数时将实际参数的地址`直接`传递到函数中,那么在函数中对`参数`所进行的修改,将影响到实际参数。
+我们先把传共享对象调用放在一边,我们再来回顾下传值调用和传引用调用的主要区别:
-那么,我来给大家总结一下,值传递和引用传递之前的区别的重点是什么:
+**传值调用是指在调用函数时将实际参数`复制`一份传递到函数中,传引用调用是指在调用函数时将实际参数的引用`直接`传递到函数中。**
-
+![pass-by-reference-vs-pass-by-value-animation][2]
-这里我们来举一个形象的例子。再来深入理解一下值传递和引用传递:
+所以,两者的最主要区别就是是直接传递的,还是传递的是一个副本。
-你有一把钥匙,当你的朋友想要去你家的时候,如果你`直接`把你的钥匙给他了,这就是引用传递。这种情况下,如果他对这把钥匙做了什么事情,比如他在钥匙上刻下了自己名字,那么这把钥匙还给你的时候,你自己的钥匙上也会多出他刻的名字。
+这里我们来举一个形象的例子。再来深入理解一下传值调用和传引用调用:
-你有一把钥匙,当你的朋友想要去你家的时候,你`复刻`了一把新钥匙给他,自己的还在自己手里,这就是值传递。这种情况下,他对这把钥匙做什么都不会影响你手里的这把钥匙。
+你有一把钥匙,当你的朋友想要去你家的时候,如果你`直接`把你的钥匙给他了,这就是引用传递。
+这种情况下,如果他对这把钥匙做了什么事情,比如他在钥匙上刻下了自己名字,那么这把钥匙还给你的时候,你自己的钥匙上也会多出他刻的名字。
-### 参考资料
+你有一把钥匙,当你的朋友想要去你家的时候,你`复刻`了一把新钥匙给他,自己的还在自己手里,这就是值传递。
-[Evaluation strategy][7]
+这种情况下,他对这把钥匙做什么都不会影响你手里的这把钥匙。
-[关于值传递和引用传递][8]
+前面我们介绍过了传值调用、传引用调用以及传值调用的特例传共享对象调用,那么,Java中是采用的哪种求值策略呢?
-[按值传递、按引用传递、按共享传递][9]
+下一篇我们深入分析。
-[Is Java “pass-by-reference” or “pass-by-value”?][2]
-[2]: https://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value
-[7]: https://en.wikipedia.org/wiki/Evaluation_strategy
-[8]: http://chenwenbo.github.io/2016/05/11/%E5%85%B3%E4%BA%8E%E5%80%BC%E4%BC%A0%E9%80%92%E5%92%8C%E5%BC%95%E7%94%A8%E4%BC%A0%E9%80%92/
-[9]: http://menzhongxin.com/2017/02/07/%E6%8C%89%E5%80%BC%E4%BC%A0%E9%80%92-%E6%8C%89%E5%BC%95%E7%94%A8%E4%BC%A0%E9%80%92%E5%92%8C%E6%8C%89%E5%85%B1%E4%BA%AB%E4%BC%A0%E9%80%92/
+ [1]: http://www.hollischuang.com/wp-content/uploads/2020/04/15865905252659.jpg
+ [2]: http://www.hollischuang.com/wp-content/uploads/2020/04/pass-by-reference-vs-pass-by-value-animation.gif
\ No newline at end of file
diff --git a/docs/basics/object-oriented/multiple-inheritance.md b/docs/basics/object-oriented/multiple-inheritance.md
index b6bcb235..308636b5 100644
--- a/docs/basics/object-oriented/multiple-inheritance.md
+++ b/docs/basics/object-oriented/multiple-inheritance.md
@@ -41,7 +41,7 @@
虽然我们还是没办法使用extends同时继承多个类,但是因为有了默认函数,我们有可能通过implements从多个接口中继承到多个默认函数,那么,又如何解决这种情况带来的菱形继承问题呢?
-这个问题,我们在后面的Java 8部分单独介绍。
+这个问题,我们在后面第20.4章节中单独介绍。
[1]: https://www.hollischuang.com/wp-content/uploads/2021/02/16145019571199.jpg
\ No newline at end of file
diff --git a/docs/basics/object-oriented/object-oriented-vs-procedure-oriented.md b/docs/basics/object-oriented/object-oriented-vs-procedure-oriented.md
index dd8232a1..cb988ce5 100644
--- a/docs/basics/object-oriented/object-oriented-vs-procedure-oriented.md
+++ b/docs/basics/object-oriented/object-oriented-vs-procedure-oriented.md
@@ -1,42 +1,40 @@
-面向对象和面向过程是两种软件开发方法,或者说是两种不同的开发范式。
+相信很多Java开发者,在最初接触Java的时候就听说过,Java是一种面向对象的开发语言,那么什么是面向对象呢?
+首先,所谓面向对象,其实是指软件工程中的一类编程风格,很多人称呼他们为开发范式、编程泛型(Programming Paradigm)。面向对象是众多开发范式中的一种。除了面向对象以外,还有面向过程、指令式编程、函数式编程等。
-### 什么是面向过程?
-
-“面向过程”(Procedure Oriented)是一种以过程为中心的编程思想,是一种自顶而下的编程模式。
-
+虽然这几年函数式编程越来越被人们所熟知,但是,在所有的开发范式中,我们接触最多的主要还是面向过程和面向对象两种。
-最典型的面向过程的编程语言就是C语言。
+那么,在本书的第一章的第一篇,我们来简单介绍下,什么是面向过程和面向对象。
-#### 概述
-把问题分解成一个一个步骤,每个步骤用函数实现,依次调用即可。
+### 什么是面向过程?
-就是说,在进行面向过程编程的时候,不需要考虑那么多,上来先定义一个函数,然后使用各种诸如if-else、for-each等方式进行代码执行。
+面向过程(Procedure Oriented)是一种以过程为中心的编程思想,是一种自顶而下的编程模式。最典型的面向过程的编程语言就是C语言。
-最典型的用法就是实现一个简单的算法,比如实现冒泡排序。
+简单来说,面向过程的开发范式中,程序员需要把问题分解成一个一个步骤,每个步骤用函数实现,依次调用即可。
+就是说,在进行面向过程编程的时候,不需要考虑那么多,上来先定义一个函数,然后使用各种诸如if-else、for-each等方式进行代码执行。最典型的用法就是实现一个简单的算法,比如实现冒泡排序。
-### 什么是面向对象?
+面向过程进行的软件开发,其代码都是流程化的,很明确的可以看出第一步做什么、第二步做什么。这种方式的代码执行起来效率很高。
-面向对象程序设计的雏形,早在出现在1960年的Simula语言中,当时的程序设计领域正面临着一种危机:在软硬件环境逐渐复杂的情况下,软件如何得到良好的维护?
+但是,面向过程同时存在着代码重用性低,扩展能力差,后期维护难度比较大等问题。
-面向对象程序设计在某种程度上通过强调可重复性解决了这一问题。
-目前较为流行的面向对象语言主要有Java、C#、C++、Python、Ruby、PHP等
+### 什么是面向对象?
-面向对象是一种将事务高度抽象化的编程模式
+面向对象(Object Oriented)的雏形,最早在出现在1960年的Simula语言中,当时的程序设计领域正面临着一种危机:在软硬件环境逐渐复杂的情况下,软件如何得到良好的维护?
-#### 概述:
+面向对象程序设计在某种程度上通过强调可重复性解决了这一问题。目前较为流行的面向对象语言主要有Java、C#、C++、Python、Ruby、PHP等。
-将问题分解成一个一个步骤,对每个步骤进行相应的抽象,形成对象,通过不同对象之间的调用,组合解决问题。
+简单来说,面向对象的开发范式中,程序员将问题分解成一个一个步骤,对每个步骤进行相应的抽象,形成对象,通过不同对象之间的调用,组合解决问题。
-就是说,在进行面向对象进行编程的时候,要把属性、行为等封装成对象,然后基于这些对象及对象的能力进行业务逻辑的实现。
+就是说,在进行面向对象进行编程的时候,要把属性、行为等封装成对象,然后基于这些对象及对象的能力进行业务逻辑的实现。比如:想要造一辆车,上来要先把车的各种属性定义出来,然后抽象成一个Car类。
-比如:想要造一辆车,上来要先把车的各种属性定义出来,然后抽象成一个Car类。
+面向对象的编程方法之所以更加受欢迎,是因为他更加符合人类的思维方式。这种方式编写出来的代码扩展性、可维护性都很高。
+与其实面向对象是一种开发范式,倒不如说面向对象是一种对现实世界的理解和抽象的方法。通过对现实世界的理解和抽象,在运用封装、继承、多态等方法,通过抽象出对象的方式进行软件开发。
-面向对象具有三大基本特征和五大基本原则,这一点在后面的章节中展开介绍。
+什么是封装、继承、多态?具体如何运营面向对象的方式编写代码呢?接下来我们介绍下面向对象具有三大基本特征和五大基本原则。
diff --git a/docs/basics/object-oriented/overloading-vs-overriding.md b/docs/basics/object-oriented/overloading-vs-overriding.md
index 2e08cd24..952316d6 100644
--- a/docs/basics/object-oriented/overloading-vs-overriding.md
+++ b/docs/basics/object-oriented/overloading-vs-overriding.md
@@ -1,28 +1,39 @@
-![overloading-vs-overriding][1]
-
-重载(Overloading)和重写(Overriding)是Java中两个比较重要的概念。但是对于新手来说也比较容易混淆。本文通过两个简单的例子说明了他们之间的区别。
+重载(Overloading)和重写(Overriding)是Java中两个比较重要的概念。但是对于新手来说也比较容易混淆,本文就举两个实际的例子,来说明下到底是什么是重写和重载。
## 定义
-### 重载
+首先我们分别来看一下重载和重写的定义:
+
+重载:指的是在同一个类中,多个函数或者方法有同样的名称,但是参数列表不相同的情形,这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者方法。
-简单说,就是函数或者方法有同样的名称,但是参数列表不相同的情形,这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者方法。
+重写:指的是在Java的子类与父类中有两个名称、参数列表都相同的方法的情况。由于他们具有相同的方法签名,所以子类中的新方法将覆盖父类中原有的方法。
+
+## 重载的例子
-### 重写
+ class Dog{
+ public void bark(){
+ System.out.println("woof ");
+ }
+
+ //overloading method
+ public void bark(int num){
+ for(int i=0; i 1、重载是一个编译期概念、重写是一个运行期间概念。
->
-> 2、重载遵循所谓“编译期绑定”,即在编译时根据参数变量的类型判断应该调用哪个方法。
->
-> 3、重写遵循所谓“运行期绑定”,即在运行的时候,根据引用变量所指向的实际对象的类型来调用方法
->
-> 4、因为在编译期已经确定调用哪个方法,所以重载并不是多态。而重写是多态。重载只是一种语言特性,是一种语法规则,与多态无关,与面向对象也无关。(注:严格来说,重载是编译时多态,即静态多态。但是,Java中提到的多态,在不特别说明的情况下都指动态多态)
+1、被重载的方法必须改变参数列表;
+2、被重载的方法可以改变返回类型;
+3、被重载的方法可以改变访问修饰符;
+4、被重载的方法可以声明新的或更广的检查异常;
+5、方法能够在同一个类中或者在一个子类中被重载。
## 重写的例子
@@ -56,58 +67,21 @@
bowl
-上面的例子中,`dog`对象被定义为`Dog`类型。在编译期,编译器会检查Dog类中是否有可访问的`bark()`方法,只要其中包含`bark()`方法,那么就可以编译通过。在运行期,`Hound`对象被`new`出来,并赋值给`dog`变量,这时,JVM是明确的知道`dog`变量指向的其实是`Hound`对象的引用。所以,当`dog`调用`bark()`方法的时候,就会调用`Hound`类中定义的`bark()`方法。这就是所谓的动态多态性。
-
-### 重写的条件
-
-> 参数列表必须完全与被重写方法的相同;
->
-> 返回类型必须完全与被重写方法的返回类型相同;
->
-> 访问级别的限制性一定不能比被重写方法的强;
->
-> 访问级别的限制性可以比被重写方法的弱;
->
-> 重写方法一定不能抛出新的检查异常或比被重写的方法声明的检查异常更广泛的检查异常
->
-> 重写的方法能够抛出更少或更有限的异常(也就是说,被重写的方法声明了异常,但重写的方法可以什么也不声明)
->
-> 不能重写被标示为final的方法;
->
-> 如果不能继承一个方法,则不能重写这个方法。
-
-## 重载的例子
-
- class Dog{
- public void bark(){
- System.out.println("woof ");
- }
-
- //overloading method
- public void bark(int num){
- for(int i=0; i 被重载的方法必须改变参数列表;
->
-> 被重载的方法可以改变返回类型;
->
-> 被重载的方法可以改变访问修饰符;
->
-> 被重载的方法可以声明新的或更广的检查异常;
->
-> 方法能够在同一个类中或者在一个子类中被重载。
+在编译期,编译器会检查Dog类中是否有可访问的`bark()`方法,只要其中包含`bark()`方法,那么就可以编译通过。
-## 参考资料
+在运行期,`Hound`对象被`new`出来,并赋值给`dog`变量,这时,JVM是明确的知道`dog`变量指向的其实是`Hound`对象的引用。所以,当`dog`调用`bark()`方法的时候,就会调用`Hound`类中定义的`bark()`方法。这就是所谓的动态多态性。
-[Overriding vs. Overloading in Java][2]
+方法重写的条件需要具备以下条件和要求:
- [1]: http://www.hollischuang.com/wp-content/uploads/2016/03/overloading-vs-overriding.png
- [2]: http://www.programcreek.com/2009/02/overriding-and-overloading-in-java-with-examples/
\ No newline at end of file
+1、参数列表必须完全与被重写方法的相同;
+2、返回类型必须完全与被重写方法的返回类型相同;
+3、访问级别的限制性一定不能比被重写方法的强;
+4、访问级别的限制性可以比被重写方法的弱;
+5、重写方法一定不能抛出新的检查异常或比被重写的方法声明的检查异常更广泛的检查异常
+6、重写的方法能够抛出更少或更有限的异常(也就是说,被重写的方法声明了异常,但重写的方法可以什么也不声明)
+7、不能重写被标示为final的方法;
+8、如果不能继承一个方法,则不能重写这个方法。
\ No newline at end of file
diff --git a/docs/basics/object-oriented/polymorphism.md b/docs/basics/object-oriented/polymorphism.md
index 5289d367..8fa3608d 100644
--- a/docs/basics/object-oriented/polymorphism.md
+++ b/docs/basics/object-oriented/polymorphism.md
@@ -1,12 +1,42 @@
-### 什么是多态
+在第1.2章节中,我们介绍了面向对象的封装、继承和多态这三个基本特性,并且分别对封装和继承简单的举例做了说明。
-多态的概念呢比较简单,就是同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。
+这一章节中,我们针对上一章节遗留的多态性进行展开介绍。
-如果按照这个概念来定义的话,那么多态应该是一种运行期的状态。
-### 多态的必要条件
+## 什么是多态
-为了实现运行期的多态,或者说是动态绑定,需要满足三个条件:
+我们先基于所有的编程语言介绍了什么是多态以及多态的分类。然后再重点介绍下Java中的多态。
+
+多态(Polymorphism),指为不同数据类型的实体提供统一的接口,或使用一个单一的符号来表示多个不同的类型。一般情况下,可以把多态分成以下几类:
+
+* 特设多态:为个体的特定类型的任意集合定义一个共同接口。
+* 参数多态:指定一个或多个类型不靠名字而是靠可以标识任何类型的抽象符号。
+* 子类型:一个名字指称很多不同的类的实例,这些类有某个共同的超类。
+
+### 特设多态
+
+特设多态是程序设计语言的一种多态,多态函数有多个不同的实现,依赖于其实参而调用相应版本的函数。
+
+上一节我们介绍过的函数重载是特设多态的一种,除此之外还有运算符重载也是特设多态的一种。
+
+
+### 参数多态
+
+参数多态在程序设计语言与类型论中是指声明与定义函数、复合类型、变量时不指定其具体的类型,而把这部分类型作为参数使用,使得该定义对各种具体类型都适用。
+
+参数多态其实也有很广泛的应用,比如Java中的泛型就是参数多态的一种。参数多态另外一个应用比较广泛的地方就是函数式编程。
+
+### 子类型
+
+在面向对象程序设计中,计算机程序运行时,相同的消息可能会送给多个不同的类别之对象,而系统可依据对象所属类别,引发对应类别的方法,而有不同的行为。
+
+这种子类型多态其实就是Java中常见的多态,下面我们针对Java中的这种子类型多态展开介绍下。
+
+## Java中的多态
+
+Java中的多态的概念比较简单,就是同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。
+
+Java中多态其实是一种运行期的状态。为了实现运行期的多态,或者说是动态绑定,需要满足三个条件:
* 有类继承或者接口实现
* 子类要重写父类的方法
@@ -53,10 +83,18 @@
### 静态多态
-上面我们说的多态,是一种运行期的概念。另外,还有一种说法,包括维基百科也说明,多态还分为动态多态和静态多态。
+上面我们说的多态,是一种运行期的概念。另外,还有一种说法,认为多态还分为动态多态和静态多态。
上面提到的那种动态绑定认为是动态多态,因为只有在运行期才能知道真正调用的是哪个类的方法。
很多人认为,还有一种静态多态,一般认为Java中的函数重载是一种静态多态,因为他需要在编译期决定具体调用哪个方法。
-但是,其实作者认为,多态应该是一种运行期特性,Java中的方法重写是多态的体现。虽然也有人提出重载是一种静态多态的想法,这个问题在StackOverflow等网站上有很多人讨论,但是并没有什么定论。我更加倾向于重载不是多态。
+结合2.1章节,我们介绍过的重载和重写的相关概念,我们再来总结下重载和重写这两个概念:
+
+1、重载是一个编译期概念、重写是一个运行期概念。
+
+2、重载遵循所谓“编译期绑定”,即在编译时根据参数变量的类型判断应该调用哪个方法。
+
+3、重写遵循所谓“运行期绑定”,即在运行的时候,根据引用变量所指向的实际对象的类型来调用方法。
+
+4、Java中的方法重写是Java多态(子类型)的实现方式。而Java中的方法重写其实是特设多态的一种实现方式。
\ No newline at end of file
diff --git a/docs/basics/object-oriented/principle.md b/docs/basics/object-oriented/principle.md
index 43e92a59..2f93e606 100644
--- a/docs/basics/object-oriented/principle.md
+++ b/docs/basics/object-oriented/principle.md
@@ -1,40 +1,66 @@
+面向对象开发范式的最大的好处就是易用、易扩展、易维护,但是,什么样的代码是易用、易扩展、易维护的呢?如何衡量他们呢?
-
+罗伯特·C·马丁在21世纪早期提出了SOLID原则,这是五个原则的缩写的组合,这五个原则沿用至今。
### 单一职责原则(Single-Responsibility Principle)
-其核心思想为:一个类,最好只做一件事,只有一个引起它的变化。单一职责原则可以看做是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。职责过多,可能引起它变化的原因就越多,这将导致职责依赖,相互之间就产生影响,从而大大损伤其内聚性和耦合度。通常意义下的单一职责,就是指只有一种单一功能,不要为类实现过多的功能点,以保证实体只有一个引起它变化的原因。
+其核心思想为:一个类,最好只做一件事,只有一个引起它的变化。
+
+单一职责原则可以看做是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。职责过多,可能引起它变化的原因就越多,这将导致职责依赖,相互之间就产生影响,从而大大损伤其内聚性和耦合度。通常意义下的单一职责,就是指只有一种单一功能,不要为类实现过多的功能点,以保证实体只有一个引起它变化的原因。
专注,是一个人优良的品质;同样的,单一也是一个类的优良设计。交杂不清的职责将使得代码看起来特别别扭牵一发而动全身,有失美感和必然导致丑陋的系统错误风险。
### 开放封闭原则(Open-Closed principle)
-其核心思想是:软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭的。开放封闭原则主要体现在两个方面1、对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。2、对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对其进行任何尝试的修改。
+其核心思想是:软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭的。
+
+开放封闭原则主要体现在两个方面:
+
+1、对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
+
+2、对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对其进行任何尝试的修改。
+
实现开放封闭原则的核心思想就是对抽象编程,而不对具体编程,因为抽象相对稳定。让类依赖于固定的抽象,所以修改就是封闭的;而通过面向对象的继承和多态机制,又可以实现对抽象类的继承,通过覆写其方法来改变固有行为,实现新的拓展方法,所以就是开放的。
“需求总是变化”没有不变的软件,所以就需要用封闭开放原则来封闭变化满足需求,同时还能保持软件内部的封装体系稳定,不被需求的变化影响。
-### Liskov替换原则(Liskov-Substitution Principle)
+### 里氏替换原则(Liskov-Substitution Principle)
+
+其核心思想是:子类必须能够替换其基类。这一思想体现为对继承机制的约束规范,只有子类能够替换基类时,才能保证系统在运行期内识别子类,这是保证继承复用的基础。
+
+在父类和子类的具体行为中,必须严格把握继承层次中的关系和特征,将基类替换为子类,程序的行为不会发生任何变化。同时,这一约束反过来则是不成立的,子类可以替换基类,但是基类不一定能替换子类。
+里氏替换原则,主要着眼于对抽象和多态建立在继承的基础上,因此只有遵循了Liskov替换原则,才能保证继承复用是可靠地。实现的方法是面向接口编程:将公共部分抽象为基类接口或抽象类,通过Extract Abstract Class,在子类中通过覆写父类的方法实现新的方式支持同样的职责。
+
+里氏替换原则是关于继承机制的设计原则,违反了Liskov替换原则就必然导致违反开放封闭原则。
-其核心思想是:子类必须能够替换其基类。这一思想体现为对继承机制的约束规范,只有子类能够替换基类时,才能保证系统在运行期内识别子类,这是保证继承复用的基础。在父类和子类的具体行为中,必须严格把握继承层次中的关系和特征,将基类替换为子类,程序的行为不会发生任何变化。同时,这一约束反过来则是不成立的,子类可以替换基类,但是基类不一定能替换子类。
-Liskov替换原则,主要着眼于对抽象和多态建立在继承的基础上,因此只有遵循了Liskov替换原则,才能保证继承复用是可靠地。实现的方法是面向接口编程:将公共部分抽象为基类接口或抽象类,通过Extract Abstract Class,在子类中通过覆写父类的方法实现新的方式支持同样的职责。
-Liskov替换原则是关于继承机制的设计原则,违反了Liskov替换原则就必然导致违反开放封闭原则。
-Liskov替换原则能够保证系统具有良好的拓展性,同时实现基于多态的抽象机制,能够减少代码冗余,避免运行期的类型判别。
+里氏替换原则能够保证系统具有良好的拓展性,同时实现基于多态的抽象机制,能够减少代码冗余,避免运行期的类型判别。
### 依赖倒置原则(Dependecy-Inversion Principle)
其核心思想是:依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。
+
我们知道,依赖一定会存在于类与类、模块与模块之间。当两个模块之间存在紧密的耦合关系时,最好的方法就是分离接口和实现:在依赖之间定义一个抽象的接口使得高层模块调用接口,而底层模块实现接口的定义,以此来有效控制耦合关系,达到依赖于抽象的设计目标。
抽象的稳定性决定了系统的稳定性,因为抽象是不变的,依赖于抽象是面向对象设计的精髓,也是依赖倒置原则的核心。
+
依赖于抽象是一个通用的原则,而某些时候依赖于细节则是在所难免的,必须权衡在抽象和具体之间的取舍,方法不是一层不变的。依赖于抽象,就是对接口编程,不要对实现编程。
### 接口隔离原则(Interface-Segregation Principle)
其核心思想是:使用多个小的专门的接口,而不要使用一个大的总接口。
+
具体而言,接口隔离原则体现在:接口应该是内聚的,应该避免“胖”接口。一个类对另外一个类的依赖应该建立在最小的接口上,不要强迫依赖不用的方法,这是一种接口污染。
+
接口有效地将细节和抽象隔离,体现了对抽象编程的一切好处,接口隔离强调接口的单一性。而胖接口存在明显的弊端,会导致实现的类型必须完全实现接口的所有方法、属性等;而某些时候,实现类型并非需要所有的接口定义,在设计上这是“浪费”,而且在实施上这会带来潜在的问题,对胖接口的修改将导致一连串的客户端程序需要修改,有时候这是一种灾难。在这种情况下,将胖接口分解为多个特点的定制化方法,使得客户端仅仅依赖于它们的实际调用的方法,从而解除了客户端不会依赖于它们不用的方法。
-分离的手段主要有以下两种:1、委托分离,通过增加一个新的类型来委托客户的请求,隔离客户和接口的直接依赖,但是会增加系统的开销。2、多重继承分离,通过接口多继承来实现客户的需求,这种方式是较好的。
+分离的手段主要有以下两种:
+
+1、委托分离,通过增加一个新的类型来委托客户的请求,隔离客户和接口的直接依赖,但是会增加系统的开销。
+
+2、多重继承分离,通过接口多继承来实现客户的需求,这种方式是较好的。
+
+以上就是5个基本的面向对象设计原则,它们就像面向对象程序设计中的金科玉律,遵守它们可以使我们的代码更加鲜活,易于复用,易于拓展,灵活优雅。
+
+不同的设计模式对应不同的需求,而设计原则则代表永恒的灵魂,需要在实践中时时刻刻地遵守。就如ARTHUR J.RIEL在那边《OOD启示录》中所说的:“你并不必严格遵守这些原则,违背它们也不会被处以宗教刑罚。但你应当把这些原则看做警铃,若违背了其中的一条,那么警铃就会响起。”
-以上就是5个基本的面向对象设计原则,它们就像面向对象程序设计中的金科玉律,遵守它们可以使我们的代码更加鲜活,易于复用,易于拓展,灵活优雅。不同的设计模式对应不同的需求,而设计原则则代表永恒的灵魂,需要在实践中时时刻刻地遵守。就如ARTHUR J.RIEL在那边《OOD启示录》中所说的:“你并不必严格遵守这些原则,违背它们也不会被处以宗教刑罚。但你应当把这些原则看做警铃,若违背了其中的一条,那么警铃就会响起。”
+很多人刚开始可能对这些原则无法深刻的理解,但是没关系,随着自己开发经验的增长,就会慢慢的可以理解这些原则了。
\ No newline at end of file
diff --git a/docs/basics/object-oriented/scope.md b/docs/basics/object-oriented/scope.md
index 39c43aad..f03889bf 100644
--- a/docs/basics/object-oriented/scope.md
+++ b/docs/basics/object-oriented/scope.md
@@ -1,3 +1,6 @@
+我们通过封装的手段,将成员变量、方法等包装在一个类中,那么,被封装在类中的这些成员变量和方法,能不能被外部访问呢?能被谁访问呢?
+
+这种能不能被访问、能被谁访问的特性,Java是通过访问控制修饰符来实现的。Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问,Java 支持 4 种不同的访问权限。
对于成员变量和方法的作用域,public,protected,private以及不写之间的区别:
@@ -8,4 +11,5 @@
`protected` : 表明成员变量或者方法对类自身,与同在一个包中的其他类可见,其他包下的类不可访问,除非是他的子类
-`default` : 表明该成员变量或者方法只有自己和其位于同一个包的内可见,其他包内的类不能访问,即便是它的子类
\ No newline at end of file
+`default` : 表明该成员变量或者方法只有自己和其位于同一个包的内可见,其他包内的类不能访问,即便是它的子类
+
diff --git a/docs/basics/object-oriented/variable.md b/docs/basics/object-oriented/variable.md
index cc3849c2..b2ba4b2a 100644
--- a/docs/basics/object-oriented/variable.md
+++ b/docs/basics/object-oriented/variable.md
@@ -25,3 +25,11 @@ Java中共有三种变量,分别是类变量、成员变量和局部变量。
}
```
上面定义的三个变量中,变量a就是类变量,变量b就是成员变量,而变量c和d是局部变量。
+
+a作为类变量,他存放在方法区中;b作为成员变量,和对象一起存储在堆内存中(不考虑栈上分配的情况);c和d作为方法的局部变量,保存在栈内存中。
+
+之所以要在这一章节重点介绍下这三种变量类型,是因为很多人因为不知道这三种类型的区别,所以不知道他们分别存放在哪里,这就导致不知道那些变量需要考虑并发问题。
+
+关于并发问题,目前本书《基本篇》还不涉及,会在下一本《并发篇》中重点介绍,这里先简单说明一下:
+
+因为只有共享变量才会遇到并发问题,所以,变量a和b是共享变量,变量c和d是非共享变量。所以如果遇到多线程场景,对于变量a和b的操作是需要考虑线程安全的,而对于线程c和d的操作是不需要考虑线程安全的。
\ No newline at end of file
diff --git a/docs/basics/object-oriented/why-pass-by-reference.md b/docs/basics/object-oriented/why-pass-by-reference.md
index 3f0d890d..81d37c9d 100644
--- a/docs/basics/object-oriented/why-pass-by-reference.md
+++ b/docs/basics/object-oriented/why-pass-by-reference.md
@@ -1,90 +1,3 @@
-对于初学者来说,要想把这个问题回答正确,最初思考这个问题的时候,我发现我竟然无法通过简单的语言把这个事情描述的很容易理解,遗憾的是,我也没有在网上找到哪篇文章可以把这个事情讲解的通俗易懂。所以,就有了我写这篇文章的初衷。
-
-### 辟谣时间
-
-关于这个问题,在[StackOverflow][5]上也引发过广泛的讨论,看来很多程序员对于这个问题的理解都不尽相同,甚至很多人理解的是错误的。还有的人可能知道Java中的参数传递是值传递,但是说不出来为什么。
-
-在开始深入讲解之前,有必要纠正一下大家以前的那些错误看法了。如果你有以下想法,那么你有必要好好阅读本文。
-
-> 错误理解一:值传递和引用传递,区分的条件是传递的内容,如果是个值,就是值传递。如果是个引用,就是引用传递。
->
-> 错误理解二:Java是引用传递。
->
-> 错误理解三:传递的参数如果是普通类型,那就是值传递,如果是对象,那就是引用传递。
-
-### 实参与形参
-
-我们都知道,在Java中定义方法的时候是可以定义参数的。比如Java中的main方法,`public static void main(String[] args)`,这里面的args就是参数。参数在程序语言中分为形式参数和实际参数。
-
-> 形式参数:是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。
->
-> 实际参数:在调用有参函数时,主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。
-
-简单举个例子:
-
- public static void main(String[] args) {
- ParamTest pt = new ParamTest();
- pt.sout("Hollis");//实际参数为 Hollis
- }
-
- public void sout(String name) { //形式参数为 name
- System.out.println(name);
- }
-
-
-实际参数是调用有参方法的时候真正传递的内容,而形式参数是用于接收实参内容的参数。
-
-### 求值策略
-
-我们说当进行方法调用的时候,需要把实际参数传递给形式参数,那么传递的过程中到底传递的是什么东西呢?
-
-这其实是程序设计中**求值策略(Evaluation strategies)**的概念。
-
-在计算机科学中,求值策略是确定编程语言中表达式的求值的一组(通常确定性的)规则。求值策略定义何时和以何种顺序求值给函数的实际参数、什么时候把它们代换入函数、和代换以何种形式发生。
-
-求值策略分为两大基本类,基于如何处理给函数的实际参数,分为严格的和非严格的。
-
-#### 严格求值
-
-在“严格求值”中,函数调用过程中,给函数的实际参数总是在应用这个函数之前求值。多数现存编程语言对函数都使用严格求值。所以,我们本文只关注严格求值。
-
-在严格求值中有几个关键的求值策略是我们比较关心的,那就是**传值调用**(Call by value)、**传引用调用**(Call by reference)以及**传共享对象调用**(Call by sharing)。
-
-* 传值调用(值传递)
- * 在传值调用中,实际参数先被求值,然后其值通过复制,被传递给被调函数的形式参数。因为形式参数拿到的只是一个"局部拷贝",所以如果在被调函数中改变了形式参数的值,并不会改变实际参数的值。
-* 传引用调用(引用传递)
- * 在传引用调用中,传递给函数的是它的实际参数的隐式引用而不是实参的拷贝。因为传递的是引用,所以,如果在被调函数中改变了形式参数的值,改变对于调用者来说是可见的。
-* 传共享对象调用(共享对象传递)
- * 传共享对象调用中,先获取到实际参数的地址,然后将其复制,并把该地址的拷贝传递给被调函数的形式参数。因为参数的地址都指向同一个对象,所以我们也称之为"传共享对象",所以,如果在被调函数中改变了形式参数的值,调用者是可以看到这种变化的。
-
-不知道大家有没有发现,其实传共享对象调用和传值调用的过程几乎是一样的,都是进行"求值"、"拷贝"、"传递"。你品,你细品。
-
-![][1]
-
-但是,传共享对象调用和内传引用调用的结果又是一样的,都是在被调函数中如果改变参数的内容,那么这种改变也会对调用者有影响。你再品,你再细品。
-
-那么,共享对象传递和值传递以及引用传递之间到底有很么关系呢?
-
-对于这个问题,我们应该关注过程,而不是结果,**因为传共享对象调用的过程和传值调用的过程是一样的,而且都有一步关键的操作,那就是"复制",所以,通常我们认为传共享对象调用是传值调用的特例**
-
-我们先把传共享对象调用放在一边,我们再来回顾下传值调用和传引用调用的主要区别:
-
-**传值调用是指在调用函数时将实际参数`复制`一份传递到函数中,传引用调用是指在调用函数时将实际参数的引用`直接`传递到函数中。**
-
-![pass-by-reference-vs-pass-by-value-animation][2]
-
-所以,两者的最主要区别就是是直接传递的,还是传递的是一个副本。
-
-这里我们来举一个形象的例子。再来深入理解一下传值调用和传引用调用:
-
-你有一把钥匙,当你的朋友想要去你家的时候,如果你`直接`把你的钥匙给他了,这就是引用传递。
-
-这种情况下,如果他对这把钥匙做了什么事情,比如他在钥匙上刻下了自己名字,那么这把钥匙还给你的时候,你自己的钥匙上也会多出他刻的名字。
-
-你有一把钥匙,当你的朋友想要去你家的时候,你`复刻`了一把新钥匙给他,自己的还在自己手里,这就是值传递。
-
-这种情况下,他对这把钥匙做什么都不会影响你手里的这把钥匙。
-
### Java的求值策略
前面我们介绍过了传值调用、传引用调用以及传值调用的特例传共享对象调用,那么,Java中是采用的哪种求值策略呢?
@@ -159,7 +72,7 @@
-在参数传递的过程中,实际参数的地址`0X1213456`被拷贝给了形参。这个过程其实就是值传递,只不过传递的值的内容是对象的引用。
+在参数传递的过程中,实际参数的地址`0X1213456`被拷贝给了形参。这个过程其实就是值传递,只不过传递的值得内容是对象的应用。
那为什么我们改了user中的属性的值,却对原来的user产生了影响呢?
@@ -220,9 +133,7 @@ OK,以上就是本文的全部内容,不知道本文是否帮助你解开了
[Passing by Value vs. by Reference Visual Explanation][6]
- [1]: https://www.hollischuang.com/wp-content/uploads/2020/04/15865905252659.jpg
- [2]: https://www.hollischuang.com/wp-content/uploads/2020/04/pass-by-reference-vs-pass-by-value-animation.gif
[3]: https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html
[4]: https://en.wikipedia.org/wiki/Evaluation_strategy
[5]: https://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value
- [6]: https://blog.penjee.com/passing-by-value-vs-by-reference-java-graphical/
+ [6]: https://blog.penjee.com/passing-by-value-vs-by-reference-java-graphical/
\ No newline at end of file
From 671c44f7c325848b3b724433e5f6d08b62a917c8 Mon Sep 17 00:00:00 2001
From: stunievi
Date: Sun, 27 Jun 2021 08:56:45 +0800
Subject: [PATCH 69/84] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=B8=80=E4=B8=AA?=
=?UTF-8?q?=E9=94=99=E5=88=AB=E5=AD=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/basics/java-basic/final-string.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/basics/java-basic/final-string.md b/docs/basics/java-basic/final-string.md
index 920b8e0c..a5763b6d 100644
--- a/docs/basics/java-basic/final-string.md
+++ b/docs/basics/java-basic/final-string.md
@@ -23,7 +23,7 @@ s = s.concat("ef");
所以,一旦一个string对象在内存(堆)中被创建出来,他就无法被修改。而且,String类的所有方法都没有改变字符串本身的值,都是返回了一个新的对象。
-如果我们想要一个可秀改的字符串,可以选择StringBuffer 或者 StringBuilder这两个代替String。
+如果我们想要一个可修改的字符串,可以选择StringBuffer 或者 StringBuilder这两个代替String。
### 为什么String要设计成不可变
From a1cfbfc6fc5055384bfc0356b6abfa943fd50ddf Mon Sep 17 00:00:00 2001
From: Gale <1372363493@qq.com>
Date: Tue, 10 Aug 2021 17:51:23 +0800
Subject: [PATCH 70/84] Update syntactic-sugar.md
---
docs/basics/java-basic/syntactic-sugar.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/basics/java-basic/syntactic-sugar.md b/docs/basics/java-basic/syntactic-sugar.md
index 8c085333..fb2a1471 100644
--- a/docs/basics/java-basic/syntactic-sugar.md
+++ b/docs/basics/java-basic/syntactic-sugar.md
@@ -268,7 +268,7 @@ Java SE5提供了一种新的类型-Java的枚举类型,关键字`enum`可以
}
-通过反编译后代码我们可以看到,`public final class T extends Enum`,说明,该类是继承了`Enum`类的,同时`final`关键字告诉我们,这个类也是不能被继承的。**当我们使用`enmu`来定义一个枚举类型的时候,编译器会自动帮我们创建一个`final`类型的类继承`Enum`类,所以枚举类型不能被继承。**
+通过反编译后代码我们可以看到,`public final class T extends Enum`,说明,该类是继承了`Enum`类的,同时`final`关键字告诉我们,这个类也是不能被继承的。**当我们使用`enum`来定义一个枚举类型的时候,编译器会自动帮我们创建一个`final`类型的类继承`Enum`类,所以枚举类型不能被继承。**
### 糖块六 、 内部类
From c2a9d1904c567ac68b99335a19fde4bed470f2df Mon Sep 17 00:00:00 2001
From: Iwi-Young <56102159+Iwi-Young@users.noreply.github.com>
Date: Fri, 20 Aug 2021 11:19:13 +0800
Subject: [PATCH 71/84] =?UTF-8?q?=E6=96=B0new=E7=9A=84user=E8=BF=98?=
=?UTF-8?q?=E6=B2=A1=E8=B5=8B=E5=80=BC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/basics/object-oriented/why-pass-by-reference.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/docs/basics/object-oriented/why-pass-by-reference.md b/docs/basics/object-oriented/why-pass-by-reference.md
index 81d37c9d..f76bb96f 100644
--- a/docs/basics/object-oriented/why-pass-by-reference.md
+++ b/docs/basics/object-oriented/why-pass-by-reference.md
@@ -87,6 +87,7 @@
public void pass(User user) {
user = new User();
user.setName("hollischuang");
+ user.setGender("Male");
System.out.println("print in pass , user is " + user);
}
@@ -136,4 +137,4 @@ OK,以上就是本文的全部内容,不知道本文是否帮助你解开了
[3]: https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html
[4]: https://en.wikipedia.org/wiki/Evaluation_strategy
[5]: https://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value
- [6]: https://blog.penjee.com/passing-by-value-vs-by-reference-java-graphical/
\ No newline at end of file
+ [6]: https://blog.penjee.com/passing-by-value-vs-by-reference-java-graphical/
From 0cb360112f59f3fbc121a221fe7e07e0ab6d996c Mon Sep 17 00:00:00 2001
From: "hollis.zhl"
Date: Fri, 31 Dec 2021 10:38:02 +0800
Subject: [PATCH 72/84] =?UTF-8?q?=E5=86=85=E5=AE=B9=E9=93=BE=E6=8E=A5?=
=?UTF-8?q?=E8=B0=83=E6=95=B4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/_sidebar.md | 2 +-
docs/basics/object-oriented/platform-independent.md | 2 +-
docs/menu.md | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index fb2a2b59..62bc04f4 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -262,7 +262,7 @@
* [什么是泛型](/basics/java-basic/generics.md)
- * [类型擦除](/basics/java-basic/type-erasue.md)
+ * [类型擦除](/basics/java-basic/type-erasure.md)
* [泛型带来的问题](/basics/java-basic/generics-problem.md)
diff --git a/docs/basics/object-oriented/platform-independent.md b/docs/basics/object-oriented/platform-independent.md
index 880d0a44..dd2f0790 100644
--- a/docs/basics/object-oriented/platform-independent.md
+++ b/docs/basics/object-oriented/platform-independent.md
@@ -118,5 +118,5 @@ Java的平台无关性是建立在Java虚拟机的平台有关性基础之上的
[5]: https://www.hollischuang.com/wp-content/uploads/2019/03/15539291533175.jpg
[6]: https://www.hollischuang.com/wp-content/uploads/2019/03/15539297082025.jpg
[7]: https://www.hollischuang.com/wp-content/uploads/2019/03/15539303829914.jpg
- [8]: http://www.hollischuang.com/wp-content/uploads/2019/03/Jietu20200614-165222.jpg
+ [8]: https://www.hollischuang.com/wp-content/uploads/2021/06/Jietu20210627-141259-2.jpg
[9]: https://www.hollischuang.com/archives/2938
diff --git a/docs/menu.md b/docs/menu.md
index f034f328..5dab4bd8 100644
--- a/docs/menu.md
+++ b/docs/menu.md
@@ -319,7 +319,7 @@ Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer)
* [什么是泛型](/basics/java-basic/generics.md)
- * [类型擦除](/basics/java-basic/type-erasue.md)
+ * [类型擦除](/basics/java-basic/type-erasure.md)
* [泛型带来的问题](/basics/java-basic/generics-problem.md)
From 7ce7caf336fb594bc0573b9d797f413dae436401 Mon Sep 17 00:00:00 2001
From: "hollis.zhl"
Date: Fri, 31 Dec 2021 10:42:43 +0800
Subject: [PATCH 73/84] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20decimal=20=E7=9F=A5?=
=?UTF-8?q?=E8=AF=86=E7=82=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../stop-create-bigdecimal-with-double.md | 163 ++++++++++++++++++
docs/menu.md | 2 +-
2 files changed, 164 insertions(+), 1 deletion(-)
create mode 100644 docs/basics/java-basic/stop-create-bigdecimal-with-double.md
diff --git a/docs/basics/java-basic/stop-create-bigdecimal-with-double.md b/docs/basics/java-basic/stop-create-bigdecimal-with-double.md
new file mode 100644
index 00000000..ee5eed79
--- /dev/null
+++ b/docs/basics/java-basic/stop-create-bigdecimal-with-double.md
@@ -0,0 +1,163 @@
+很多人都知道,在进行金额表示、金额计算等场景,不能使用double、float等类型,而是要使用对精度支持的更好的BigDecimal。
+
+所以,很多支付、电商、金融等业务中,BigDecimal的使用非常频繁。**但是,如果误以为只要使用BigDecimal表示数字,结果就一定精确,那就大错特错了!**
+
+在之前的一篇文章中,我们介绍过,使用BigDecimal的equals方法并不能验证两个数是否真的相等([为什么阿里巴巴禁止使用BigDecimal的equals方法做等值比较?][1])。
+
+除了这个情况,BigDecimal的使用的第一步就是创建一个BigDecimal对象,如果这一步都有问题,那么后面怎么算都是错的!
+
+那到底应该如何正确的创建一个BigDecimal?
+
+**关于这个问题,我Review过很多代码,也面试过很多一线开发,很多人都掉进坑里过。这是一个很容易被忽略,但是又影响重大的问题。**
+
+关于这个问题,在《阿里巴巴Java开发手册》中有一条建议,或者说是要求:
+
+![][2]
+
+这是一条【强制】建议,那么,这背后的原理是什么呢?
+
+想要搞清楚这个问题,主要需要弄清楚以下几个问题:
+
+1、为什么说double不精确? 2、BigDecimal是如何保证精确的?
+
+在知道这两个问题的答案之后,我们也就大概知道为什么不能使用BigDecimal(double)来创建一个BigDecimal了。
+
+### double为什么不精确
+
+首先,**计算机是只认识二进制的**,即0和1,这个大家一定都知道。
+
+那么,所有数字,包括整数和小数,想要在计算机中存储和展示,都需要转成二进制。
+
+**十进制整数转成二进制很简单,通常采用"除2取余,逆序排列"即可,如10的二进制为1010。**
+
+但是,小数的二进制如何表示呢?
+
+十进制小数转成二进制,一般采用"乘2取整,顺序排列"方法,如0.625转成二进制的表示为0.101。
+
+但是,并不是所有小数都能转成二进制,如0.1就不能直接用二进制表示,他的二进制是0.000110011001100… 这是一个无限循环小数。
+
+**所以,计算机是没办法用二进制精确的表示0.1的。也就是说,在计算机中,很多小数没办法精确的使用二进制表示出来。**
+
+那么,这个问题总要解决吧。那么,**人们想出了一种采用一定的精度,使用近似值表示一个小数的办法**。这就是IEEE 754(IEEE二进制浮点数算术标准)规范的主要思想。
+
+IEEE 754规定了多种表示浮点数值的方式,其中最常用的就是32位单精度浮点数和64位双精度浮点数。
+
+在Java中,使用float和double分别用来表示单精度浮点数和双精度浮点数。
+
+所谓精度不同,可以简单的理解为保留有效位数不同。采用保留有效位数的方式近似的表示小数。
+
+所以,大家也就知道为什么**double表示的小数不精确**了。
+
+接下来,再回到BigDecimal的介绍,我们接下来看看是如何表示一个数的,他如何保证精确呢?
+
+### BigDecimal如何精确计数?
+
+如果大家看过BigDecimal的源码,其实可以发现,**实际上一个BigDecimal是通过一个"无标度值"和一个"标度"来表示一个数的。**
+
+在BigDecimal中,标度是通过scale字段来表示的。
+
+而无标度值的表示比较复杂。当unscaled value超过阈值(默认为Long.MAX_VALUE)时采用intVal字段存储unscaled value,intCompact字段存储Long.MIN_VALUE,否则对unscaled value进行压缩存储到long型的intCompact字段用于后续计算,intVal为空。
+
+涉及到的字段就是这几个:
+
+ public class BigDecimal extends Number implements Comparable {
+ private final BigInteger intVal;
+ private final int scale;
+ private final transient long intCompact;
+ }
+
+
+关于无标度值的压缩机制大家了解即可,不是本文的重点,大家只需要知道BigDecimal主要是通过一个无标度值和标度来表示的就行了。
+
+**那么标度到底是什么呢?**
+
+除了scale这个字段,在BigDecimal中还提供了scale()方法,用来返回这个BigDecimal的标度。
+
+ /**
+ * Returns the scale of this {@code BigDecimal}. If zero
+ * or positive, the scale is the number of digits to the right of
+ * the decimal point. If negative, the unscaled value of the
+ * number is multiplied by ten to the power of the negation of the
+ * scale. For example, a scale of {@code -3} means the unscaled
+ * value is multiplied by 1000.
+ *
+ * @return the scale of this {@code BigDecimal}.
+ */
+ public int scale() {
+ return scale;
+ }
+
+
+那么,scale到底表示的是什么,其实上面的注释已经说的很清楚了:
+
+> 如果scale为零或正值,则该值表示这个数字小数点右侧的位数。如果scale为负数,则该数字的真实值需要乘以10的该负数的绝对值的幂。例如,scale为-3,则这个数需要乘1000,即在末尾有3个0。
+
+如123.123,那么如果使用BigDecimal表示,那么他的无标度值为123123,他的标度为3。
+
+**而二进制无法表示的0.1,使用BigDecimal就可以表示了,及通过无标度值1和标度1来表示。**
+
+我们都知道,想要创建一个对象,需要使用该类的构造方法,在BigDecimal中一共有以下4个构造方法:
+
+ BigDecimal(int)
+ BigDecimal(double)
+ BigDecimal(long)
+ BigDecimal(String)
+
+
+以上四个方法,创建出来的的BigDecimal的标度(scale)是不同的。
+
+其中 BigDecimal(int)和BigDecimal(long) 比较简单,因为都是整数,所以他们的标度都是0。
+
+而BigDecimal(double) 和BigDecimal(String)的标度就有很多学问了。
+
+### BigDecimal(double)有什么问题
+
+BigDecimal中提供了一个通过double创建BigDecimal的方法——BigDecimal(double) ,但是,同时也给我们留了一个坑!
+
+因为我们知道,double表示的小数是不精确的,如0.1这个数字,double只能表示他的近似值。
+
+所以,**当我们使用new BigDecimal(0.1)创建一个BigDecimal 的时候,其实创建出来的值并不是正好等于0.1的。**
+
+而是0.1000000000000000055511151231257827021181583404541015625。这是因为doule自身表示的只是一个近似值。
+
+![][3]
+
+**所以,如果我们在代码中,使用BigDecimal(double) 来创建一个BigDecimal的话,那么是损失了精度的,这是极其严重的。**
+
+### 使用BigDecimal(String)创建
+
+那么,该如何创建一个精确的BigDecimal来表示小数呢,答案是使用String创建。
+
+而对于BigDecimal(String) ,当我们使用new BigDecimal("0.1")创建一个BigDecimal 的时候,其实创建出来的值正好就是等于0.1的。
+
+那么他的标度也就是1。
+
+但是需要注意的是,new BigDecimal("0.10000")和new BigDecimal("0.1")这两个数的标度分别是5和1,如果使用BigDecimal的equals方法比较,得到的结果是false,具体原因和解决办法参考[为什么阿里巴巴禁止使用BigDecimal的equals方法做等值比较?][1]
+
+那么,想要创建一个能精确的表示0.1的BigDecimal,请使用以下两种方式:
+
+ BigDecimal recommend1 = new BigDecimal("0.1");
+ BigDecimal recommend2 = BigDecimal.valueOf(0.1);
+
+
+这里,留一个思考题,BigDecimal.valueOf()是调用Double.toString方法实现的,那么,既然double都是不精确的,BigDecimal.valueOf(0.1)怎么保证精确呢?
+
+### 总结
+
+因为计算机采用二进制处理数据,但是很多小数,如0.1的二进制是一个无线循环小数,而这种数字在计算机中是无法精确表示的。
+
+所以,人们采用了一种通过近似值的方式在计算机中表示,于是就有了单精度浮点数和双精度浮点数等。
+
+所以,作为单精度浮点数的float和双精度浮点数的double,在表示小数的时候只是近似值,并不是真实值。
+
+所以,当使用BigDecimal(Double)创建一个的时候,得到的BigDecimal是损失了精度的。
+
+而使用一个损失了精度的数字进行计算,得到的结果也是不精确的。
+
+想要避免这个问题,可以通过BigDecimal(String)的方式创建BigDecimal,这样的情况下,0.1就会被精确的表示出来。
+
+其表现形式是一个无标度数值1,和一个标度1的组合。
+
+ [1]: https://mp.weixin.qq.com/s/iiZW9xr1Xb2JIaRFnWLZUg
+ [2]: https://www.hollischuang.com/wp-content/uploads/2021/01/16119907257353.jpg
+ [3]: https://www.hollischuang.com/wp-content/uploads/2021/01/16119945021181.jpg
\ No newline at end of file
diff --git a/docs/menu.md b/docs/menu.md
index 5dab4bd8..2e781dd8 100644
--- a/docs/menu.md
+++ b/docs/menu.md
@@ -457,7 +457,7 @@ Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer)
* [为什么不能使用BigDecimal的equals比较大小](/basics/java-basic/stop-using-equlas-in-bigdecimal.md)
- * 为什么不能直接使用double创建一个BigDecimal
+ * [为什么不能直接使用double创建一个BigDecimal](/basics/java-basic/stop-create-bigdecimal-with-double.md)
* Java 8
From 964d050ef3d456af3509a02b87e3370baa892769 Mon Sep 17 00:00:00 2001
From: "hollis.zhl"
Date: Fri, 31 Dec 2021 10:43:15 +0800
Subject: [PATCH 74/84] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20decimal=20=E7=9F=A5?=
=?UTF-8?q?=E8=AF=86=E7=82=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/_sidebar.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index 62bc04f4..0ddb569f 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -400,7 +400,7 @@
* [为什么不能使用BigDecimal的equals比较大小](/basics/java-basic/stop-using-equlas-in-bigdecimal.md)
- * 为什么不能直接使用double创建一个BigDecimal
+ * [为什么不能直接使用double创建一个BigDecimal](/basics/java-basic/stop-create-bigdecimal-with-double.md)
* Java 8
From 5d640559c453b659cd1dacb225c228eeeba1c6d0 Mon Sep 17 00:00:00 2001
From: lowking
Date: Mon, 14 Feb 2022 13:15:19 +0800
Subject: [PATCH 75/84] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=B8=80=E4=BA=9B?=
=?UTF-8?q?=E9=94=99=E5=88=AB=E5=AD=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../design-patterns/abstract-factory-pattern.md | 2 +-
docs/advance/design-patterns/adapter-pattern.md | 6 +++---
docs/advance/design-patterns/strategy-pattern.md | 4 ++--
docs/basics/concurrent-coding/concurrent.md | 2 +-
.../basics/concurrent-coding/debug-in-multithread.md | 2 +-
docs/basics/java-basic/enum-class.md | 2 +-
docs/basics/java-basic/enum-singleton.md | 12 ++++++------
docs/basics/java-basic/final-string.md | 2 +-
docs/basics/java-basic/hash-in-hashmap.md | 2 +-
docs/basics/java-basic/hashmap-default-capacity.md | 2 +-
docs/basics/java-basic/length-of-string.md | 2 +-
docs/basics/object-oriented/java-pass-by.md | 2 +-
12 files changed, 20 insertions(+), 20 deletions(-)
diff --git a/docs/advance/design-patterns/abstract-factory-pattern.md b/docs/advance/design-patterns/abstract-factory-pattern.md
index cb0fad47..ab115aa1 100644
--- a/docs/advance/design-patterns/abstract-factory-pattern.md
+++ b/docs/advance/design-patterns/abstract-factory-pattern.md
@@ -36,7 +36,7 @@
>
> Product(具体产品):定义具体工厂生产的具体产品对象,实现抽象产品接口中定义的业务方法。
-本文的例子采用一个汽车代工厂造汽车的例子。假设我们是一家汽车代工厂商,我们负责给奔驰和特斯拉两家公司制造车子。我们简单的把奔驰车理解为需要加油的车,特斯拉为需要充电的车。其中奔驰车中包含跑车和商务车两种,特斯拉同样也包含奔驰车和商务车。
+本文的例子采用一个汽车代工厂造汽车的例子。假设我们是一家汽车代工厂商,我们负责给奔驰和特斯拉两家公司制造车子。我们简单的把奔驰车理解为需要加油的车,特斯拉为需要充电的车。其中奔驰车中包含跑车和商务车两种,特斯拉同样也包含跑车和商务车。
[
][6]
diff --git a/docs/advance/design-patterns/adapter-pattern.md b/docs/advance/design-patterns/adapter-pattern.md
index b5db9af9..49384606 100644
--- a/docs/advance/design-patterns/adapter-pattern.md
+++ b/docs/advance/design-patterns/adapter-pattern.md
@@ -125,7 +125,7 @@ GOF中将适配器模式分为类适配器模式和对象适配器模式。区
public void charge(){
System.out.println("开始给我的GalaxyS7手机充电...");
microUsbInterface.chargeWithMicroUsb();
- System.out.println("开始给我的GalaxyS7手机充电...");
+ System.out.println("结束给我的GalaxyS7手机充电...");
}
public MicroUsbInterface getMicroUsbInterface() {
@@ -203,14 +203,14 @@ GOF中将适配器模式分为类适配器模式和对象适配器模式。区
==============================
开始给我的GalaxyS7手机充电...
使用MicroUsb型号的充电器充电...
- 开始给我的GalaxyS7手机充电...
+ 结束给我的GalaxyS7手机充电...
==============================
开始给我的Iphone6Plus手机充电...
使用MicroUsb型号的充电器充电...
结束给我的Iphone6Plus手机充电...
-上面的例子通过适配器,把一个MicroUsb型号的充电器用来给Iphone充电。从代码层面,就是通过适配器复用了MicroUsb接口及其实现类。在很大程度上福永了已有的代码。
+上面的例子通过适配器,把一个MicroUsb型号的充电器用来给Iphone充电。从代码层面,就是通过适配器复用了MicroUsb接口及其实现类。在很大程度上复用了已有的代码。
## 优缺点
diff --git a/docs/advance/design-patterns/strategy-pattern.md b/docs/advance/design-patterns/strategy-pattern.md
index 29f26245..7ef203f8 100644
--- a/docs/advance/design-patterns/strategy-pattern.md
+++ b/docs/advance/design-patterns/strategy-pattern.md
@@ -88,7 +88,7 @@
@Override
public double calPrice(double bookPrice) {
- System.out.println("对于中级会员的折扣为20%");
+ System.out.println("对于高级会员的折扣为20%");
return bookPrice * 0.8;
}
}
@@ -149,7 +149,7 @@
}
}
- //对于中级会员的折扣为20%
+ //对于高级会员的折扣为20%
//高级会员图书的最终价格为:240.0
//对于中级会员的折扣为10%
//中级会员图书的最终价格为:270.0
diff --git a/docs/basics/concurrent-coding/concurrent.md b/docs/basics/concurrent-coding/concurrent.md
index 4f14ebbf..3685a1bb 100644
--- a/docs/basics/concurrent-coding/concurrent.md
+++ b/docs/basics/concurrent-coding/concurrent.md
@@ -1,6 +1,6 @@
并发(Concurrent),在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。
-那么,操作系统视如何实现这种并发的呢?
+那么,操作系统是如何实现这种并发的呢?
现在我们用到操作系统,无论是Windows、Linux还是MacOS等其实都是**多用户多任务分时操作系统**。使用这些操作系统的用户是可以“同时”干多件事的。
diff --git a/docs/basics/concurrent-coding/debug-in-multithread.md b/docs/basics/concurrent-coding/debug-in-multithread.md
index f51bbc67..056f6e19 100644
--- a/docs/basics/concurrent-coding/debug-in-multithread.md
+++ b/docs/basics/concurrent-coding/debug-in-multithread.md
@@ -2,7 +2,7 @@
但是我之前面试过很多人,很多人都知道多线程怎么实现,但是却不知道如何调试多线程的代码,这篇文章我们来介绍下如何调试多线程的代码。
-首先我们写一个多线程的例子,使用继承Runnable接口的方式定义多个线程,并启动执行。
+首先我们写一个多线程的例子,使用实现Runnable接口的方式定义多个线程,并启动执行。
/**
* @author Hollis
diff --git a/docs/basics/java-basic/enum-class.md b/docs/basics/java-basic/enum-class.md
index 58ad1bdb..159fe15a 100644
--- a/docs/basics/java-basic/enum-class.md
+++ b/docs/basics/java-basic/enum-class.md
@@ -11,4 +11,4 @@ Java中定义枚举是使用enum关键字的,但是Java中其实还有一个ja
这个类我们在日常开发中不会用到,但是其实我们使用enum定义的枚举,其实现方式就是通过继承Enum类实现的。
-当我们使用enmu来定义一个枚举类型的时候,编译器会自动帮我们创建一个final类型的类继承Enum类,所以枚举类型不能被继承。
\ No newline at end of file
+当我们使用enum来定义一个枚举类型的时候,编译器会自动帮我们创建一个final类型的类继承Enum类,所以枚举类型不能被继承。
\ No newline at end of file
diff --git a/docs/basics/java-basic/enum-singleton.md b/docs/basics/java-basic/enum-singleton.md
index 089bd57a..05164b23 100644
--- a/docs/basics/java-basic/enum-singleton.md
+++ b/docs/basics/java-basic/enum-singleton.md
@@ -39,14 +39,14 @@
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
- if (singleton == null) {
- synchronized (Singleton.class) {
if (singleton == null) {
- singleton = new Singleton();
+ synchronized (Singleton.class) {
+ if (singleton == null) {
+ singleton = new Singleton();
+ }
+ }
}
- }
- }
- return singleton;
+ return singleton;
}
}
diff --git a/docs/basics/java-basic/final-string.md b/docs/basics/java-basic/final-string.md
index a5763b6d..a2e2e05b 100644
--- a/docs/basics/java-basic/final-string.md
+++ b/docs/basics/java-basic/final-string.md
@@ -70,7 +70,7 @@ String s2 = s;
当我们在程序中传递一个字符串的时候,如果这个字符串的内容是不可变的,那么我们就可以相信这个字符串中的内容。
-但是,如果是可变的,那么这个字符串内容就可能随时都被修改。那么这个字符串内容就完全可信了。这样整个系统就没有安全性可言了。
+但是,如果是可变的,那么这个字符串内容就可能随时都被修改。那么这个字符串内容就完全不可信了。这样整个系统就没有安全性可言了。
#### 线程安全
diff --git a/docs/basics/java-basic/hash-in-hashmap.md b/docs/basics/java-basic/hash-in-hashmap.md
index ddb52259..4cc4f7e6 100644
--- a/docs/basics/java-basic/hash-in-hashmap.md
+++ b/docs/basics/java-basic/hash-in-hashmap.md
@@ -79,7 +79,7 @@
}
-前面我说过,`indexFor`方法其实主要是将hash生成的整型转换成链表数组中的下标。那么`return h & (length-1);`是什么意思呢?其实,他就是取模。Java之所有使用位运算(&)来代替取模运算(%),最主要的考虑就是效率。**位运算(&)效率要比代替取模运算(%)高很多,主要原因是位运算直接对内存数据进行操作,不需要转成十进制,因此处理速度非常快。**
+前面我说过,`indexFor`方法其实主要是将hash生成的整型转换成链表数组中的下标。那么`return h & (length-1);`是什么意思呢?其实,他就是取模。Java之所以使用位运算(&)来代替取模运算(%),最主要的考虑就是效率。**位运算(&)效率要比代替取模运算(%)高很多,主要原因是位运算直接对内存数据进行操作,不需要转成十进制,因此处理速度非常快。**
那么,为什么可以使用位运算(&)来实现取模运算(%)呢?这实现的原理如下:
diff --git a/docs/basics/java-basic/hashmap-default-capacity.md b/docs/basics/java-basic/hashmap-default-capacity.md
index cb561673..423d843f 100644
--- a/docs/basics/java-basic/hashmap-default-capacity.md
+++ b/docs/basics/java-basic/hashmap-default-capacity.md
@@ -194,7 +194,7 @@ Step 1 怎么理解呢?其实是对一个二进制数依次向右移位,然
### 扩容
-除了初始化的时候回指定HashMap的容量,在进行扩容的时候,其容量也可能会改变。
+除了初始化的时候会指定HashMap的容量,在进行扩容的时候,其容量也可能会改变。
HashMap有扩容机制,就是当达到扩容条件时会进行扩容。HashMap的扩容条件就是当HashMap中的元素个数(size)超过临界值(threshold)时就会自动扩容。
diff --git a/docs/basics/java-basic/length-of-string.md b/docs/basics/java-basic/length-of-string.md
index 7c3fcceb..6b9d37a3 100644
--- a/docs/basics/java-basic/length-of-string.md
+++ b/docs/basics/java-basic/length-of-string.md
@@ -134,4 +134,4 @@ int 是一个 32 位变量类型,取正数部分来算的话,他们最长可
在运行期,长度不能超过Int的范围,否则会抛异常。
-最后,这个知识点 ,我录制了视频(https://www.bilibili.com/video/BV1uK4y1t7H1/),其中有关于如何进行实验测试、如何查阅Java规范以及如何对javac进行deubg的技巧。欢迎进一步学习。
+最后,这个知识点 ,我录制了视频([点击跳转](https://www.bilibili.com/video/BV1uK4y1t7H1/)) ,其中有关于如何进行实验测试、如何查阅Java规范以及如何对javac进行deubg的技巧。欢迎进一步学习。
diff --git a/docs/basics/object-oriented/java-pass-by.md b/docs/basics/object-oriented/java-pass-by.md
index c05138bc..7d2abf93 100644
--- a/docs/basics/object-oriented/java-pass-by.md
+++ b/docs/basics/object-oriented/java-pass-by.md
@@ -56,7 +56,7 @@
* 传值调用(值传递)
* 在传值调用中,实际参数先被求值,然后其值通过复制,被传递给被调函数的形式参数。因为形式参数拿到的只是一个"局部拷贝",所以如果在被调函数中改变了形式参数的值,并不会改变实际参数的值。
-* 传引用调用(应用传递)
+* 传引用调用(引用传递)
* 在传引用调用中,传递给函数的是它的实际参数的隐式引用而不是实参的拷贝。因为传递的是引用,所以,如果在被调函数中改变了形式参数的值,改变对于调用者来说是可见的。
* 传共享对象调用(共享对象传递)
* 传共享对象调用中,先获取到实际参数的地址,然后将其复制,并把该地址的拷贝传递给被调函数的形式参数。因为参数的地址都指向同一个对象,所以我们称也之为"传共享对象",所以,如果在被调函数中改变了形式参数的值,调用者是可以看到这种变化的。
From 622e877351900db0ed27da7775fb7a07915f9e32 Mon Sep 17 00:00:00 2001
From: hollis
Date: Sun, 29 May 2022 13:52:49 +0800
Subject: [PATCH 76/84] update
---
README.md | 4 +
docs/README.md | 4 +
docs/_sidebar.md | 4 +-
docs/basics/concurrent-coding/thread-safe.md | 119 ++++++++++++++
.../concurrent-coding/why-not-executors.md | 153 ++++++++++++++++++
docs/menu.md | 4 +-
pics/book.jpeg | Bin 0 -> 477476 bytes
7 files changed, 284 insertions(+), 4 deletions(-)
create mode 100644 docs/basics/concurrent-coding/thread-safe.md
create mode 100644 docs/basics/concurrent-coding/why-not-executors.md
create mode 100644 pics/book.jpeg
diff --git a/README.md b/README.md
index 89b8bccd..a11f820c 100644
--- a/README.md
+++ b/README.md
@@ -2,9 +2,13 @@
  
+成神之路系列丛书的第一本《深入理解Java核心技术(基础篇)》已经正式出版了,这本书囊括了中基础篇的几乎全部内容,欢迎大家购买品鉴。
+
+
| 主要版本 | 更新时间 | 备注 |
| ---- | ---------- | -------------- |
+| v4.0 | 2022-05-20 | 知识体系完善,知识点补充|
| v3.0 | 2020-03-31 | 知识体系完善,在v2.0的基础上,新增20%左右的知识点
调整部分知识的顺序及结构,方便阅读和理解
通过GitHub Page搭建,便于阅读|
| v2.0 | 2019-02-19 | 结构调整,更适合从入门到精通;
进一步完善知识体系;
新技术补充;|
| v1.1 | 2018-03-12 | 增加新技术知识、完善知识体系 |
diff --git a/docs/README.md b/docs/README.md
index 57416c18..45ea4c4a 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -2,9 +2,13 @@
  
+成神之路系列丛书的第一本《深入理解Java核心技术(基础篇)》已经正式出版了,这本书囊括了中基础篇的几乎全部内容,欢迎大家购买品鉴。
+
+
| 主要版本 | 更新时间 | 备注 |
| ---- | ---------- | -------------- |
+| v4.0 | 2022-05-20 | 知识体系完善,知识点补充|
| v3.0 | 2020-03-31 | 知识体系完善,在v2.0的基础上,新增20%左右的知识点
调整部分知识的顺序及结构,方便阅读和理解
通过GitHub Page搭建,便于阅读|
| v2.0 | 2019-02-19 | 结构调整,更适合从入门到精通;
进一步完善知识体系;
新技术补充;|
| v1.1 | 2018-03-12 | 增加新技术知识、完善知识体系 |
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index 0ddb569f..f12297d8 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -478,11 +478,11 @@
* 线程池原理
- * 为什么不允许使用Executors创建线程池
+ * [为什么不允许使用Executors创建线程池](/basics/concurrent-coding/why-not-executors.md)
* 线程安全
- * 什么是线程安全
+ * [什么是线程安全](/basics/concurrent-coding/thread-safe.md)
* 多级缓存和一致性问题
diff --git a/docs/basics/concurrent-coding/thread-safe.md b/docs/basics/concurrent-coding/thread-safe.md
new file mode 100644
index 00000000..10374e58
--- /dev/null
+++ b/docs/basics/concurrent-coding/thread-safe.md
@@ -0,0 +1,119 @@
+# 什么是线程安全
+
+线程安全,维基百科中的解释是:
+
+> 线程安全是编程中的术语,指某个函数、函数库在**并发**环境中被调用时,能够正确地处理**多个线程**之间的**共享变量**,使程序功能正确完成。
+
+我们把这个定义拆解一下,我们需要弄清楚这么几点: 1、并发 2、多线程 3、共享变量
+
+# 并发
+
+提到线程安全,必须要提及的一个词那就是并发,如果没有并发的话,那么也就不存在线程安全问题了。
+
+## 什么是并发
+
+并发(Concurrent),在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。
+
+那么,操作系统视如何实现这种并发的呢?
+
+现在我们用到操作系统,无论是Windows、Linux还是MacOS等其实都是**多用户多任务分时操作系统**。使用这些操作系统的用户是可以“同时”干多件事的。
+
+但是实际上,对于单CPU的计算机来说,在CPU中,同一时间是只能干一件事儿的。为了看起来像是“同时干多件事”,分时操作系统是把CPU的时间划分成长短基本相同的时间区间,即”时间片”,通过操作系统的管理,把这些时间片依次轮流地分配给各个用户使用。
+
+如果某个作业在时间片结束之前,整个任务还没有完成,那么该作业就被暂停下来,放弃CPU,等待下一轮循环再继续做.此时CPU又分配给另一个作业去使用。
+
+由于计算机的处理速度很快,只要时间片的间隔取得适当,那么一个用户作业从用完分配给它的一个时间片到获得下一个CPU时间片,中间有所”停顿”,但用户察觉不出来,好像整个系统全由它”独占”似的。
+
+所以,在单CPU的计算机中,我们看起来“同时干多件事”,其实是通过CPU时间片技术,并发完成的。
+
+提到并发,还有另外一个词容易和他混淆,那就是并行。
+
+## 并发与并行之间的关系
+
+并行(Parallel),当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。
+
+Erlang 之父 Joe Armstrong 用一张比较形象的图解释了并发与并行的区别:
+
+
+
+并发是两个队伍交替使用一台咖啡机。并行是两个队伍同时使用两台咖啡机。
+
+映射到计算机系统中,上图中的咖啡机就是CPU,两个队伍指的就是两个进程。
+
+# 多线程
+
+## 进程和线程
+
+理解了并发和并行之间的关系和区别后,我们再回到前面介绍的多任务分时操作系统,看看CPU是如何进行进程调度的。
+
+为了看起来像是“同时干多件事”,分时操作系统是把CPU的时间划分成长短基本相同的”时间片”,通过操作系统的管理,把这些时间片依次轮流地分配给各个用户的各个任务使用。
+
+在多任务处理系统中,CPU需要处理所有程序的操作,当用户来回切换它们时,需要记录这些程序执行到哪里。在操作系统中,CPU切换到另一个进程需要保存当前进程的状态并恢复另一个进程的状态:当前运行任务转为就绪(或者挂起、删除)状态,另一个被选定的就绪任务成为当前任务。**上下文切换**就是这样一个过程,他允许CPU记录并恢复各种正在运行程序的状态,使它能够完成切换操作。
+
+> 在上下文切换过程中,CPU会停止处理当前运行的程序,并保存当前程序运行的具体位置以便之后继续运行。从这个角度来看,上下文切换有点像我们同时阅读几本书,在来回切换书本的同时我们需要记住每本书当前读到的页码。在程序中,上下文切换过程中的“页码”信息是保存在进程控制块(PCB)中的。PCB还经常被称作“切换帧”(switchframe)。“页码”信息会一直保存到CPU的内存中,直到他们被再次使用。
+
+对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。
+
+而在多个进程之间切换的时候,需要进行上下文切换。但是上下文切换势必会耗费一些资源。于是人们考虑,能不能在一个进程中增加一些“子任务”,这样减少上下文切换的成本。比如我们使用Word的时候,它可以同时进行打字、拼写检查、字数统计等,这些子任务之间共用同一个进程资源,但是他们之间的切换不需要进行上下文切换。
+
+在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。
+
+随着时间的慢慢发展,人们进一步的切分了进程和线程之间的职责。**把进程当做资源分配的基本单元,把线程当做执行的基本单元,同一个进程的多个线程之间共享资源**
+
+拿我们比较熟悉的Java语言来说,Java程序是运行在JVM上面的,每一个JVM其实就是一个进程。所有的资源分配都是基于JVM进程来的。而在这个JVM进程中,又可以创建出很多线程,多个线程之间共享JVM资源,并且多个线程可以并发执行。
+
+# 共享变量
+
+所谓共享变量,指的是多个线程都可以操作的变量。
+
+前面我们提到过,进程视分配资源的基本单位,线程是执行的基本单位。所以,多个线程之间是可以共享一部分进程中的数据的。在JVM中,Java堆和方法区的区域是多个线程共享的数据区域。也就是说,多个线程可以操作保存在堆或者方法区中的同一个数据。那么,换句话说,保存在堆和方法区中的变量就是Java中的共享变量。
+
+那么,Java中哪些变量是存放在堆中,哪些变量是存放在方法区中,又有哪些变量是存放在栈中的呢?
+
+## 类变量、成员变量和局部变量
+
+Java中共有三种变量,分别是类变量、成员变量和局部变量。他们分别存放在JVM的方法区、堆内存和栈内存中。
+
+ /**
+ * @author Hollis
+ */
+ public class Variables {
+
+ /**
+ * 类变量
+ */
+ private static int a;
+
+ /**
+ * 成员变量
+ */
+ private int b;
+
+ /**
+ * 局部变量
+ * @param c
+ */
+ public void test(int c){
+ int d;
+ }
+ }
+
+
+上面定义的三个变量中,变量a就是类变量,变量b就是成员变量,而变量c和d是局部变量。
+
+所以,变量a和b是共享变量,变量c和d是非共享变量。所以如果遇到多线程场景,对于变量a和b的操作是需要考虑线程安全的,而对于线程c和d的操作是不需要考虑线程安全的。
+
+# 小结
+
+在了解了一些基础知识以后,我们再来回过头看看线程安全的定义:
+
+> 线程安全是编程中的术语,指某个函数、函数库在**并发**环境中被调用时,能够正确地处理**多个线程**之间的**共享变量**,使程序功能正确完成。
+
+现在我们知道了什么是并发环境,什么是多个线程以及什么是共享变量。那么只要我们在编写多线程的代码的时候注意一下,保证程序功能可以正确的执行就行了。
+
+那么问题来了,定义中说线程安全能够**正确地处理**多个线程之间的共享变量,使程序功能**正确完成**。
+
+多线程场景中存在哪些问题会导致无法正确的处理共享变量? 多线程场景中存在哪些问题会导致程序无法正确完成? 如何解决多线程场景中影响『正确』的这些问题? 解决这些问题的各个手段的实现原理又是什么?
+
+ [1]: http://www.hollischuang.com/archives/3029
+ [2]: http://www.hollischuang.com/archives/tag/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3java%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B
\ No newline at end of file
diff --git a/docs/basics/concurrent-coding/why-not-executors.md b/docs/basics/concurrent-coding/why-not-executors.md
new file mode 100644
index 00000000..9460028a
--- /dev/null
+++ b/docs/basics/concurrent-coding/why-not-executors.md
@@ -0,0 +1,153 @@
+在《[深入源码分析Java线程池的实现原理][1]》这篇文章中,我们介绍过了Java中线程池的常见用法以及基本原理。
+
+在文中有这样一段描述:
+
+> 可以通过Executors静态工厂构建线程池,但一般不建议这样使用。
+
+关于这个问题,在那篇文章中并没有深入的展开。作者之所以这么说,是因为这种创建线程池的方式有很大的隐患,稍有不慎就有可能导致线上故障,如:一次Java线程池误用引发的血案和总结( )
+
+本文我们就来围绕这个问题来分析一下为什么JDK自身提供的构建线程池的方式并不建议使用?到底应该如何创建一个线程池呢?
+
+### Executors
+
+Executors 是一个Java中的工具类。提供工厂方法来创建不同类型的线程池。
+
+![][2]
+
+从上图中也可以看出,Executors的创建线程池的方法,创建出来的线程池都实现了ExecutorService接口。常用方法有以下几个:
+
+`newFiexedThreadPool(int Threads)`:创建固定数目线程的线程池。
+
+`newCachedThreadPool()`:创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果没有可用的线程,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
+
+`newSingleThreadExecutor()`创建一个单线程化的Executor。
+
+`newScheduledThreadPool(int corePoolSize)`创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
+
+类看起来功能还是比较强大的,又用到了工厂模式、又有比较强的扩展性,重要的是用起来还比较方便,如:
+
+ExecutorService executor = Executors.newFixedThreadPool(nThreads) ;
+
+
+即可创建一个固定大小的线程池。
+
+但是为什么我说不建议大家使用这个类来创建线程池呢?
+
+我提到的是『不建议』,但是在阿里巴巴Java开发手册中也明确指出,而且用的词是『不允许』使用Executors创建线程池。
+

+
+### Executors存在什么问题
+
+在阿里巴巴Java开发手册中提到,使用Executors创建线程池可能会导致OOM(OutOfMemory ,内存溢出),但是并没有说明为什么,那么接下来我们就来看一下到底为什么不允许使用Executors?
+
+我们先来一个简单的例子,模拟一下使用Executors导致OOM的情况。
+
+/**
+ * @author Hollis
+ */
+public class ExecutorsDemo {
+ private static ExecutorService executor = Executors.newFixedThreadPool(15);
+ public static void main(String[] args) {
+ for (int i = 0; i < Integer.MAX_VALUE; i++) {
+ executor.execute(new SubThread());
+ }
+ }
+}
+
+class SubThread implements Runnable {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(10000);
+ } catch (InterruptedException e) {
+ //do nothing
+ }
+ }
+}
+
+
+通过指定JVM参数:`-Xmx8m -Xms8m` 运行以上代码,会抛出OOM:
+
+Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
+ at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
+ at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
+ at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)
+
+
+以上代码指出,`ExecutorsDemo.java`的第16行,就是代码中的`executor.execute(new SubThread());`。
+
+### Executors为什么存在缺陷
+
+通过上面的例子,我们知道了`Executors`创建的线程池存在OOM的风险,那么到底是什么原因导致的呢?我们需要深入`Executors`的源码来分析一下。
+
+其实,在上面的报错信息中,我们是可以看出蛛丝马迹的,在以上的代码中其实已经说了,真正的导致OOM的其实是`LinkedBlockingQueue.offer`方法。
+
+Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
+ at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
+ at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
+ at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)
+
+
+如果读者翻看代码的话,也可以发现,其实底层确实是通过`LinkedBlockingQueue`实现的:
+
+public static ExecutorService newFixedThreadPool(int nThreads) {
+ return new ThreadPoolExecutor(nThreads, nThreads,
+ 0L, TimeUnit.MILLISECONDS,
+ new LinkedBlockingQueue<Runnable>());
+
+
+如果读者对Java中的阻塞队列有所了解的话,看到这里或许就能够明白原因了。
+
+Java中的`BlockingQueue`主要有两种实现,分别是`ArrayBlockingQueue` 和 `LinkedBlockingQueue`。
+
+`ArrayBlockingQueue`是一个用数组实现的有界阻塞队列,必须设置容量。
+
+`LinkedBlockingQueue`是一个用链表实现的有界阻塞队列,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为`Integer.MAX_VALUE`。
+
+这里的问题就出在:**不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE。**也就是说,如果我们不设置`LinkedBlockingQueue`的容量的话,其默认容量将会是`Integer.MAX_VALUE`。
+
+而`newFixedThreadPool`中创建`LinkedBlockingQueue`时,并未指定容量。此时,`LinkedBlockingQueue`就是一个无边界队列,对于一个无边界队列来说,是可以不断的向队列中加入任务的,这种情况下就有可能因为任务过多而导致内存溢出问题。
+
+上面提到的问题主要体现在`newFixedThreadPool`和`newSingleThreadExecutor`两个工厂方法上,并不是说`newCachedThreadPool`和`newScheduledThreadPool`这两个方法就安全了,这两种方式创建的最大线程数可能是`Integer.MAX_VALUE`,而创建这么多线程,必然就有可能导致OOM。
+
+### 创建线程池的正确姿势
+
+避免使用Executors创建线程池,主要是避免使用其中的默认实现,那么我们可以自己直接调用`ThreadPoolExecutor`的构造函数来自己创建线程池。在创建的同时,给`BlockQueue`指定容量就可以了。
+
+private static ExecutorService executor = new ThreadPoolExecutor(10, 10,
+ 60L, TimeUnit.SECONDS,
+ new ArrayBlockingQueue(10));
+
+
+这种情况下,一旦提交的线程数超过当前可用线程数时,就会抛出`java.util.concurrent.RejectedExecutionException`,这是因为当前线程池使用的队列是有边界队列,队列已经满了便无法继续处理新的请求。但是异常(Exception)总比发生错误(Error)要好。
+
+除了自己定义`ThreadPoolExecutor`外。还有其他方法。这个时候第一时间就应该想到开源类库,如apache和guava等。
+
+作者推荐使用guava提供的ThreadFactoryBuilder来创建线程池。
+
+public class ExecutorsDemo {
+
+ private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
+ .setNameFormat("demo-pool-%d").build();
+
+ private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
+ 0L, TimeUnit.MILLISECONDS,
+ new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
+
+ public static void main(String[] args) {
+
+ for (int i = 0; i < Integer.MAX_VALUE; i++) {
+ pool.execute(new SubThread());
+ }
+ }
+}
+
+
+通过上述方式创建线程时,不仅可以避免OOM的问题,还可以自定义线程名称,更加方便的出错的时候溯源。
+
+思考题,文中作者说:发生异常(Exception)要比发生错误(Error)好,为什么这么说?
+
+文中提到的《阿里巴巴Java开发手册》,请关注公众号Hollis,回复:手册。即可获得完整版PDF。
+
+ [1]: https://mp.weixin.qq.com/s/-89-CcDnSLBYy3THmcLEdQ
+ [2]: http://www.hollischuang.com/wp-content/uploads/2018/10/15406248096737.jpg
\ No newline at end of file
diff --git a/docs/menu.md b/docs/menu.md
index 2e781dd8..a0914b2b 100644
--- a/docs/menu.md
+++ b/docs/menu.md
@@ -535,11 +535,11 @@ Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer)
* 线程池原理
- * 为什么不允许使用Executors创建线程池
+ * [为什么不允许使用Executors创建线程池](/basics/concurrent-coding/why-not-executors.md)
* 线程安全
- * 什么是线程安全
+ * [什么是线程安全](/basics/concurrent-coding/thread-safe.md)
* 多级缓存和一致性问题
diff --git a/pics/book.jpeg b/pics/book.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..73a855ec30d47833695ba3fcac2fe8169972d291
GIT binary patch
literal 477476
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
z
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