diff --git a/README.md b/README.md
index ad23d2a4b3f5d05a300ac904fdad1f795694efda..a6c76d80a55d7ac26e952e74a2def4e62033c37b 100644
--- a/README.md
+++ b/README.md
@@ -15,16 +15,10 @@
-
-
-
-
# 前言
同学们好,我是二哥呀,欢迎来到《教妹学 Java》专栏。
-[我妹今年上大学了](https://mp.weixin.qq.com/s/bsu9uH8VKh5Vtue-9SafwQ),学的计算机编程,立志像我一样做一名正儿八经的 Java 程序员。我期初是反抗的,因为程序员这行业容易掉头发,作为一名需要美貌的女生,长发飘飘是必须的啊。但与其反抗,不如做点更积极的事情,比如说写点有趣的文章,教她更快地掌握 Java 这门编程语言,于是就有了这个专栏。
-
![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/tech-sister-01.png)
@@ -59,12 +53,31 @@
- [代码初始化块](docs/object-class/code-init.md)
- [final 关键字](docs/object-class/java-final.md)
- [instanceof](docs/object-class/java-instanceof.md)
+- [抽象类](docs/object-class/java-abstract.md)
+- [接口](docs/object-class/java-interface.md)
+- [值传递与引用传递](docs/object-class/pass-by-value.md)
+- [浅拷贝与深拷贝](docs/object-class/deep-copy.md)
+- [自动拆箱与装箱](docs/object-class/box.md)
+
+## **数组**
+
+- [数组概览](docs/array/gailan.md)
+- [Arrays](docs/array/arrays.md)
+- [打印数组](docs/array/print.md)
+
+## **字符串**
+
+- [字符串源码分析](docs/string/source.md)
+- [字符串常量池](docs/string/constant-pool.md)
+- [intern](docs/string/intern.md)
+
+## **字符串**
# 公众号
如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号“**沉默王二**”。
-**《教妹学Java》:** 本文档的离线版 PDF 版可以扫描下方的二维码关注我的[公众号](#公众号)后回复 **" 03"** 关键字即可领取!
+**《教妹学Java》:** 本文档的离线版 PDF 版可以扫描下方的二维码关注我的[公众号](#公众号)后回复 **"03"** 关键字即可领取!
@@ -73,7 +86,7 @@
# Donate
-开源不易,如果《教妹学 Java》专栏对你有些帮助,可以请作者喝杯咖啡,让他继续肝!
+开源不易,如果《教妹学 Java》专栏对你有些帮助,可以请二哥喝杯咖啡,让他继续肝!
diff --git a/docs/array/arrays.md b/docs/array/arrays.md
new file mode 100644
index 0000000000000000000000000000000000000000..3f924e3f0ece6a12824fb36bd88425899e53bcc5
--- /dev/null
+++ b/docs/array/arrays.md
@@ -0,0 +1,416 @@
+## 数组专用工具类
+
+“哥,数组专用工具类是专门用来操作数组的吗?比如说创建数组、数组排序、数组检索等等。”三妹的提问其实已经把答案说了出来。
+
+“是滴,这里说的数组专用工具类指的是 `java.util.Arrays` 类,基本上常见的数组操作,这个类都提供了静态方法可供直接调用。毕竟数组本身想完成这些操作还是挺麻烦的,有了这层封装,就方便多了。”在回答三妹的同时,我打开 Intellij IDEA,找到了 Arrays 类的源码。
+
+```java
+package java.util;
+/**
+ * @author Josh Bloch
+ * @author Neal Gafter
+ * @author John Rose
+ * @since 1.2
+ */
+public class Arrays {}
+```
+
+“具体来说,数组操作可分为以下 9 种。”
+
+- 创建数组
+- 比较数组
+- 数组排序
+- 数组检索
+- 数组转流
+- 打印数组
+- 数组转 List
+- setAll(没想好中文名)
+- parallelPrefix(没想好中文名)
+
+“我们来一个一个学习。”
+
+### 01、创建数组
+
+使用 Arrays 类创建数组可以通过以下三个方法:
+
+- copyOf,复制指定的数组,截取或用 null 填充
+- copyOfRange,复制指定范围内的数组到一个新的数组
+- fill,对数组进行填充
+
+1)copyOf,直接来看例子:
+
+```java
+String[] intro = new String[] { "沉", "默", "王", "二" };
+String[] revised = Arrays.copyOf(intro, 3);
+String[] expanded = Arrays.copyOf(intro, 5);
+System.out.println(Arrays.toString(revised));
+System.out.println(Arrays.toString(expanded));
+```
+
+revised 和 expanded 是复制后的新数组,长度分别是 3 和 5,指定的数组长度是 4。来看一下输出结果:
+
+```
+[沉, 默, 王]
+[沉, 默, 王, 二, null]
+```
+
+看到没?revised 截取了最后一位,因为长度是 3 嘛;expanded 用 null 填充了一位,因为长度是 5。
+
+ArrayList(内部的数据结构用的就是数组)源码中的 `grow()` 方法就调用了 `copyOf()` 方法:当 ArrayList 初始大小不满足元素的增长时就会扩容。
+
+```java
+private Object[] grow(int minCapacity) {
+ return elementData = Arrays.copyOf(elementData,
+ newCapacity(minCapacity));
+}
+```
+
+2)copyOfRange,直接来看例子:
+
+```java
+String[] intro = new String[] { "沉", "默", "王", "二" };
+String[] abridgement = Arrays.copyOfRange(intro, 0, 3);
+System.out.println(Arrays.toString(abridgement));
+```
+
+`copyOfRange()` 方法需要三个参数,第一个是指定的数组,第二个是起始位置(包含),第三个是截止位置(不包含)。来看一下输出结果:
+
+```java
+[沉, 默, 王]
+```
+
+0 的位置是“沉”,3 的位置是“二”,也就是说截取了从 0 位(包含)到 3 位(不包含)的数组元素。那假如说下标超出了数组的长度,会发生什么呢?
+
+```java
+String[] abridgementExpanded = Arrays.copyOfRange(intro, 0, 6);
+System.out.println(Arrays.toString(abridgementExpanded));
+```
+
+结束位置此时为 6,超出了指定数组的长度 4,来看一下输出结果:
+
+```
+[沉, 默, 王, 二, null, null]
+```
+
+仍然使用了 null 进行填充。
+
+“为什么要这么做呢?”经过这段时间的学习,三妹的眼光越来越毒辣了,问的问题都恰到好处。
+
+“嗯,我想是 Arrays 的设计者考虑到了数组越界的问题,不然每次调用 Arrays 类就要先判断很多次长度,很麻烦。”稍作思考后,我给出了这样一个回答。
+
+
+3)fill,直接来看例子:
+
+```java
+String[] stutter = new String[4];
+Arrays.fill(stutter, "沉默王二");
+System.out.println(Arrays.toString(stutter));
+```
+
+使用 new 关键字创建了一个长度为 4 的数组,然后使用 `fill()` 方法将 4 个位置填充为“沉默王二”,来看一下输出结果:
+
+```
+[沉默王二, 沉默王二, 沉默王二, 沉默王二]
+```
+
+如果想要一个元素完全相同的数组时, `fill()` 方法就派上用场了。
+
+### 02、比较数组
+
+Arrays 类的 `equals()` 方法用来判断两个数组是否相等,来看下面这个例子:
+
+```java
+String[] intro = new String[] { "沉", "默", "王", "二" };
+boolean result = Arrays.equals(new String[] { "沉", "默", "王", "二" }, intro);
+System.out.println(result);
+boolean result1 = Arrays.equals(new String[] { "沉", "默", "王", "三" }, intro);
+System.out.println(result1);
+```
+
+输出结果如下所示:
+
+```
+true
+false
+```
+
+指定的数组为沉默王二四个字,比较的数组一个是沉默王二,一个是沉默王三,所以 result 为 true,result1 为 false。
+
+简单看一下 `equals()` 方法的源码:
+
+```java
+public static boolean equals(Object[] a, Object[] a2) {
+ if (a==a2)
+ return true;
+ if (a==null || a2==null)
+ return false;
+
+ int length = a.length;
+ if (a2.length != length)
+ return false;
+
+ for (int i=0; i fence(1)
+ at java.base/java.util.Spliterators.checkFromToBounds(Spliterators.java:387)
+```
+
+### 06、打印数组
+
+因为数组是一个对象,直接 `System.out.println` 的话,结果是这样的:
+
+```
+[Ljava.lang.String;@3d075dc0
+```
+
+最优雅的打印方式,是使用 `Arrays.toString()`,来看一下该方法的源码:
+
+```java
+public static String toString(Object[] a) {
+ if (a == null)
+ return "null";
+
+ int iMax = a.length - 1;
+ if (iMax == -1)
+ return "[]";
+
+ StringBuilder b = new StringBuilder();
+ b.append('[');
+ for (int i = 0; ; i++) {
+ b.append(String.valueOf(a[i]));
+ if (i == iMax)
+ return b.append(']').toString();
+ b.append(", ");
+ }
+}
+```
+
+- 先判断 null,是的话,直接返回“null”字符串;
+- 获取数组的长度,如果数组的长度为 0( 等价于 length - 1 为 -1),返回中括号“[]”,表示数组为空的;
+- 如果数组既不是 null,长度也不为 0,就声明 StringBuilder 对象,然后添加一个数组的开始标记“[”,之后再遍历数组,把每个元素添加进去;其中一个小技巧就是,当遇到末尾元素的时候(i == iMax),不再添加逗号和空格“, ”,而是添加数组的闭合标记“]”。
+
+“哥,我能不能问一个问题呀?”
+
+“你问啊。”
+
+“就是为什么判断数组长度为 0 的时候判断的是减 1 后比较 -1 呢?为什么不直接比较 0 呢?”
+
+“呀,你这个问题问的很妙啊!”我想到三妹说一句“respect”,很强!“其实是和遍历数组的时候判断 `i == iMax` 有关了,否则这里就要用 `i == iMax -1` 来判断是否到达数组的最后一个元素了。”
+
+“哦----------”三妹似乎明白了什么。
+
+### 07、数组转 List
+
+尽管数组非常强大,但它自身可以操作的工具方法很少,比如说判断数组中是否包含某个值。如果能转成 List 的话,就简便多了,因为 Java 的集合框架 List 中封装了很多常用的方法。
+
+```java
+String[] intro = new String[] { "沉", "默", "王", "二" };
+List rets = Arrays.asList(intro);
+System.out.println(rets.contains("二"));
+```
+
+不过需要注意的是,`Arrays.asList()` 返回的是 `java.util.Arrays.ArrayList`,并不是 `java.util.ArrayList`,它的长度是固定的,无法进行元素的删除或者添加。
+
+```java
+rets.add("三");
+rets.remove("二");
+```
+
+这个在编码的时候一定要注意,否则在执行这两个方法的时候,会抛出异常:
+
+```
+Exception in thread "main" java.lang.UnsupportedOperationException
+ at java.base/java.util.AbstractList.add(AbstractList.java:153)
+ at java.base/java.util.AbstractList.add(AbstractList.java:111)
+```
+
+要想操作元素的话,需要多一步转化,转成真正的 `java.util.ArrayList`:
+
+```java
+List rets1 = new ArrayList<>(Arrays.asList(intro));
+rets1.add("三");
+rets1.remove("二");
+```
+
+### 08、setAll
+
+Java 8 新增了 `setAll()` 方法,它提供了一个函数式编程的入口,可以对数组的元素进行填充:
+
+```java
+int[] array = new int[10];
+Arrays.setAll(array, i -> i * 10);
+System.out.println(Arrays.toString(array));
+```
+
+“这段代码什么意思呢?”三妹问。
+
+i 就相当于是数组的下标,值从 0 开始,到 9 结束,那么 `i * 10` 就意味着值从 0 * 10 开始,到 9 * 10 结束,来看一下输出结果:
+
+```
+[0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
+```
+
+可以用来为新数组填充基于原来数组的新元素。
+
+### 09、parallelPrefix
+
+`parallelPrefix()` 方法和 `setAll()` 方法一样,也是 Java 8 之后提供的,提供了一个函数式编程的入口,通过遍历数组中的元素,将当前下标位置上的元素与它之前下标的元素进行操作,然后将操作后的结果覆盖当前下标位置上的元素。
+
+```java
+int[] arr = new int[] { 1, 2, 3, 4};
+Arrays.parallelPrefix(arr, (left, right) -> left + right);
+System.out.println(Arrays.toString(arr));
+```
+
+上面代码中有一个 Lambda 表达式(`(left, right) -> left + right`),是什么意思呢?上面这段代码等同于:
+
+```java
+int[] arr = new int[]{1, 2, 3, 4};
+Arrays.parallelPrefix(arr, (left, right) -> {
+ System.out.println(left + "," + right);
+ return left + right;
+});
+System.out.println(Arrays.toString(arr));
+```
+
+来看一下输出结果就明白了:
+
+```
+1,2
+3,3
+6,4
+[1, 3, 6, 10]
+```
+
+也就是说, Lambda 表达式执行了三次:
+
+- 第一次是 1 和 2 相加,结果是 3,替换下标为 1 的位置
+- 第二次是 3 和 3 相加,结果是 6,也就是第一次的结果和下标为 2 的元素相加的结果
+- 第三次是 6 和 4 相加,结果是 10,也就是第二次的结果和下标为 3 的元素相加的结果
+
+
+### 10、总结
+
+“好了,三妹,就先学到这吧。如果你以后翻 Java 源码的时候,只要是用到数组的,尤其是 ArrayList 类,就可以看到 Arrays 类的很多影子。”
+
+“嗯嗯,我先复习一下这节的内容。哥,你去休息吧。”
+
+我来到客厅,坐到沙发上,捧起黄永玉先生的《无愁河上的浪荡汉子·八年卷 1》看了起来,津津有味。。。。。。
\ No newline at end of file
diff --git a/docs/array/gailan.md b/docs/array/gailan.md
new file mode 100644
index 0000000000000000000000000000000000000000..1dd98c4bcbdcfb4a0097ac6210419953d6ff8030
--- /dev/null
+++ b/docs/array/gailan.md
@@ -0,0 +1,246 @@
+## 数组概览
+
+“哥,我看你之前的文章里提到,ArrayList 的内部是用数组实现的,我就对数组非常感兴趣,想深入地了解一下,今天终于到这个环节了,好期待呀!”三妹的语气里显得很兴奋。
+
+“的确是的,看 ArrayList 的源码就一清二楚了。”我一边说,一边打开 Intellij IDEA,并找到了 ArrayList 的源码。
+
+```java
+/**
+ * The array buffer into which the elements of the ArrayList are stored.
+ * The capacity of the ArrayList is the length of this array buffer. Any
+ * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
+ * will be expanded to DEFAULT_CAPACITY when the first element is added.
+ */
+transient Object[] elementData; // non-private to simplify nested class access
+
+/**
+ * The size of the ArrayList (the number of elements it contains).
+ *
+ * @serial
+ */
+private int size;
+```
+
+“瞧见没?`Object[] elementData` 就是数组。”我指着显示屏上这串代码继续说。
+
+数组是一个对象,它包含了一组固定数量的元素,并且这些元素的类型是相同的。数组会按照索引的方式将元素放在指定的位置上,意味着我们可以通过索引来访问这些元素。在 Java 中,索引是从 0 开始的。
+
+“哥,能说一下为什么索引从 0 开始吗?”三妹突然这个话题很感兴趣。
+
+“哦,Java 是基于 C/C++ 语言实现的,而 C 语言的下标是从 0 开始的,所以 Java 就继承了这个良好的传统习惯。C语言有一个很重要概念,叫做指针,它实际上是一个偏移量,距离开始位置的偏移量,第一个元素就在开始的位置,它的偏移量就为 0,所以索引就为 0。”此刻,我很自信。
+
+“此外,还有另外一种说法。早期的计算机资源比较匮乏,0 作为起始下标相比较于 1 作为起始下标,编译的效率更高。”
+
+“哦。”三妹意味深长地点了点头。
+
+我们可以将数组理解为一个个整齐排列的单元格,每个单元格里面存放着一个元素。
+
+数组元素的类型可以是基本数据类型(比如说 int、double),也可以是引用数据类型(比如说 String),包括自定义类型。
+
+数组的声明方式分两种。
+
+先来看第一种:
+
+```java
+int[] anArray;
+```
+
+再来看第二种:
+
+```java
+int anOtherArray[];
+```
+
+不同之处就在于中括号的位置,是跟在类型关键字的后面,还是跟在变量的名称的后面。前一种的使用频率更高一些,像 ArrayList 的源码中就用了第一种方式。
+
+同样的,数组的初始化方式也有多种,最常见的是:
+
+```java
+int[] anArray = new int[10];
+```
+
+看到了没?上面这行代码中使用了 new 关键字,这就意味着数组的确是一个对象,只有对象的创建才会用到 new 关键字,基本数据类型是不用的。然后,我们需要在方括号中指定数组的长度。
+
+这时候,数组中的每个元素都会被初始化为默认值,int 类型的就为 0,Object 类型的就为 null。 不同数据类型的默认值不同,可以参照[之前的文章](https://mp.weixin.qq.com/s/twim3w_dp5ctCigjLGIbFw)。
+
+另外,还可以使用大括号的方式,直接初始化数组中的元素:
+
+```java
+int anOtherArray[] = new int[] {1, 2, 3, 4, 5};
+```
+
+这时候,数组的元素分别是 1、2、3、4、5,索引依次是 0、1、2、3、4,长度是 5。
+
+“哥,怎么访问数组呢?”三妹及时地插话到。
+
+前面提到过,可以通过索引来访问数组的元素,就像下面这样:
+
+```java
+anArray[0] = 10;
+```
+
+变量名,加上中括号,加上元素的索引,就可以访问到数组,通过“=”操作符可以对元素进行赋值。
+
+如果索引的值超出了数组的界限,就会抛出 `ArrayIndexOutOfBoundException`。
+
+既然数组的索引是从 0 开始,那就是到数组的 `length - 1` 结束,不要使用超出这个范围内的索引访问数组,就不会抛出数组越界的异常了。
+
+当数组的元素非常多的时候,逐个访问数组就太辛苦了,所以需要通过遍历的方式。
+
+第一种,使用 for 循环:
+
+```java
+int anOtherArray[] = new int[] {1, 2, 3, 4, 5};
+for (int i = 0; i < anOtherArray.length; i++) {
+ System.out.println(anOtherArray[i]);
+}
+```
+
+通过 length 属性获取到数组的长度,然后从 0 开始遍历,就得到了数组的所有元素。
+
+第二种,使用 for-each 循环:
+
+```java
+for (int element : anOtherArray) {
+ System.out.println(element);
+}
+```
+
+如果不需要关心索引的话(意味着不需要修改数组的某个元素),使用 for-each 遍历更简洁一些。当然,也可以使用 while 和 do-while 循环。
+
+在 Java 中,可变参数用于将任意数量的参数传递给方法,来看 `varargsMethod()` 方法:
+
+```java
+void varargsMethod(String... varargs) {}
+```
+
+该方法可以接收任意数量的字符串参数,可以是 0 个或者 N 个,本质上,可变参数就是通过数组实现的。为了证明这一点,我们可以看一下反编译一后的字节码:
+
+```java
+public class VarargsDemo
+{
+
+ public VarargsDemo()
+ {
+ }
+
+ transient void varargsMethod(String as[])
+ {
+ }
+}
+```
+
+所以,我们其实可以直接将数组作为参数传递给该方法:
+
+```java
+VarargsDemo demo = new VarargsDemo();
+String[] anArray = new String[] {"沉默王二", "一枚有趣的程序员"};
+demo.varargsMethod(anArray);
+```
+
+也可以直接传递多个字符串,通过逗号隔开的方式:
+
+```java
+demo.varargsMethod("沉默王二", "一枚有趣的程序员");
+```
+
+在 Java 中,数组与 List 关系非常密切。List 封装了很多常用的方法,方便我们对集合进行一些操作,而如果直接操作数组的话,有很多不便,因为数组本身没有提供这些封装好的操作,所以有时候我们需要把数组转成 List。
+
+“怎么转呢?”三妹问到。
+
+最原始的方式,就是通过遍历数组的方式,一个个将数组添加到 List 中。
+
+```java
+int[] anArray = new int[] {1, 2, 3, 4, 5};
+
+List aList = new ArrayList<>();
+for (int element : anArray) {
+ aList.add(element);
+}
+```
+
+更优雅的方式是通过 Arrays 类的 `asList()` 方法:
+
+```java
+List aList = Arrays.asList(anArray);
+```
+
+但需要注意的是,该方法返回的 ArrayList 并不是 `java.util.ArrayList`,它其实是 Arrays 类的一个内部类:
+
+```java
+private static class ArrayList extends AbstractList
+ implements RandomAccess, java.io.Serializable{}
+```
+
+如果需要添加元素或者删除元素的话,需要把它转成 `java.util.ArrayList`。
+
+```java
+new ArrayList<>(Arrays.asList(anArray));
+```
+
+Java 8 新增了 Stream 流的概念,这就意味着我们也可以将数组转成 Stream 进行操作。
+
+```java
+String[] anArray = new String[] {"沉默王二", "一枚有趣的程序员", "好好珍重他"};
+Stream aStream = Arrays.stream(anArray);
+```
+
+
+如果想对数组进行排序的话,可以使用 Arrays 类提供的 `sort()` 方法。
+
+- 基本数据类型按照升序排列
+- 实现了 Comparable 接口的对象按照 `compareTo()` 的排序
+
+来看第一个例子:
+
+```java
+int[] anArray = new int[] {5, 2, 1, 4, 8};
+Arrays.sort(anArray);
+```
+
+排序后的结果如下所示:
+
+```java
+[1, 2, 4, 5, 8]
+```
+
+来看第二个例子:
+
+```java
+String[] yetAnotherArray = new String[] {"A", "E", "Z", "B", "C"};
+Arrays.sort(yetAnotherArray, 1, 3,
+ Comparator.comparing(String::toString).reversed());
+```
+
+只对 1-3 位置上的元素进行反序,所以结果如下所示:
+
+```
+[A, Z, E, B, C]
+```
+
+有时候,我们需要从数组中查找某个具体的元素,最直接的方式就是通过遍历的方式:
+
+```java
+int[] anArray = new int[] {5, 2, 1, 4, 8};
+for (int i = 0; i < anArray.length; i++) {
+ if (anArray[i] == 4) {
+ System.out.println("找到了 " + i);
+ break;
+ }
+}
+```
+
+上例中从数组中查询元素 4,找到后通过 break 关键字退出循环。
+
+如果数组提前进行了排序,就可以使用二分查找法,这样效率就会更高一些。`Arrays.binarySearch()` 方法可供我们使用,它需要传递一个数组,和要查找的元素。
+
+```java
+int[] anArray = new int[] {1, 2, 3, 4, 5};
+int index = Arrays.binarySearch(anArray, 4);
+```
+
+“除了一维数组,还有二维数组,三妹你可以去研究下,比如说用二维数组打印一下杨辉三角。”说完,我就去阳台上休息了,留三妹在那里学习,不能打扰她。
+
+-------
+
+**点赞越多,更新的动力越足哟,疯狂暗示**~
\ No newline at end of file
diff --git a/docs/array/print.md b/docs/array/print.md
new file mode 100644
index 0000000000000000000000000000000000000000..1bf2a0f7ff8d2bb525f9a8eaa63c7c23559c0bac
--- /dev/null
+++ b/docs/array/print.md
@@ -0,0 +1,152 @@
+## 打印数组
+
+“哥,之前听你说,数组也是一个对象,但 Java 中并未明确的定义这样一个类。”看来三妹有在用心地学习。
+
+“是的,因此数组也就没有机会覆盖 `Object.toString()` 方法。如果尝试直接打印数组的话,输出的结果并不是我们预期的结果。”我接着三妹的话继续说。
+
+“那怎么打印数组呢?”三妹心有灵犀地把今天的核心问题提了出来。
+
+“首先,我们来看一下,为什么不能直接打印数组,直接打印的话,会出现什么问题。”
+
+来看这样一个例子。
+
+```
+String [] cmowers = {"沉默","王二","一枚有趣的程序员"};
+System.out.println(cmowers);
+```
+
+程序打印的结果是:
+
+```
+[Ljava.lang.String;@3d075dc0
+```
+
+`[Ljava.lang.String;` 表示字符串数组的 Class 名,@ 后面的是十六进制的 hashCode——这样的打印结果太“人性化”了,一般人表示看不懂!为什么会这样显示呢?查看一下 `java.lang.Object` 类的 `toString()` 方法就明白了。
+
+```java
+public String toString() {
+ return getClass().getName() + "@" + Integer.toHexString(hashCode());
+}
+```
+
+再次证明,数组虽然没有显式定义成一个类,但它的确是一个对象,继承了祖先类 Object 的所有方法。
+
+“哥,那为什么数组不单独定义一个类来表示呢?就像字符串 String 类那样呢?”三妹这个问题让人头大,但也好解释。
+
+“一个合理的说法是 Java 将其隐藏了。假如真的存在这么一个类,就叫 Array.java 吧,我们假想一下它真实的样子,必须得有一个容器来存放数组的每一个元素,就像 String 类那样。”一边回答三妹,我一边打开了 String 类的源码。
+
+```java
+public final class String
+ implements java.io.Serializable, Comparable, CharSequence {
+ /** The value is used for character storage. */
+ private final char value[];
+}
+```
+
+“最终还是要用类似一种数组的形式来存放数组的元素,对吧?这就变得很没有必要了,不妨就把数组当做是一个没有形体的对象吧!”
+
+“好了,不讨论这个了。”我怕话题扯远了,扯到我自己也答不出来就尴尬了,赶紧把三妹的思路拽了回来。
+
+“我们来看第一种打印数组的方法,使用时髦一点的 Stream 流。”
+
+第一种形式:
+
+```java
+Arrays.asList(cmowers).stream().forEach(s -> System.out.println(s));
+```
+
+第二种形式:
+
+```java
+Stream.of(cmowers).forEach(System.out::println);
+```
+
+第三种形式:
+
+```java
+Arrays.stream(cmowers).forEach(System.out::println);
+```
+
+打印的结果如下所示。
+
+```
+沉默
+王二
+一枚有趣的程序员
+```
+
+没错,这三种方式都可以轻松胜任本职工作,并且显得有点高大上,毕竟用到了 Stream,以及 lambda 表达式。
+
+“当然了,也可以使用比较土的方式,for 循环。甚至 for-each 也行。”
+
+```java
+for(int i = 0; i < cmowers.length; i++){
+ System.out.println(cmowers[i]);
+}
+
+for (String s : cmowers) {
+ System.out.println(s);
+}
+```
+
+“哥,你难道忘了[上一篇](https://mp.weixin.qq.com/s/acnDNH6A8USm_EYIT6i-jA)在讲 Arrays 工具类的时候,提到过另外一种方法 `Arrays.toString()` 吗?”三妹看我一直说不到点子上,有点着急了。
+
+“当然没有了,我认为 `Arrays.toString()` 是打印数组的最佳方式,没有之一。”我的情绪有点激动。
+
+`Arrays.toString()` 可以将任意类型的数组转成字符串,包括基本类型数组和引用类型数组。该方法有多种重载形式。
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/array/print-01.png)
+
+使用 `Arrays.toString()` 方法来打印数组再优雅不过了,就像,就像,就像蒙娜丽莎的微笑。
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/array/print-02.png)
+
+(三妹看到这么一副图的时候忍不住地笑了)
+
+“三妹,你不要笑,来,怀揣着愉快的心情看一下代码示例。”
+
+```java
+String [] cmowers = {"沉默","王二","一枚有趣的程序员"};
+System.out.println(Arrays.toString(cmowers));
+```
+
+程序打印结果:
+
+```
+[沉默, 王二, 一枚有趣的程序员]
+```
+
+哇,打印格式不要太完美,不多不少!完全是我们预期的结果:`[]` 表明是一个数组,`,` 点和空格用来分割元素。
+
+“哥,那如果我想打印二维数组呢?”
+
+“可以使用 `Arrays.deepToString()` 方法。”
+
+```java
+String[][] deepArray = new String[][] {{"沉默", "王二"}, {"一枚有趣的程序员"}};
+System.out.println(Arrays.deepToString(deepArray));
+```
+
+打印结果如下所示。
+
+```
+[[沉默, 王二], [一枚有趣的程序员]]
+```
+
+-------
+
+“说到打印,三妹,哥给你提醒一点。阿里巴巴的 Java 开发手册上有这样一条规约,你看。”
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/array/print-03.png)
+
+“什么是 POJO 呢,就是 Plain Ordinary Java Object 的缩写,一般在 Web 应用程序中建立一个数据库的映射对象时,我们称它为 POJO,这类对象不继承或不实现任何其它 Java 框架的类或接口。”
+
+“对于这样的类,最好是重写一下它的 `toString()` 方法,方便查看这个对象到底包含了什么字段,好排查问题。”
+
+“如果不重写的话,打印出来的 Java 对象就像直接打印数组的那样,一串谁也看不懂的字符序列。”
+
+“可以借助 Intellij IDEA 生成重写的 `toString()` 方法,特别方便。”
+
+“好的,哥,我记住了。以后遇到的话,我注意下。你去休息吧,我来敲一下你提到的这些代码,练一练。”
+
+“OK,我走,我走。”
diff --git a/docs/object-class/box.md b/docs/object-class/box.md
new file mode 100644
index 0000000000000000000000000000000000000000..246c65ede8062e9f470384ee020a21d733ed0702
--- /dev/null
+++ b/docs/object-class/box.md
@@ -0,0 +1,256 @@
+## 自动装箱与拆箱
+
+“哥,听说 Java 的每个基本类型都对应了一个包装类型,比如说 int 的包装类型为 Integer,double 的包装类型为 Double,是这样吗?”从三妹这句话当中,能听得出来,她已经提前预习这块内容了。
+
+“是的,三妹。基本类型和包装类型的区别主要有以下 4 点,我来带你学习一下。”我回答说。我们家的斜对面刚好是一所小学,所以时不时还能听到朗朗的读书声,让人心情非常愉快。
+
+“三妹,你准备好了吗?我们开始吧。”
+
+“第一,**包装类型可以为 null,而基本类型不可以**。别小看这一点区别,它使得包装类型可以应用于 POJO 中,而基本类型则不行。”
+
+“POJO 是什么呢?”遇到不会的就问,三妹在这一点上还是非常兢兢业业的。
+
+“POJO 的英文全称是 Plain Ordinary Java Object,翻译一下就是,简单无规则的 Java 对象,只有字段以及对应的 setter 和 getter 方法。”
+
+```java
+class Writer {
+ private Integer age;
+ private String name;
+
+ public Integer getAge() {
+ return age;
+ }
+
+ public void setAge(Integer age) {
+ this.age = age;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
+```
+
+和 POJO 类似的,还有数据传输对象 DTO(Data Transfer Object,泛指用于展示层与服务层之间的数据传输对象)、视图对象 VO(View Object,把某个页面的数据封装起来)、持久化对象 PO(Persistant Object,可以看成是与数据库中的表映射的 Java 对象)。
+
+“那为什么 POJO 的字段必须要用包装类型呢?”三妹问。
+
+“《阿里巴巴 Java 开发手册》上有详细的说明,你看。”我打开 PDF,并翻到了对应的内容,指着屏幕念道。
+
+>数据库的查询结果可能是 null,如果使用基本类型的话,因为要自动拆箱,就会抛出 NullPointerException 的异常。
+
+“什么是自动拆箱呢?”
+
+“自动拆箱指的是,将包装类型转为基本类型,比如说把 Integer 对象转换成 int 值;对应的,把基本类型转为包装类型,则称为自动装箱。”
+
+“哦。”
+
+“那接下来,我们来看第二点不同。**包装类型可用于泛型,而基本类型不可以**,否则就会出现编译错误。”一边说着,我一边在 Intellij IDEA 中噼里啪啦地敲了起来。
+
+“三妹,你瞧,编译器提示错误了。”
+
+```java
+List list = new ArrayList<>(); // 提示 Syntax error, insert "Dimensions" to complete ReferenceType
+List list = new ArrayList<>();
+```
+
+“为什么呢?”三妹及时地问道。
+
+“因为泛型在编译时会进行类型擦除,最后只保留原始类型,而原始类型只能是 Object 类及其子类——基本类型是个例外。”
+
+“那,接下来,我们来说第三点,**基本类型比包装类型更高效**。”我喝了一口茶继续说道。
+
+“作为局部变量时,基本类型在栈中直接存储的具体数值,而包装类型则存储的是堆中的引用。”我一边说着,一边打开 `draw.io` 画起了图。
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/object-class/box-01.png)
+
+很显然,相比较于基本类型而言,包装类型需要占用更多的内存空间,不仅要存储对象,还要存储引用。假如没有基本类型的话,对于数值这类经常使用到的数据来说,每次都要通过 new 一个包装类型就显得非常笨重。
+
+“三妹,你想知道程序运行时,数据都存储在什么地方吗?”
+
+“嗯嗯,哥,你说说呗。”
+
+“通常来说,有 4 个地方可以用来存储数据。”
+
+1)寄存器。这是最快的存储区,因为它位于 CPU 内部,用来暂时存放参与运算的数据和运算结果。
+
+2)栈。位于 RAM(Random Access Memory,也叫主存,与 CPU 直接交换数据的内部存储器)中,速度仅次于寄存器。但是,在分配内存的时候,存放在栈中的数据大小与生存周期必须在编译时是确定的,缺乏灵活性。基本数据类型的值和对象的引用通常存储在这块区域。
+
+3)堆。也位于 RAM 区,可以动态分配内存大小,编译器不必知道要从堆里分配多少存储空间,生存周期也不必事先告诉编译器,Java 的垃圾收集器会自动收走不再使用的数据,因此可以得到更大的灵活性。但是,运行时动态分配内存和销毁对象都需要占用时间,所以效率比栈低一些。new 创建的对象都会存储在这块区域。
+
+4)磁盘。如果数据完全存储在程序之外,就可以不受程序的限制,在程序没有运行时也可以存在。像文件、数据库,就是通过持久化的方式,让对象存放在磁盘上。当需要的时候,再反序列化成程序可以识别的对象。
+
+“能明白吗?三妹?”
+
+“这节讲完后,我再好好消化一下。”
+
+“那好,我们来说第四点,**两个包装类型的值可以相同,但却不相等**。”
+
+```java
+Integer chenmo = new Integer(10);
+Integer wanger = new Integer(10);
+
+System.out.println(chenmo == wanger); // false
+System.out.println(chenmo.equals(wanger )); // true
+```
+
+“两个包装类型在使用“==”进行判断的时候,判断的是其指向的地址是否相等,由于是两个对象,所以地址是不同的。”
+
+“而 chenmo.equals(wanger) 的输出结果为 true,是因为 equals() 方法内部比较的是两个 int 值是否相等。”
+
+```java
+private final int value;
+
+public int intValue() {
+ return value;
+}
+public boolean equals(Object obj) {
+ if (obj instanceof Integer) {
+ return value == ((Integer)obj).intValue();
+ }
+ return false;
+}
+```
+
+虽然 chenmo 和 wanger 的值都是 10,但他们并不相等。换句话说就是:将“==”操作符应用于包装类型比较的时候,其结果很可能会和预期的不符。
+
+“三妹,瞧,`((Integer)obj).intValue()` 这段代码就是用来自动拆箱的。下面,我们来详细地说一说自动装箱和自动拆箱。”
+
+既然有基本类型和包装类型,肯定有些时候要在它们之间进行转换。把基本类型转换成包装类型的过程叫做装箱(boxing)。反之,把包装类型转换成基本类型的过程叫做拆箱(unboxing)。
+
+在 Java 1.5 之前,开发人员要手动进行装拆箱,比如说:
+
+```java
+Integer chenmo = new Integer(10); // 手动装箱
+int wanger = chenmo.intValue(); // 手动拆箱
+```
+
+Java 1.5 为了减少开发人员的工作,提供了自动装箱与自动拆箱的功能。这下就方便了。
+
+```jav
+Integer chenmo = 10; // 自动装箱
+int wanger = chenmo; // 自动拆箱
+```
+
+来看一下反编译后的代码。
+
+```java
+Integer chenmo = Integer.valueOf(10);
+int wanger = chenmo.intValue();
+```
+
+也就是说,自动装箱是通过 `Integer.valueOf()` 完成的;自动拆箱是通过 `Integer.intValue()` 完成的。
+
+“嗯,三妹,给你出一道面试题吧。”
+
+```java
+// 1)基本类型和包装类型
+int a = 100;
+Integer b = 100;
+System.out.println(a == b);
+
+// 2)两个包装类型
+Integer c = 100;
+Integer d = 100;
+System.out.println(c == d);
+
+// 3)
+c = 200;
+d = 200;
+System.out.println(c == d);
+```
+
+“给你 3 分钟时间,你先思考下,我去抽根华子,等我回来,然后再来分析一下为什么。”
+
+。。。。。。
+
+“嗯,哥,你过来吧,我说一说我的想法。”
+
+第一段代码,基本类型和包装类型进行 == 比较,这时候 b 会自动拆箱,直接和 a 比较值,所以结果为 true。
+
+第二段代码,两个包装类型都被赋值为了 100,这时候会进行自动装箱,按照你之前说的,将“==”操作符应用于包装类型比较的时候,其结果很可能会和预期的不符,我想结果可能为 false。
+
+第三段代码,两个包装类型重新被赋值为了 200,这时候仍然会进行自动装箱,我想结果仍然为 false。
+
+“嗯嗯,三妹,你分析的很有逻辑,但第二段代码的结果为 true,是不是感到很奇怪?”
+
+“为什么会这样呀?”三妹急切地问。
+
+“你说的没错,自动装箱是通过 Integer.valueOf() 完成的,我们来看看这个方法的源码就明白为什么了。”
+
+```java
+public static Integer valueOf(int i) {
+ if (i >= IntegerCache.low && i <= IntegerCache.high)
+ return IntegerCache.cache[i + (-IntegerCache.low)];
+ return new Integer(i);
+}
+```
+
+是不是看到了一个之前从来没见过的类——IntegerCache?
+
+“难道说是 Integer 的缓存类?”三妹做出了自己的判断。
+
+“是的,来看一下 IntegerCache 的源码吧。”
+
+```java
+private static class IntegerCache {
+ static final int low = -128;
+ static final int high;
+ static final Integer cache[];
+
+ static {
+ // high value may be configured by property
+ int h = 127;
+ int i = parseInt(integerCacheHighPropValue);
+ i = Math.max(i, 127);
+ h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
+ high = h;
+
+ cache = new Integer[(high - low) + 1];
+ int j = low;
+ for(int k = 0; k < cache.length; k++)
+ cache[k] = new Integer(j++);
+
+ // range [-128, 127] must be interned (JLS7 5.1.7)
+ assert IntegerCache.high >= 127;
+ }
+}
+```
+
+大致瞟一下这段代码你就全明白了。-128 到 127 之间的数会从 IntegerCache 中取,然后比较,所以第二段代码(100 在这个范围之内)的结果是 true,而第三段代码(200 不在这个范围之内,所以 new 出来了两个 Integer 对象)的结果是 false。
+
+“三妹,看完上面的分析之后,我希望你记住一点:**当需要进行自动装箱时,如果数字在 -128 至 127 之间时,会直接使用缓存中的对象,而不是重新创建一个对象**。”
+
+“自动装拆箱是一个很好的功能,大大节省了我们开发人员的精力,但也会引发一些麻烦,比如下面这段代码,性能就很差。”
+
+```java
+long t1 = System.currentTimeMillis();
+Long sum = 0L;
+for (int i = 0; i < Integer.MAX_VALUE;i++) {
+ sum += i;
+}
+long t2 = System.currentTimeMillis();
+System.out.println(t2-t1);
+```
+
+“知道为什么吗?三妹。”
+
+“难道是因为 sum 被声明成了包装类型 Long 而不是基本类型 long。”三妹若有所思。
+
+“是滴,由于 sum 是个 Long 型,而 i 为 int 类型,`sum += i` 在执行的时候,会先把 i 强转为 long 型,然后再把 sum 拆箱为 long 型进行相加操作,之后再自动装箱为 Long 型赋值给 sum。”
+
+“三妹,你可以试一下,把 sum 换成 long 型比较一下它们运行的时间。”
+
+。。。。。。
+
+“哇,sum 为 Long 型的时候,足足运行了 5825 毫秒;sum 为 long 型的时候,只需要 679 毫秒。”
+
+“好了,三妹,今天的主题就先讲到这吧。我再去来根华子。”
+
+-------
+
+PS:点击「**阅读原文**」可直达《教妹学Java》专栏的 GitHub 开源地址,记得 star 哦!
\ No newline at end of file
diff --git a/docs/object-class/deep-copy.md b/docs/object-class/deep-copy.md
new file mode 100644
index 0000000000000000000000000000000000000000..54e7abb666c61cfde15e49b918677575fbddb35a
--- /dev/null
+++ b/docs/object-class/deep-copy.md
@@ -0,0 +1,419 @@
+## 浅拷贝与深拷贝
+
+“哥,听说浅拷贝和深拷贝是 Java 面试中经常会被问到的一个问题,是这样吗?”
+
+“还真的是,而且了解浅拷贝和深拷贝的原理,对 Java 是值传递还是引用传递也会有更深的理解。”我肯定地回答。
+
+“不管是浅拷贝还是深拷贝,都可以通过调用 Object 类的 `clone()` 方法来完成。”我一边说,一边打开 Intellij IDEA,并找到了 `clone()` 方法的源码。
+
+```java
+@HotSpotIntrinsicCandidate
+protected native Object clone() throws CloneNotSupportedException;
+```
+
+其中 `@HotSpotIntrinsicCandidate` 是 Java 9 引入的一个注解,被它标注的方法,在 HotSpot 虚拟机中会有一套高效的实现。需要注意的是,`clone()` 方法同时是一个本地(`native`)方法,它的具体实现会交给 HotSpot 虚拟机,那就意味着虚拟机在运行该方法的时候,会将其替换为更高效的 C/C++ 代码,进而调用操作系统去完成对象的克隆工作。
+
+“哥,那你就先说浅拷贝吧!”
+
+“好的呀。直接上实战代码。”
+
+```java
+class Writer implements Cloneable{
+ private int age;
+ private String name;
+
+ // getter/setter 和构造方法都已省略
+
+ @Override
+ public String toString() {
+ return super.toString().substring(26) + "{" +
+ "age=" + age +
+ ", name='" + name + '\'' +
+ '}';
+ }
+
+ @Override
+ protected Object clone() throws CloneNotSupportedException {
+ return super.clone();
+ }
+}
+```
+
+Writer 类有两个字段,分别是 int 类型的 age,和 String 类型的 name。然后重写了 `toString()` 方法,方便打印对象的具体信息。并且重写了 `clone()` 方法,方法体里面也很简单,直接调用 Object 类的 `clone()` 方法。
+
+“既然 Writer 类的 `clone()` 方法体里只有一行代码,调用的还是超类 Object 的 `clone()` 方法?为什么还要重写呢?不是多此一举吗?”三妹着急地问。
+
+“嗯,是这样的,三妹。Object 类中的 `clone()` 方法是 protected 的,如果 Writer 类不去重写的话,Writer 类的对象是无法调用 `clone()` 方法的,因为 protected 修饰的方法对子类并不可见。”
+
+“哦哦,那为什么要实现 Cloneable 接口呢?”三妹开启了十万个为什么的模式。
+
+Cloneable 接口是一个标记接口,它肚子里面是空的:
+
+```java
+public interface Cloneable {
+}
+```
+
+只是,如果一个类没有实现 Cloneable 接口,即便它重写了 `clone()` 方法,依然是无法调用该方法进行对象克隆的,程序在执行 `clone()` 方法的时候会抛出 CloneNotSupportedException 异常。
+
+```java
+Exception in thread "main" java.lang.CloneNotSupportedException
+```
+
+标记接口的作用其实很简单,用来表示某个功能在执行的时候是合法的。
+
+“哦,我悟了!”三妹看来是彻底明白了我说的内容。
+
+“接着,来测试类。”
+
+```java
+class TestClone {
+ public static void main(String[] args) throws CloneNotSupportedException {
+ Writer writer1 = new Writer(18,"二哥");
+ Writer writer2 = (Writer) writer1.clone();
+
+ System.out.println("浅拷贝后:");
+ System.out.println("writer1:" + writer1);
+ System.out.println("writer2:" + writer2);
+
+ writer2.setName("三妹");
+
+ System.out.println("调整了 writer2 的 name 后:");
+ System.out.println("writer1:" + writer1);
+ System.out.println("writer2:" + writer2);
+ }
+}
+```
+
+- 通过 new 关键字声明了一个 Writer 对象(18 岁的二哥),将其赋值给 writer1。
+- 通过调用 `clone()` 方法进行对象拷贝,并将其赋值给 writer2。
+- 之后打印 writer1 和 writer2。
+- 将 writer2 的 name 字段调整为“三妹”。
+- 再次打印。
+
+来看一下输出结果。
+
+```
+浅拷贝后:
+writer1:Writer@68837a77{age=18, name='二哥'}
+writer2:Writer@b97c004{age=18, name='二哥'}
+调整了 writer2 的 name 后:
+writer1:Writer@68837a77{age=18, name='二哥'}
+writer2:Writer@b97c004{age=18, name='三妹'}
+```
+
+可以看得出,浅拷贝后,writer1 和 writer2 引用了不同的对象,但值是相同的,说明拷贝成功。之后,修改了 writer2 的 name 字段,直接上图就明白了。
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/object-class/deep-copy-01.png)
+
+ 之前的例子中,Writer 类只有两个字段,没有引用类型字段。那么,我们再来看另外一个例子,为 Writer 类增加一个自定义的引用类型字段 Book,先来看 Book 的定义。
+
+```java
+class Book {
+ private String bookName;
+ private int price;
+
+ // getter/setter 和构造方法都已省略
+
+ @Override
+ public String toString() {
+ return super.toString().substring(26) +
+ " bookName='" + bookName + '\'' +
+ ", price=" + price +
+ '}';
+ }
+}
+```
+
+有两个字段,分别是 String 类型的 bookName 和 int 类型的 price。
+
+然后来看 Writer 类的定义。
+
+```java
+class Writer implements Cloneable{
+ private int age;
+ private String name;
+ private Book book;
+
+ // getter/setter 和构造方法都已省略
+
+ @Override
+ public String toString() {
+ return super.toString().substring(26) +
+ " age=" + age +
+ ", name='" + name + '\'' +
+ ", book=" + book +
+ '}';
+ }
+
+ @Override
+ protected Object clone() throws CloneNotSupportedException {
+ return super.clone();
+ }
+}
+```
+
+比之前的例子多了一个自定义类型的字段 book,`clone()` 方法并没有任何改变。
+
+再来看测试类。
+
+```java
+class TestClone {
+ public static void main(String[] args) throws CloneNotSupportedException {
+ Writer writer1 = new Writer(18,"二哥");
+ Book book1 = new Book("编译原理",100);
+ writer1.setBook(book1);
+
+ Writer writer2 = (Writer) writer1.clone();
+ System.out.println("浅拷贝后:");
+
+ System.out.println("writer1:" + writer1);
+ System.out.println("writer2:" + writer2);
+
+ Book book2 = writer2.getBook();
+ book2.setBookName("永恒的图灵");
+ book2.setPrice(70);
+ System.out.println("writer2.book 变更后:");
+
+ System.out.println("writer1:" + writer1);
+ System.out.println("writer2:" + writer2);
+ }
+}
+```
+
+- 通过 new 关键字声明了一个 Writer 对象(18 岁的二哥),将其赋值给 writer1。
+- 通过 new 关键字声明了一个 Book 对象(100 块的编译原理),将其赋值给 book1。
+- 将 writer1 的 book 字段设置为 book1。
+- 通过调用 `clone()` 方法进行对象拷贝,并将其赋值给 writer2。
+- 之后打印 writer1 和 writer2。
+- 获取 writer2 的 book 字段,并将其赋值给 book2。
+- 将 book2 的 bookName 字段调整为“永恒的图灵”,price 字段调整为 70。
+- 再次打印。
+
+来看一下输出结果。
+
+```
+浅拷贝后:
+writer1:Writer@68837a77 age=18, name='二哥', book=Book@32e6e9c3 bookName='编译原理', price=100}}
+writer2:Writer@6d00a15d age=18, name='二哥', book=Book@32e6e9c3 bookName='编译原理', price=100}}
+writer2.book 变更后:
+writer1:Writer@68837a77 age=18, name='二哥', book=Book@32e6e9c3 bookName='永恒的图灵', price=70}}
+writer2:Writer@36d4b5c age=18, name='二哥', book=Book@32e6e9c3 bookName='永恒的图灵', price=70}}
+```
+
+与之前例子不同的是,writer2.book 变更后,writer1.book 也发生了改变。这是因为字符串 String 是不可变对象,一个新的值必须在字符串常量池中开辟一段新的内存空间,而自定义对象的内存地址并没有发生改变,只是对应的字段值发生了改变,见下图。
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/object-class/deep-copy-02.png)
+
+“哇,哥,果真一图胜千言,我明白了。”三妹似乎对我画的图很感兴趣呢,“那你继续说深拷贝吧!”
+
+“嗯,三妹,你有没有注意到,浅拷贝克隆的对象中,引用类型的字段指向的是同一个,当改变任何一个对象,另外一个对象也会随之改变,除去字符串的特殊性外。”
+
+“深拷贝和浅拷贝不同的,深拷贝中的引用类型字段也会克隆一份,当改变任何一个对象,另外一个对象不会随之改变。”
+
+“明白了这一点后,我们再来看例子。”
+
+```java
+class Book implements Cloneable{
+ private String bookName;
+ private int price;
+
+ // getter/setter 和构造方法都已省略
+
+ @Override
+ public String toString() {
+ return super.toString().substring(26) +
+ " bookName='" + bookName + '\'' +
+ ", price=" + price +
+ '}';
+ }
+
+ @Override
+ protected Object clone() throws CloneNotSupportedException {
+ return super.clone();
+ }
+}
+```
+
+注意,此时的 Book 类和浅拷贝时不同,重写了 `clone()` 方法,并实现了 Cloneable 接口。为的就是深拷贝的时候也能够克隆该字段。
+
+```java
+class Writer implements Cloneable{
+ private int age;
+ private String name;
+ private Book book;
+
+ // getter/setter 和构造方法都已省略
+
+ @Override
+ public String toString() {
+ return super.toString().substring(26) +
+ " age=" + age +
+ ", name='" + name + '\'' +
+ ", book=" + book +
+ '}';
+ }
+
+ @Override
+ protected Object clone() throws CloneNotSupportedException {
+ Writer writer = (Writer) super.clone();
+ writer.setBook((Book) writer.getBook().clone());
+ return writer;
+ }
+}
+```
+
+注意,此时 Writer 类也与之前的不同,`clone()` 方法当中,不再只调用 Object 的 `clone()` 方法对 Writer 进行克隆了,还对 Book 也进行了克隆。
+
+来看测试类。
+
+```java
+class TestClone {
+ public static void main(String[] args) throws CloneNotSupportedException {
+ Writer writer1 = new Writer(18,"二哥");
+ Book book1 = new Book("编译原理",100);
+ writer1.setBook(book1);
+
+ Writer writer2 = (Writer) writer1.clone();
+ System.out.println("深拷贝后:");
+
+ System.out.println("writer1:" + writer1);
+ System.out.println("writer2:" + writer2);
+
+ Book book2 = writer2.getBook();
+ book2.setBookName("永恒的图灵");
+ book2.setPrice(70);
+ System.out.println("writer2.book 变更后:");
+
+ System.out.println("writer1:" + writer1);
+ System.out.println("writer2:" + writer2);
+ }
+}
+```
+
+这个测试类和之前的浅拷贝的测试类就完全一样了,但运行结果是不同的。
+
+```
+深拷贝后:
+writer1:Writer@6be46e8f age=18, name='二哥', book=Book@5056dfcb bookName='编译原理', price=100}}
+writer2:Writer@6d00a15d age=18, name='二哥', book=Book@51efea79 bookName='编译原理', price=100}}
+writer2.book 变更后:
+writer1:Writer@6be46e8f age=18, name='二哥', book=Book@5056dfcb bookName='编译原理', price=100}}
+writer2:Writer@6d00a15d age=18, name='二哥', book=Book@51efea79 bookName='永恒的图灵', price=70}}
+```
+
+不只是 writer1 和 writer2 是不同的对象,它们中的 book 也是不同的对象。所以,改变了 writer2 中的 book 并不会影响到 writer1。
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/object-class/deep-copy-03.png)
+
+不过,通过 `clone()` 方法实现的深拷贝比较笨重,因为要将所有的引用类型都重写 `clone()` 方法,当嵌套的对象比较多的时候,就废了!
+
+“那有没有好的办法呢?”三妹急切的问。
+
+“当然有了,利用序列化。”我胸有成竹的回答,“序列化是将对象写到流中便于传输,而反序列化则是将对象从流中读取出来。”
+
+“写入流中的对象就是对原始对象的拷贝。需要注意的是,每个要序列化的类都要实现 Serializable 接口,该接口和 Cloneable 接口类似,都是标记型接口。”
+
+来看例子。
+
+```java
+class Book implements Serializable {
+ private String bookName;
+ private int price;
+
+ // getter/setter 和构造方法都已省略
+
+ @Override
+ public String toString() {
+ return super.toString().substring(26) +
+ " bookName='" + bookName + '\'' +
+ ", price=" + price +
+ '}';
+ }
+}
+```
+
+Book 需要实现 Serializable 接口。
+
+```java
+class Writer implements Serializable {
+ private int age;
+ private String name;
+ private Book book;
+
+ // getter/setter 和构造方法都已省略
+
+ @Override
+ public String toString() {
+ return super.toString().substring(26) +
+ " age=" + age +
+ ", name='" + name + '\'' +
+ ", book=" + book +
+ '}';
+ }
+
+ //深度拷贝
+ public Object deepClone() throws IOException, ClassNotFoundException {
+ // 序列化
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(bos);
+
+ oos.writeObject(this);
+
+ // 反序列化
+ ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
+ ObjectInputStream ois = new ObjectInputStream(bis);
+
+ return ois.readObject();
+ }
+}
+```
+
+Writer 类也需要实现 Serializable 接口,并且在该类中,增加了一个 `deepClone()` 的方法,利用 OutputStream 进行序列化,InputStream 进行反序列化,这样就实现了深拷贝。
+
+来看示例。
+
+```java
+class TestClone {
+ public static void main(String[] args) throws IOException, ClassNotFoundException {
+ Writer writer1 = new Writer(18,"二哥");
+ Book book1 = new Book("编译原理",100);
+ writer1.setBook(book1);
+
+ Writer writer2 = (Writer) writer1.deepClone();
+ System.out.println("深拷贝后:");
+
+ System.out.println("writer1:" + writer1);
+ System.out.println("writer2:" + writer2);
+
+ Book book2 = writer2.getBook();
+ book2.setBookName("永恒的图灵");
+ book2.setPrice(70);
+ System.out.println("writer2.book 变更后:");
+
+ System.out.println("writer1:" + writer1);
+ System.out.println("writer2:" + writer2);
+ }
+}
+```
+
+与之前测试类不同的是,调用了 `deepClone()` 方法。
+
+```
+深拷贝后:
+writer1:Writer@9629756 age=18, name='二哥', book=Book@735b5592 bookName='编译原理', price=100}}
+writer2:Writer@544fe44c age=18, name='二哥', book=Book@31610302 bookName='编译原理', price=100}}
+writer2.book 变更后:
+writer1:Writer@9629756 age=18, name='二哥', book=Book@735b5592 bookName='编译原理', price=100}}
+writer2:Writer@544fe44c age=18, name='二哥', book=Book@31610302 bookName='永恒的图灵', price=70}}
+```
+
+测试结果和之前用 `clone()` 方法实现的深拷贝类似。
+
+“不过,三妹,需要注意,由于是序列化涉及到输入流和输出流的读写,在性能上要比 HotSpot 虚拟机实现的 `clone()` 方法差很多。”我语重心长地说。
+
+“好的,二哥,你先去休息吧,让我来琢磨一会,总结一下浅拷贝和深拷贝之间的差异。”
+
+“嗯嗯。”
+
+PS:点击「**阅读原文**」可直达《教妹学Java》专栏的 GitHub 开源地址,记得 star 哦!
\ No newline at end of file
diff --git a/docs/object-class/java-abstract.md b/docs/object-class/java-abstract.md
new file mode 100644
index 0000000000000000000000000000000000000000..6415e6fa9dbbb8f49006dcdac7ad0b6d9841ae92
--- /dev/null
+++ b/docs/object-class/java-abstract.md
@@ -0,0 +1,241 @@
+## 抽象类
+
+“二哥,你这明显加快了更新的频率呀!”三妹对于我最近的肝劲由衷的佩服了起来。
+
+“哈哈,是呀,这次不能再断更了,我要再更 175 篇,总计 200 篇,给广大的学弟学妹们一个完整的 Java 学习体系。”我对未来充满了信心。
+
+“那就开始吧。”三妹说。
+
+-------
+
+
+定义抽象类的时候需要用到关键字 `abstract`,放在 `class` 关键字前,就像下面这样。
+
+```java
+abstract class AbstractPlayer {
+}
+```
+
+关于抽象类的命名,《阿里的 Java 开发手册》上有强调,“抽象类命名要使用 Abstract 或 Base 开头”,这条规约还是值得遵守的。
+
+抽象类是不能实例化的,尝试通过 `new` 关键字实例化的话,编译器会报错,提示“类是抽象的,不能实例化”。
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/object-class/abstract-01.png)
+
+虽然抽象类不能实例化,但可以有子类。子类通过 `extends` 关键字来继承抽象类。就像下面这样。
+
+```java
+public class BasketballPlayer extends AbstractPlayer {
+}
+```
+
+如果一个类定义了一个或多个抽象方法,那么这个类必须是抽象类。
+
+当我们尝试在一个普通类中定义抽象方法的时候,编译器会有两处错误提示。第一处在类级别上,提示“这个类必须通过 `abstract` 关键字定义”,见下图。
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/object-class/abstract-02.png)
+
+第二处在尝试定义 abstract 的方法上,提示“抽象方法所在的类不是抽象的”,见下图。
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/object-class/abstract-03.png)
+
+抽象类中既可以定义抽象方法,也可以定义普通方法,就像下面这样:
+
+```java
+public abstract class AbstractPlayer {
+ abstract void play();
+
+ public void sleep() {
+ System.out.println("运动员也要休息而不是挑战极限");
+ }
+}
+```
+
+抽象类派生的子类必须实现父类中定义的抽象方法。比如说,抽象类 AbstractPlayer 中定义了 `play()` 方法,子类 BasketballPlayer 中就必须实现。
+
+```java
+public class BasketballPlayer extends AbstractPlayer {
+ @Override
+ void play() {
+ System.out.println("我是张伯伦,篮球场上得过 100 分");
+ }
+}
+```
+
+如果没有实现的话,编译器会提示“子类必须实现抽象方法”,见下图。
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/object-class/abstract-04.png)
+
+“二哥,抽象方法我明白了,那什么时候使用抽象方法呢?能给我讲讲它的应用场景吗?”三妹及时的插话道。
+
+“这问题问的恰到好处呀!”我扶了扶眼镜继续说。
+
+**第一种场景**。
+
+当我们希望一些通用的功能被多个子类复用的时候,就可以使用抽象类。比如说,AbstractPlayer 抽象类中有一个普通的方法 `sleep()`,表明所有运动员都需要休息,那么这个方法就可以被子类复用。
+
+```java
+abstract class AbstractPlayer {
+ public void sleep() {
+ System.out.println("运动员也要休息而不是挑战极限");
+ }
+}
+```
+
+子类 BasketballPlayer 继承了 AbstractPlayer 类:
+
+```java
+class BasketballPlayer extends AbstractPlayer {
+}
+```
+
+也就拥有了 `sleep()` 方法。BasketballPlayer 的对象可以直接调用父类的 `sleep()` 方法:
+
+```java
+BasketballPlayer basketballPlayer = new BasketballPlayer();
+basketballPlayer.sleep();
+```
+
+子类 FootballPlayer 继承了 AbstractPlayer 类:
+
+```java
+class FootballPlayer extends AbstractPlayer {
+}
+```
+
+也拥有了 `sleep()` 方法,FootballPlayer 的对象也可以直接调用父类的 `sleep()` 方法:
+
+```java
+FootballPlayer footballPlayer = new FootballPlayer();
+footballPlayer.sleep();
+```
+
+这样是不是就实现了代码的复用呢?
+
+**第二种场景**。
+
+当我们需要在抽象类中定义好 API,然后在子类中扩展实现的时候就可以使用抽象类。比如说,AbstractPlayer 抽象类中定义了一个抽象方法 `play()`,表明所有运动员都可以从事某项运动,但需要对应子类去扩展实现,表明篮球运动员打篮球,足球运动员踢足球。
+
+```java
+abstract class AbstractPlayer {
+ abstract void play();
+}
+```
+
+BasketballPlayer 继承了 AbstractPlayer 类,扩展实现了自己的 `play()` 方法。
+
+```java
+public class BasketballPlayer extends AbstractPlayer {
+ @Override
+ void play() {
+ System.out.println("我是张伯伦,我篮球场上得过 100 分,");
+ }
+}
+```
+
+FootballPlayer 继承了 AbstractPlayer 类,扩展实现了自己的 `play()` 方法。
+
+```java
+public class FootballPlayer extends AbstractPlayer {
+ @Override
+ void play() {
+ System.out.println("我是C罗,我能接住任意高度的头球");
+ }
+}
+```
+
+为了进一步展示抽象类的特性,我们再来看一个具体的示例。假设现在有一个文件,里面的内容非常简单,只有一个“Hello World”,现在需要有一个读取器将内容从文件中读取出来,最好能按照大写的方式,或者小写的方式来读。
+
+这时候,最好定义一个抽象类 BaseFileReader:
+
+```java
+abstract class BaseFileReader {
+ protected Path filePath;
+
+ protected BaseFileReader(Path filePath) {
+ this.filePath = filePath;
+ }
+
+ public List readFile() throws IOException {
+ return Files.lines(filePath)
+ .map(this::mapFileLine).collect(Collectors.toList());
+ }
+
+ protected abstract String mapFileLine(String line);
+}
+```
+
+- filePath 为文件路径,使用 protected 修饰,表明该成员变量可以在需要时被子类访问到。
+
+- `readFile()` 方法用来读取文件,方法体里面调用了抽象方法 `mapFileLine()`——需要子类来扩展实现大小写的不同读取方式。
+
+在我看来,BaseFileReader 类设计的就非常合理,并且易于扩展,子类只需要专注于具体的大小写实现方式就可以了。
+
+小写的方式:
+
+```java
+class LowercaseFileReader extends BaseFileReader {
+ protected LowercaseFileReader(Path filePath) {
+ super(filePath);
+ }
+
+ @Override
+ protected String mapFileLine(String line) {
+ return line.toLowerCase();
+ }
+}
+```
+
+大写的方式:
+
+```java
+class UppercaseFileReader extends BaseFileReader {
+ protected UppercaseFileReader(Path filePath) {
+ super(filePath);
+ }
+
+ @Override
+ protected String mapFileLine(String line) {
+ return line.toUpperCase();
+ }
+}
+```
+
+从文件里面一行一行读取内容的代码被子类复用了。与此同时,子类只需要专注于自己该做的工作,LowercaseFileReader 以小写的方式读取文件内容,UppercaseFileReader 以大写的方式读取文件内容。
+
+来看一下测试类 FileReaderTest:
+
+```java
+public class FileReaderTest {
+ public static void main(String[] args) throws URISyntaxException, IOException {
+ URL location = FileReaderTest.class.getClassLoader().getResource("helloworld.txt");
+ Path path = Paths.get(location.toURI());
+ BaseFileReader lowercaseFileReader = new LowercaseFileReader(path);
+ BaseFileReader uppercaseFileReader = new UppercaseFileReader(path);
+ System.out.println(lowercaseFileReader.readFile());
+ System.out.println(uppercaseFileReader.readFile());
+ }
+}
+```
+
+在项目的 resource 目录下建一个文本文件,名字叫 helloworld.txt,里面的内容就是“Hello World”。文件的具体位置如下图所示,我用的集成开发环境是 Intellij IDEA。
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/object-class/abstract-05.png)
+
+
+在 resource 目录下的文件可以通过 `ClassLoader.getResource()` 的方式获取到 URI 路径,然后就可以取到文本内容了。
+
+输出结果如下所示:
+
+```
+[hello world]
+[HELLO WORLD]
+```
+
+-------
+
+“完了吗?二哥”三妹似乎还沉浸在聆听教诲的快乐中。
+
+“是滴,这次我们系统化的学习了抽象类,可以说面面俱到了。三妹你可以把代码敲一遍,加强了一些印象,电脑交给你了。”说完,我就跑到阳台去抽烟了。
+
+“呼。。。。。”一个大大的眼圈飘散开来,又是愉快的一天~
diff --git a/docs/object-class/java-interface.md b/docs/object-class/java-interface.md
new file mode 100644
index 0000000000000000000000000000000000000000..13a38747e7f041997001ef660115ed48f24399d6
--- /dev/null
+++ b/docs/object-class/java-interface.md
@@ -0,0 +1,321 @@
+## 接口
+
+“哥,我看你朋友圈说《教妹学 Java》专栏收到了第一笔赞赏呀,虽然只有一块钱,但我也替你感到开心。”三妹的脸上洋溢着自信的微笑,仿佛这钱是打给她的一样。
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/object-class/interface-01.png)
+
+“是啊,早上起来的时候看到这条信息,还真的是挺开心的,虽然只有一块钱,但是开源的第一笔,也是我人生当中的第一笔,真的非常感谢这个读者,值得纪念的一天。”我自己也掩饰不住内心的激动。
+
+“有了这份鼓励,我相信你更新下去的动力更足了!”三妹今天说的话真的是特别令人喜欢。
+
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/object-class/interface-02.png)
+
+“是啊是啊,所以,今天要更新第 26 讲了——接口。”我接着说,“对于面向对象编程来说,抽象是一个极具魅力的特征。如果一个程序员的抽象思维很差,那他在编程中就会遇到很多困难,无法把业务变成具体的代码。在 Java 中,可以通过两种形式来达到抽象的目的,一种上一篇的主角——[抽象类](https://mp.weixin.qq.com/s/WSmGwdtlimIFVVDVKfvrWQ),另外一种就是今天的主角——接口。”
+
+----------
+
+“接口是什么呀?”三妹顺着我的话题及时的插话到。
+
+接口通过 interface 关键字来定义,它可以包含一些常量和方法,来看下面这个示例。
+
+```java
+public interface Electronic {
+ // 常量
+ String LED = "LED";
+
+ // 抽象方法
+ int getElectricityUse();
+
+ // 静态方法
+ static boolean isEnergyEfficient(String electtronicType) {
+ return electtronicType.equals(LED);
+ }
+
+ // 默认方法
+ default void printDescription() {
+ System.out.println("电子");
+ }
+}
+```
+
+来看一下这段代码反编译后的字节码。
+
+```java
+public interface Electronic
+{
+
+ public abstract int getElectricityUse();
+
+ public static boolean isEnergyEfficient(String electtronicType)
+ {
+ return electtronicType.equals("LED");
+ }
+
+ public void printDescription()
+ {
+ System.out.println("\u7535\u5B50");
+ }
+
+ public static final String LED = "LED";
+}
+```
+
+发现没?接口中定义的所有变量或者方法,都会自动添加上 `public` 关键字。
+
+接下来,我来一一解释下 Electronic 接口中的核心知识点。
+
+1)接口中定义的变量会在编译的时候自动加上 `public static final` 修饰符(注意看一下反编译后的字节码),也就是说上例中的 LED 变量其实就是一个常量。
+
+Java 官方文档上有这样的声明:
+
+>Every field declaration in the body of an interface is implicitly public, static, and final.
+
+换句话说,接口可以用来作为常量类使用,还能省略掉 `public static final`,看似不错的一种选择,对吧?
+
+不过,这种选择并不可取。因为接口的本意是对方法进行抽象,而常量接口会对子类中的变量造成命名空间上的“污染”。
+
+2)没有使用 `private`、`default` 或者 `static` 关键字修饰的方法是隐式抽象的,在编译的时候会自动加上 `public abstract` 修饰符。也就是说上例中的 `getElectricityUse()` 其实是一个抽象方法,没有方法体——这是定义接口的本意。
+
+3)从 Java 8 开始,接口中允许有静态方法,比如说上例中的 `isEnergyEfficient()` 方法。
+
+静态方法无法由(实现了该接口的)类的对象调用,它只能通过接口名来调用,比如说 `Electronic.isEnergyEfficient("LED")`。
+
+接口中定义静态方法的目的是为了提供一种简单的机制,使我们不必创建对象就能调用方法,从而提高接口的竞争力。
+
+4)接口中允许定义 `default` 方法也是从 Java 8 开始的,比如说上例中的 `printDescription()` 方法,它始终由一个代码块组成,为,实现该接口而不覆盖该方法的类提供默认实现。既然要提供默认实现,就要有方法体,换句话说,默认方法后面不能直接使用“;”号来结束——编译器会报错。
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/object-class/interface-03.png)
+
+“为什么要在接口中定义默认方法呢?”三妹好奇地问到。
+
+允许在接口中定义默认方法的理由很充分,因为一个接口可能有多个实现类,这些类就必须实现接口中定义的抽象类,否则编译器就会报错。假如我们需要在所有的实现类中追加某个具体的方法,在没有 `default` 方法的帮助下,我们就必须挨个对实现类进行修改。
+
+由之前的例子我们就可以得出下面这些结论:
+
+- 接口中允许定义变量
+- 接口中允许定义抽象方法
+- 接口中允许定义静态方法(Java 8 之后)
+- 接口中允许定义默认方法(Java 8 之后)
+
+除此之外,我们还应该知道:
+
+1)接口不允许直接实例化,否则编译器会报错。
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/object-class/interface-04.png)
+
+需要定义一个类去实现接口,见下例。
+
+```java
+public class Computer implements Electronic {
+
+ public static void main(String[] args) {
+ new Computer();
+ }
+
+ @Override
+ public int getElectricityUse() {
+ return 0;
+ }
+}
+```
+
+然后再实例化。
+
+```
+Electronic e = new Computer();
+```
+
+2)接口可以是空的,既可以不定义变量,也可以不定义方法。最典型的例子就是 Serializable 接口,在 `java.io` 包下。
+
+```java
+public interface Serializable {
+}
+```
+
+Serializable 接口用来为序列化的具体实现提供一个标记,也就是说,只要某个类实现了 Serializable 接口,那么它就可以用来序列化了。
+
+3)不要在定义接口的时候使用 final 关键字,否则会报编译错误,因为接口就是为了让子类实现的,而 final 阻止了这种行为。
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/object-class/interface-05.png)
+
+4)接口的抽象方法不能是 private、protected 或者 final,否则编译器都会报错。
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/object-class/interface-06.png)
+
+5)接口的变量是隐式 `public static final`(常量),所以其值无法改变。
+
+“接口可以做什么呢?”三妹见缝插针,问的很及时。
+
+第一,使某些实现类具有我们想要的功能,比如说,实现了 Cloneable 接口的类具有拷贝的功能,实现了 Comparable 或者 Comparator 的类具有比较功能。
+
+Cloneable 和 Serializable 一样,都属于标记型接口,它们内部都是空的。实现了 Cloneable 接口的类可以使用 `Object.clone()` 方法,否则会抛出 CloneNotSupportedException。
+
+```java
+public class CloneableTest implements Cloneable {
+ @Override
+ protected Object clone() throws CloneNotSupportedException {
+ return super.clone();
+ }
+
+ public static void main(String[] args) throws CloneNotSupportedException {
+ CloneableTest c1 = new CloneableTest();
+ CloneableTest c2 = (CloneableTest) c1.clone();
+ }
+}
+```
+
+运行后没有报错。现在把 `implements Cloneable` 去掉。
+
+```java
+public class CloneableTest {
+ @Override
+ protected Object clone() throws CloneNotSupportedException {
+ return super.clone();
+ }
+
+ public static void main(String[] args) throws CloneNotSupportedException {
+ CloneableTest c1 = new CloneableTest();
+ CloneableTest c2 = (CloneableTest) c1.clone();
+
+ }
+}
+```
+
+运行后抛出 CloneNotSupportedException:
+
+```
+Exception in thread "main" java.lang.CloneNotSupportedException: com.cmower.baeldung.interface1.CloneableTest
+ at java.base/java.lang.Object.clone(Native Method)
+ at com.cmower.baeldung.interface1.CloneableTest.clone(CloneableTest.java:6)
+ at com.cmower.baeldung.interface1.CloneableTest.main(CloneableTest.java:11)
+```
+
+
+第二,Java 原则上只支持单一继承,但通过接口可以实现多重继承的目的。
+
+如果有两个类共同继承(extends)一个父类,那么父类的方法就会被两个子类重写。然后,如果有一个新类同时继承了这两个子类,那么在调用重写方法的时候,编译器就不能识别要调用哪个类的方法了。这也正是著名的菱形问题,见下图。
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/object-class/interface-07.png)
+
+
+简单解释下,ClassC 同时继承了 ClassA 和 ClassB,ClassC 的对象在调用 ClassA 和 ClassB 中重写的方法时,就不知道该调用 ClassA 的方法,还是 ClassB 的方法。
+
+接口没有这方面的困扰。来定义两个接口,Fly 接口会飞,Run 接口会跑。
+
+```java
+public interface Fly {
+ void fly();
+}
+public interface Run {
+ void run();
+}
+```
+
+然后让 Pig 类同时实现这两个接口。
+
+```java
+public class Pig implements Fly,Run{
+ @Override
+ public void fly() {
+ System.out.println("会飞的猪");
+ }
+
+ @Override
+ public void run() {
+ System.out.println("会跑的猪");
+ }
+}
+```
+
+在某种形式上,接口实现了多重继承的目的:现实世界里,猪的确只会跑,但在雷军的眼里,站在风口的猪就会飞,这就需要赋予这只猪更多的能力,通过抽象类是无法实现的,只能通过接口。
+
+第三,实现多态。
+
+什么是多态呢?通俗的理解,就是同一个事件发生在不同的对象上会产生不同的结果,鼠标左键点击窗口上的 X 号可以关闭窗口,点击超链接却可以打开新的网页。
+
+多态可以通过继承(`extends`)的关系实现,也可以通过接口的形式实现。
+
+Shape 接口表示一个形状。
+
+```java
+public interface Shape {
+ String name();
+}
+```
+
+Circle 类实现了 Shape 接口,并重写了 `name()` 方法。
+
+```java
+public class Circle implements Shape {
+ @Override
+ public String name() {
+ return "圆";
+ }
+}
+```
+
+Square 类也实现了 Shape 接口,并重写了 `name()` 方法。
+
+```java
+public class Square implements Shape {
+ @Override
+ public String name() {
+ return "正方形";
+ }
+}
+```
+
+然后来看测试类。
+
+```java
+List shapes = new ArrayList<>();
+Shape circleShape = new Circle();
+Shape squareShape = new Square();
+
+shapes.add(circleShape);
+shapes.add(squareShape);
+
+for (Shape shape : shapes) {
+ System.out.println(shape.name());
+}
+```
+
+这就实现了多态,变量 circleShape、squareShape 的引用类型都是 Shape,但执行 `shape.name()` 方法的时候,Java 虚拟机知道该去调用 Circle 的 `name()` 方法还是 Square 的 `name()` 方法。
+
+说一下多态存在的 3 个前提:
+
+1、要有继承关系,比如说 Circle 和 Square 都实现了 Shape 接口。
+2、子类要重写父类的方法,Circle 和 Square 都重写了 `name()` 方法。
+3、父类引用指向子类对象,circleShape 和 squareShape 的类型都为 Shape,但前者指向的是 Circle 对象,后者指向的是 Square 对象。
+
+然后,我们来看一下测试结果:
+
+```
+圆
+正方形
+```
+
+也就意味着,尽管在 for 循环中,shape 的类型都为 Shape,但在调用 `name()` 方法的时候,它知道 Circle 对象应该调用 Circle 类的 `name()` 方法,Square 对象应该调用 Square 类的 `name()` 方法。
+
+“哦,我理解了哥。那我再问一下,抽象类和接口有什么差别呢?”
+
+“哇,三妹呀,你这个问题恰到好处,问到了点子上。”我不由得为三妹竖起了大拇指。
+
+1)语法层面上
+
+- 接口中不能有 public 和 protected 修饰的方法,抽象类中可以有。
+- 接口中的变量只能是隐式的常量,抽象类中可以有任意类型的变量。
+- 一个类只能继承一个抽象类,但却可以实现多个接口。
+
+2)设计层面上
+
+抽象类是对类的一种抽象,继承抽象类的子类和抽象类本身是一种 `is-a` 的关系。
+
+接口是对类的某种行为的一种抽象,接口和类之间并没有很强的关联关系,举个例子来说,所有的类都可以实现 `Serializable` 接口,从而具有序列化的功能,但不能说所有的类和 Serializable 之间是 `is-a` 的关系。
+
+--------
+
+“好了,三妹,接口就学到这吧,下课,哈哈哈。”我抬起头看了看窗外,天气还真不错,希望五一的张家界也能晴空万里~
+
+“嗯嗯,哥,休息下吧,我给你揉揉肩膀~~~~”不得不说,有个贴心的妹妹还真的是挺舒服。。。。。
\ No newline at end of file
diff --git a/docs/object-class/pass-by-value.md b/docs/object-class/pass-by-value.md
new file mode 100644
index 0000000000000000000000000000000000000000..29da0e1c9f95821dc6e848f34bebf240ed7206ce
--- /dev/null
+++ b/docs/object-class/pass-by-value.md
@@ -0,0 +1,135 @@
+## 值传递与引用传递
+
+“哥,说说 Java 到底是值传递还是引用传递吧?”三妹一脸的困惑,看得出来她被这个问题折磨得不轻。
+
+“说实在的,我在一开始学 Java 的时候也被这个问题折磨得够呛,总以为基本数据类型在传参的时候是值传递,而引用类型是引用传递。”我对三妹袒露了心声,为的就是让她不再那么焦虑,她哥当年也是这么过来的。
+
+ C 语言是很多编程语言的母胎,包括 Java,那么对于 C 语言来说,所有的方法参数都是“通过值”传递的,也就是说,传递给被调用方法的参数值存放在临时变量中,而不是存放在原来的变量中。这就意味着,被调用的方法不能修改调用方法中变量的值,而只能修改其私有变量的临时副本的值。
+
+Java 继承了 C 语言这一特性,因此 Java 是按照值来传递的。
+
+接下来,我们必须得搞清楚,到底什么是值传递(pass by value),什么是引用传递(pass by reference),否则,讨论 Java 到底是值传递还是引用传递就显得毫无意义。
+
+当一个参数按照值的方式在两个方法之间传递时,调用者和被调用者其实是用的两个不同的变量——被调用者中的变量(原始值)是调用者中变量的一份拷贝,对它们当中的任何一个变量修改都不会影响到另外一个变量,据说 Fortran 语言是通过引用传递的。
+
+“Fortran 语言?”三妹睁大了双眼,似乎听见了什么新的名词。
+
+“是的,Fortran 语言,1957 年由 IBM 公司开发,是世界上第一个被正式采用并流传至今的高级编程语言。”
+
+当一个参数按照引用传递的方式在两个方法之间传递时,调用者和被调用者其实用的是同一个变量,当该变量被修改时,双方都是可见的。
+
+“我们之所以容易搞不清楚 Java 到底是值传递还是引用传递,主要是因为 Java 中的两类数据类型的叫法容易引发误会,比如说 int 是基本类型,说它是值传递的,我们就很容易理解;但对于引用类型,比如说 String,说它也是值传递的时候,我们就容易弄不明白。”
+
+我们来看看基本数据类型和引用数据类型之间的差别。
+
+```java
+int age = 18;
+String name = "二哥";
+```
+
+age 是基本类型,值就保存在变量中,而 name 是引用类型,变量中保存的是对象的地址。一般称这种变量为对象的引用,引用存放在栈中,而对象存放在堆中。
+
+这里说的栈和堆,是指内存中的一块区域,和数据结构中的栈和堆不一样。栈是由编译器自动分配释放的,所以适合存放编译期就确定生命周期的数据;而堆中存放的数据,编译器是不需要知道生命周期的,创建后的回收工作由垃圾收集器来完成。
+
+“画幅图。”
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/object-class/pass-by-value-01.png)
+
+当用 = 赋值运算符改变 age 和 name 的值时。
+
+```java
+age = 16;
+name = "三妹";
+```
+
+对于基本类型 age,赋值运算符会直接改变变量的值,原来的值被覆盖。
+
+对于引用类型 name,赋值运算符会改变对象引用中保存的地址,原来的地址被覆盖,但原来的对象不会被覆盖。
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/object-class/pass-by-value-02.png)
+
+“三妹,注意听,接下来,我们来说说基本数据类型的参数传递。”
+
+Java 有 8 种基本数据类型,分别是 int、long、byte、short、float、double 、char 和 boolean,就拿 int 类型来举例吧。
+
+```java
+class PrimitiveTypeDemo {
+ public static void main(String[] args) {
+ int age = 18;
+ modify(age);
+ System.out.println(age);
+ }
+
+ private static void modify(int age1) {
+ age1 = 30;
+ }
+}
+```
+
+1)`main()` 方法中的 age 为基本类型,所以它的值 18 直接存储在变量中。
+
+2)调用 `modify()` 方法的时候,将会把 age 的值 18 复制给形参 age1。
+
+3)`modify()` 方法中,对 age1 做出了修改。
+
+4)回到 `main()` 方法中,age 的值仍然为 18,并没有发生改变。
+
+如果我们想让 age 的值发生改变,就需要这样做。
+
+```java
+class PrimitiveTypeDemo1 {
+ public static void main(String[] args) {
+ int age = 18;
+ age = modify(age);
+ System.out.println(age);
+ }
+
+ private static int modify(int age1) {
+ age1 = 30;
+ return age1;
+ }
+}
+```
+
+第一,让 `modify()` 方法有返回值;
+
+第二,使用赋值运算符重新对 age 进行赋值。
+
+“好了,再来说说引用类型的参数传递。”
+
+就以 String 为例吧。
+
+```java
+class ReferenceTypeDemo {
+ public static void main(String[] args) {
+ String name = "二哥";
+ modify(name);
+ System.out.println(name);
+ }
+
+ private static void modify(String name1) {
+ name1 = "三妹";
+ }
+}
+```
+
+在调用 `modify()` 方法的时候,形参 name1 复制了 name 的地址,指向的是堆中“二哥”的位置。
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/object-class/pass-by-value-03.png)
+
+当 `modify()` 方法调用结束后,改变了形参 name1 的地址,但 `main()` 方法中 name 并没有发生改变。
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/object-class/pass-by-value-04.png)
+
+总结:
+
+- Java 中的参数传递是按值传递的。
+- 如果参数是基本类型,传递的是基本类型的字面量值的拷贝。
+- 如果参数是引用类型,传递的是引用的对象在堆中地址的拷贝。
+
+“好了,三妹,今天的学习就到这吧。”
+
+
+
+
+
diff --git a/docs/string/constant-pool.md b/docs/string/constant-pool.md
new file mode 100644
index 0000000000000000000000000000000000000000..bc56c18c48975d6c7fee5a178164f95b7e20bd29
--- /dev/null
+++ b/docs/string/constant-pool.md
@@ -0,0 +1,86 @@
+## 字符串常量池
+
+“三妹,今天我们来学习一下字符串常量池吧,这是字符串非常中关键的一个知识点。”我话音未落,青岛路小学那边传来了嘹亮的歌声就钻进了我的耳朵,“唱 ~ 山 ~ 歌 ~”
+
+三妹说,“好呀,开始吧,哥。”
+
+“先从这道面试题开始吧!”
+
+```java
+String s = new String("二哥");
+```
+
+“这行代码创建了几个对象?”
+
+“不就一个吗?”三妹不假思索地回答。
+
+“不,两个!”我直接否定了三妹的答案,“使用 new 关键字创建一个字符串对象时,Java 虚拟机会先在字符串常量池中查找有没有‘二哥’这个字符串对象,如果有,就不会在字符串常量池中创建‘二哥’这个对象了,直接在堆中创建一个‘二哥’的字符串对象,然后将堆中这个‘二哥’的对象地址返回赋值给变量 s。”
+
+“如果没有,先在字符串常量池中创建一个‘二哥’的字符串对象,然后再在堆中创建一个‘二哥’的字符串对象,然后将堆中这个‘二哥’的字符串对象地址返回赋值给变量 s。”
+
+“为什么要先在字符串常量池中创建对象,然后再在堆上创建呢?这样不就多此一举了?”三妹敏锐地发现了问题。
+
+我回答,“由于字符串的使用频率实在是太高了,所以 Java 虚拟机为了提高性能和减少内存开销,在创建字符串对象的时候进行了一些优化,特意为字符串开辟了一个字符串常量池。”
+
+通常情况下,我们会采用双引号的方式来创建字符串对象,而不是通过 new 关键字的方式:
+
+```java
+String s = "三妹";
+```
+
+当执行 `String s = "三妹"` 时,Java 虚拟机会先在字符串常量池中查找有没有“三妹”这个字符串对象,如果有,则不创建任何对象,直接将字符串常量池中这个“三妹”的对象地址返回,赋给变量 s;如果没有,在字符串常量池中创建“三妹”这个对象,然后将其地址返回,赋给变量 s。
+
+“哦,我明白了,哥。”三妹突然插话到,“有了字符串常量池,就可以通过双引号的方式直接创建字符串对象,不用再通过 new 的方式在堆中创建对象了,对吧?”
+
+“是滴。new 的方式始终会创建一个对象,不管字符串的内容是否已经存在,而双引号的方式会重复利用字符串常量池中已经存在的对象。”我说。
+
+来看下面这个例子:
+
+```java
+String s = new String("二哥");
+String s1 = new String("二哥");
+```
+
+ 按照我们之前的分析,这两行代码会创建三个对象,字符串常量池中一个,堆上两个。
+
+再来看下面这个例子:
+
+```java
+String s = "三妹";
+String s1 = "三妹";
+```
+
+这两行代码只会创建一个对象,就是字符串常量池中的那个。这样的话,性能肯定就提高了!
+
+“那哥,字符串常量池在内存中的什么位置呢?”三妹问。
+
+我说,“三妹,你这个问题问得好呀!”
+
+在 Java 8 之前,字符串常量池在永久代中。
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/string/constant-pool-01.png)
+
+Java 8 之后,移除了永久代,字符串常量池就移到了堆中。
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/string/constant-pool-02.png)
+
+“哥,能再简单给我解释一下方法区,永久代和元空间的概念吗?有点模糊。”三妹说。
+
+我说,“可以呀。”
+
+- 方法区是 Java 虚拟机规范中的一个概念,就像是一个接口吧;
+- 永久代是 HotSpot 虚拟机中对方法的一个实现,就像是接口的实现类;
+- Java 8 的时候,移除了永久代,取而代之的是元空间,是方法区的另外一个实现。
+
+永久代是放在运行时数据区中的,所以它的大小受到 Java 虚拟机本身大小的限制,所以 Java 8 之前,会经常遇到 `java.lang.OutOfMemoryError: PremGen Space` 的异常,PremGen Space 就是方法区的意思;而元空间是直接放在内存中的,所以只受本机可用内存的限制,虽然也会发生内存溢出,但出现的几率相对之前就小了很多。
+
+“明白了吧,三妹?”我问。
+
+“嗯嗯。”三妹回答。
+
+“那关于字符串常量池,就先说这么多吧,是不是还挺有意思的。”我说。
+
+“是的,我现在是彻底搞懂了字符串常量池,哥,你真棒!”三妹说。
+
+
+PS:点击「**阅读原文**」可直达《教妹学Java》专栏的 GitHub 开源地址,记得 star 哦!
\ No newline at end of file
diff --git a/docs/string/intern.md b/docs/string/intern.md
new file mode 100644
index 0000000000000000000000000000000000000000..586c3be746b0b3babd571dc7ebc71aecf9f571a2
--- /dev/null
+++ b/docs/string/intern.md
@@ -0,0 +1,109 @@
+## intern
+
+“哥,你发给我的那篇文章我看了,结果直接把我给看得不想学 Java 了!”三妹气冲冲地说。
+
+“哪一篇啊?”看着三妹面色沉重,我关心地问到。
+
+“就是美团技术团队深入解析 `String.intern()` 那篇啊!”三妹回答。
+
+>https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html
+
+“哦,我想起来了,不挺好一篇文章嘛,深入浅出,精品中的精品,看完后你应该对 String 的 intern 彻底理解了才对呀。”
+
+“好是好,但我就是看不懂!”三妹委屈地说,“哥,还是你亲自给我讲讲吧?”
+
+“好吧,上次学的[字符串常量池](https://mp.weixin.qq.com/s/b69zXknKLIa3FWs0Yj23xA)你都搞清楚了吧?”
+
+“嗯。”三妹微微的点了点头。
+
+要理解美团技术团队的这篇文章,你只需要记住这几点内容:
+
+第一,使用双引号声明的字符串对象会保存在字符串常量池中。
+
+第二,使用 new 关键字创建的字符串对象会先从字符串常量池中找,如果没找到就创建一个,然后再在堆中创建字符串对象;如果找到了,就直接在堆中创建字符串对象。
+
+第三,针对没有使用双引号声明的字符串对象来说,就像下面代码中的 s1 那样:
+
+```java
+String s1 = new String("二哥") + new String("三妹");
+```
+
+如果想把 s1 的内容也放入字符串常量池的话,可以调用 `intern()` 方法来完成。
+
+不过,需要注意的是,Java 7 的时候,字符串常量池从永久代中移动到了堆中,虽然此时永久代还没有完全被移除。Java 8 的时候,永久代被彻底移除。
+
+这个变化也直接影响了 `String.intern()` 方法在执行时的策略,Java 7 之前,执行 `String.intern()` 方法的时候,不管对象在堆中是否已经创建,字符串常量池中仍然会创建一个内容完全相同的新对象; Java 7 之后呢,由于字符串常量池放在了堆中,执行 `String.intern()` 方法的时候,如果对象在堆中已经创建了,字符串常量池中就不需要再创建新的对象了,而是直接保存堆中对象的引用,也就节省了一部分的内存空间。
+
+“三妹,来猜猜这段代码输出的结果吧。”我说。
+
+```java
+String s1 = new String("二哥三妹");
+String s2 = s1.intern();
+System.out.println(s1 == s2);
+```
+
+“哥,这我完全猜不出啊,还是你直接解释吧。”三妹说。
+
+“好吧。”
+
+第一行代码,字符串常量池中会先创建一个“二哥三妹”的对象,然后堆中会再创建一个“二哥三妹”的对象,s1 引用的是堆中的对象。
+
+第二行代码,对 s1 执行 `intern()` 方法,该方法会从字符串常量池中查找“二哥三妹”这个字符串是否存在,此时是存在的,所以 s2 引用的是字符串常量池中的对象。
+
+也就意味着 s1 和 s2 的引用地址是不同的,一个来自堆,一个来自字符串常量池,所以输出的结果为 false。
+
+“来看一下运行结果。”我说。
+
+```
+false
+```
+
+“我来画幅图,帮助你理解下。”看到三妹惊讶的表情,我耐心地说。
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/string/intern-01.png)
+
+“这下理解了吧?”我问三妹。
+
+“嗯嗯,一下子就豁然开朗了!”三妹说。
+
+“好,我们再来看下面这段代码。”
+
+```java
+String s1 = new String("二哥") + new String("三妹");
+String s2 = s1.intern();
+System.out.println(s1 == s2);
+```
+
+“难道也输出 false ?”三妹有点不确定。
+
+“不,这段代码会输出 true。”我否定了三妹的猜测。
+
+“为啥呀?”三妹迫切地想要知道答案。
+
+第一行代码,会在字符串常量池中创建两个对象,一个是“二哥”,一个是“三妹”,然后在堆中会创建两个匿名对象“二哥”和“三妹”(可以暂时忽略),最后还有一个“二哥三妹”的对象,s1 引用的是堆中“二哥三妹”这个对象。
+
+第二行代码,对 s1 执行 `intern()` 方法,该方法会从字符串常量池中查找“二哥三妹”这个对象是否存在,此时不存在的,但堆中已经存在了,所以字符串常量池中保存的是堆中这个“二哥三妹”对象的引用,也就是说,s2 和 s1 的引用地址是相同的,所以输出的结果为 true。
+
+“来看一下运行结果。”我胸有成竹地说。
+
+```
+true
+```
+
+“我再来画幅图,帮助你理解下。”
+
+![](https://cdn.jsdelivr.net/gh/itwanger/Tech-Sister-Learn-Java/images/string/intern-02.png)
+
+“哇,我明白了!”三妹长舒一口气,大有感慨 intern 也没什么难理解的意味。
+
+不过需要注意的是,尽管 intern 可以确保所有具有相同内容的字符串共享相同的内存空间,但也不要烂用 intern,因为任何的缓存池都是有大小限制的,不能无缘无故就占用了相对稀缺的缓存空间,导致其他字符串没有坑位可占。
+
+另外,字符串常量池本质上是一个固定大小的 StringTable,如果放进去的字符串过多,就会造成严重的哈希冲突,从而导致链表变长,链表变长也就意味着字符串常量池的性能会大幅下降,因为要一个一个找是需要花费时间的。
+
+“好了,三妹,关于 String 的 intern 就讲到这吧,这次理解了吧?”我问。
+
+“哥,你真棒!”
+
+看到三妹一点一滴的进步,我也感到由衷的开心。
+
+PS:点击「**阅读原文**」可直达《教妹学Java》专栏的 GitHub 开源地址,记得 star 哦!
\ No newline at end of file
diff --git a/docs/string/source.md b/docs/string/source.md
new file mode 100644
index 0000000000000000000000000000000000000000..c4cbafa237186f5b1c9caad27471d1595d997718
--- /dev/null
+++ b/docs/string/source.md
@@ -0,0 +1,131 @@
+## 字符串源码分析
+
+我正坐在沙发上津津有味地读刘欣大佬的《码农翻身》——Java 帝国这一章,门铃响了。起身打开门一看,是三妹,她从学校回来了。
+
+“三妹,你回来的真及时,今天我们打算讲 Java 中的字符串呢。”等三妹换鞋的时候我说。
+
+“哦,可以呀,哥。听说字符串的细节特别多,什么字符串常量池了、字符串不可变性了、字符串拼接了、字符串长度限制了等等,你最好慢慢讲,否则我可能一时半会消化不了。”三妹的态度显得很诚恳。
+
+“嗯,我已经想好了,今天就只带你大概认识一下字符串,具体的细节咱们后面再慢慢讲,保证你能及时消化。”
+
+“好,那就开始吧。”三妹已经准备好坐在了电脑桌的边上。
+
+我应了一声后走到电脑桌前坐下来,顺手打开 Intellij IDEA,并找到了 String 的源码。
+
+```java
+public final class String
+ implements java.io.Serializable, Comparable, CharSequence {
+ @Stable
+ private final byte[] value;
+ private final byte coder;
+ private int hash;
+}
+```
+
+“第一,String 类是 final 的,意味着它不能被子类继承。”
+
+“第二,String 类实现了 Serializable 接口,意味着它可以序列化。”
+
+“第三,String 类实现了 Comparable 接口,意味着最好不要用‘==’来比较两个字符串是否相等,而应该用 `compareTo()` 方法去比较。”
+
+“第四,StringBuffer、StringBuilder 和 String 一样,都实现了 CharSequence 接口,所以它们仨属于近亲。由于 String 是不可变的,所以遇到字符串拼接的时候就可以考虑一下 String 的另外两个好兄弟,StringBuffer 和 StringBuilder,它俩是可变的。”
+
+“第五,Java 9 以前,String 是用 char 型数组实现的,之后改成了 byte 型数组实现,并增加了 coder 来表示编码。在 Latin1 字符为主的程序里,可以把 String 占用的内存减少一半。当然,天下没有免费的午餐,这个改进在节省内存的同时引入了编码检测的开销。”
+
+“第六,每一个字符串都会有一个 hash 值,这个哈希值在很大概率是不会重复的,因此 String 很适合来作为 HashMap 的键值。”
+
+“String 可能是 Java 中使用频率最高的引用类型了,因此 String 类的设计者可以说是用心良苦。”
+
+比如说 String 的不可变性。
+
+- String 类被 final 关键字修饰,所以它不会有子类,这就意味着没有子类可以重写它的方法,改变它的行为。
+- String 类的数据存储在 `byte[]` 数组中,而这个数组也被 final 关键字修饰了,这就表示 String 对象是没法被修改的,只要初始化一次,值就确定了。
+
+“哥,为什么要这样设计呢?”三妹有些不解。
+
+“我先简单来说下,三妹,能懂最好,不能懂后面再细说。”
+
+第一,可以保证 String 对象的安全性,避免被篡改,毕竟像密码这种隐私信息一般就是用字符串存储的。
+
+第二,保证哈希值不会频繁变更。毕竟要经常作为哈希表的键值,经常变更的话,哈希表的性能就会很差劲。
+
+第三,可以实现字符串常量池。
+
+“由于字符串的不可变性,String 类的一些方法实现最终都返回了新的字符串对象。”等三妹稍微缓了一会后,我继续说到。
+
+“就拿 `substring()` 方法来说。”
+
+```java
+public String substring(int beginIndex) {
+ if (beginIndex < 0) {
+ throw new StringIndexOutOfBoundsException(beginIndex);
+ }
+ int subLen = length() - beginIndex;
+ if (subLen < 0) {
+ throw new StringIndexOutOfBoundsException(subLen);
+ }
+ if (beginIndex == 0) {
+ return this;
+ }
+ return isLatin1() ? StringLatin1.newString(value, beginIndex, subLen)
+ : StringUTF16.newString(value, beginIndex, subLen);
+}
+
+// StringLatin1.newString
+public static String newString(byte[] val, int index, int len) {
+ return new String(Arrays.copyOfRange(val, index, index + len),
+ LATIN1);
+}
+
+// UTF16.newString
+public static String newString(byte[] val, int index, int len) {
+ if (String.COMPACT_STRINGS) {
+ byte[] buf = compress(val, index, len);
+ if (buf != null) {
+ return new String(buf, LATIN1);
+ }
+ }
+ int last = index + len;
+ return new String(Arrays.copyOfRange(val, index << 1, last << 1), UTF16);
+}
+```
+
+`substring()` 方法用于截取字符串,不管是 Latin1 字符还是 UTF16 字符,最终返回的都是 new 出来的新字符串对象。
+
+“还有 `concat()` 方法。”
+
+```java
+public String concat(String str) {
+ int olen = str.length();
+ if (olen == 0) {
+ return this;
+ }
+ if (coder() == str.coder()) {
+ byte[] val = this.value;
+ byte[] oval = str.value;
+ int len = val.length + oval.length;
+ byte[] buf = Arrays.copyOf(val, len);
+ System.arraycopy(oval, 0, buf, val.length, oval.length);
+ return new String(buf, coder);
+ }
+ int len = length();
+ byte[] buf = StringUTF16.newBytesFor(len + olen);
+ getBytes(buf, 0, UTF16);
+ str.getBytes(buf, len, UTF16);
+ return new String(buf, UTF16);
+}
+```
+
+`concat()` 方法用于拼接字符串,不管编码是否一致,最终也返回的是新的字符串对象。
+
+“`replace()` 替换方法其实也一样,三妹,你可以自己一会看一下源码,也是返回新的字符串对象。”
+
+“这就意味着,不管是截取、拼接,还是替换,都不是在原有的字符串上进行的,而是重新生成了新的字符串对象。也就是说,这些操作执行过后,**原来的字符串对象并没有发生改变**。”
+
+“三妹,你记住,String 对象一旦被创建后就固定不变了,对 String 对象的任何修改都不会影响到原来的字符串对象,都会生成新的字符串对象。”
+
+“嗯嗯,记住了,哥。”三妹很乖。
+
+“那今天就先讲到这吧,后面我们再对每一个细分领域深入地展开一下。你可以找一些资料先预习下,我出去散会心。。。。。”
+
+PS:点击「**阅读原文**」可直达《教妹学Java》专栏的 GitHub 开源地址,记得 star 哦!
\ No newline at end of file
diff --git a/images/array/print-01.png b/images/array/print-01.png
new file mode 100644
index 0000000000000000000000000000000000000000..c1b4f87c4ee3a5febd01a812ca59c556060d1d1a
Binary files /dev/null and b/images/array/print-01.png differ
diff --git a/images/array/print-02.png b/images/array/print-02.png
new file mode 100644
index 0000000000000000000000000000000000000000..543e1099fe8119a0a7877522a8f0b23259260b62
Binary files /dev/null and b/images/array/print-02.png differ
diff --git a/images/array/print-03.png b/images/array/print-03.png
new file mode 100644
index 0000000000000000000000000000000000000000..82672572459e1adff40a4d90d280dc5f7c2803d5
Binary files /dev/null and b/images/array/print-03.png differ
diff --git a/images/object-class/abstract-01.png b/images/object-class/abstract-01.png
new file mode 100644
index 0000000000000000000000000000000000000000..64fb4210917ac3763491b6c87de8320ba3063647
Binary files /dev/null and b/images/object-class/abstract-01.png differ
diff --git a/images/object-class/abstract-02.png b/images/object-class/abstract-02.png
new file mode 100644
index 0000000000000000000000000000000000000000..213ce81236580592879ad2028e7610dabf41ea04
Binary files /dev/null and b/images/object-class/abstract-02.png differ
diff --git a/images/object-class/abstract-03.png b/images/object-class/abstract-03.png
new file mode 100644
index 0000000000000000000000000000000000000000..dd2dc7fc5e4c8bc938ec3ba7ab4e524f3d6aef73
Binary files /dev/null and b/images/object-class/abstract-03.png differ
diff --git a/images/object-class/abstract-04.png b/images/object-class/abstract-04.png
new file mode 100644
index 0000000000000000000000000000000000000000..058e04016d27db3b19665e7f3296db7580540273
Binary files /dev/null and b/images/object-class/abstract-04.png differ
diff --git a/images/object-class/abstract-05.png b/images/object-class/abstract-05.png
new file mode 100644
index 0000000000000000000000000000000000000000..1fe4d7a7b30435e718990e4f63e85623a437119f
Binary files /dev/null and b/images/object-class/abstract-05.png differ
diff --git a/images/object-class/box-01.png b/images/object-class/box-01.png
new file mode 100644
index 0000000000000000000000000000000000000000..f3bcde846810b2c89e7d823d6903cf4bf13a4d31
Binary files /dev/null and b/images/object-class/box-01.png differ
diff --git a/images/object-class/deep-copy-01.png b/images/object-class/deep-copy-01.png
new file mode 100644
index 0000000000000000000000000000000000000000..6cd9eb2e4d0e2f24fea33eed30bfce1e3e4ae556
Binary files /dev/null and b/images/object-class/deep-copy-01.png differ
diff --git a/images/object-class/deep-copy-02.png b/images/object-class/deep-copy-02.png
new file mode 100644
index 0000000000000000000000000000000000000000..a64ab815fbeef8cd7efcb1f925740623ae604807
Binary files /dev/null and b/images/object-class/deep-copy-02.png differ
diff --git a/images/object-class/deep-copy-03.png b/images/object-class/deep-copy-03.png
new file mode 100644
index 0000000000000000000000000000000000000000..848030d936f4495cd83243220030c08bdbc3a7d7
Binary files /dev/null and b/images/object-class/deep-copy-03.png differ
diff --git a/images/object-class/interface-01.png b/images/object-class/interface-01.png
new file mode 100644
index 0000000000000000000000000000000000000000..eea89f67ee65c3ef61fe765d040aed489f2ec881
Binary files /dev/null and b/images/object-class/interface-01.png differ
diff --git a/images/object-class/interface-02.png b/images/object-class/interface-02.png
new file mode 100644
index 0000000000000000000000000000000000000000..cc8d803d75728deb2431a126470902e794fbac01
Binary files /dev/null and b/images/object-class/interface-02.png differ
diff --git a/images/object-class/interface-03.png b/images/object-class/interface-03.png
new file mode 100644
index 0000000000000000000000000000000000000000..611a3479487c059f412fd77704ba02c2264f5612
Binary files /dev/null and b/images/object-class/interface-03.png differ
diff --git a/images/object-class/interface-04.png b/images/object-class/interface-04.png
new file mode 100644
index 0000000000000000000000000000000000000000..4c47b0cdd7bcd0ef48f367e90c92e68d9abedc30
Binary files /dev/null and b/images/object-class/interface-04.png differ
diff --git a/images/object-class/interface-05.png b/images/object-class/interface-05.png
new file mode 100644
index 0000000000000000000000000000000000000000..58bacad3e57f0d74e654fc651709ae86110ad1fc
Binary files /dev/null and b/images/object-class/interface-05.png differ
diff --git a/images/object-class/interface-06.png b/images/object-class/interface-06.png
new file mode 100644
index 0000000000000000000000000000000000000000..4d26823a7651c42818635da313085b1c5fed683f
Binary files /dev/null and b/images/object-class/interface-06.png differ
diff --git a/images/object-class/interface-07.png b/images/object-class/interface-07.png
new file mode 100644
index 0000000000000000000000000000000000000000..def25e8ae8a0ff048d8f1a4de705b4a50e4da9e8
Binary files /dev/null and b/images/object-class/interface-07.png differ
diff --git a/images/object-class/pass-by-value-01.png b/images/object-class/pass-by-value-01.png
new file mode 100644
index 0000000000000000000000000000000000000000..9725fdca6ce956b7fac5b8eb405b2c5a59ea198c
Binary files /dev/null and b/images/object-class/pass-by-value-01.png differ
diff --git a/images/object-class/pass-by-value-02.png b/images/object-class/pass-by-value-02.png
new file mode 100644
index 0000000000000000000000000000000000000000..463475e1efb893454af9e05fc83b405966103b97
Binary files /dev/null and b/images/object-class/pass-by-value-02.png differ
diff --git a/images/object-class/pass-by-value-03.png b/images/object-class/pass-by-value-03.png
new file mode 100644
index 0000000000000000000000000000000000000000..eaed16da40cc6c5983eef23127817b95a2529b82
Binary files /dev/null and b/images/object-class/pass-by-value-03.png differ
diff --git a/images/object-class/pass-by-value-04.png b/images/object-class/pass-by-value-04.png
new file mode 100644
index 0000000000000000000000000000000000000000..41e1c15a3bc6252495a9f0f7a4360c8f750b0180
Binary files /dev/null and b/images/object-class/pass-by-value-04.png differ
diff --git a/images/string/constant-pool-01.png b/images/string/constant-pool-01.png
new file mode 100644
index 0000000000000000000000000000000000000000..b0596c39fa65df21f3db5709cc2b02ce46adb006
Binary files /dev/null and b/images/string/constant-pool-01.png differ
diff --git a/images/string/constant-pool-02.png b/images/string/constant-pool-02.png
new file mode 100644
index 0000000000000000000000000000000000000000..83ff6a6f42230473f6922915ac3bceac2f1f97c9
Binary files /dev/null and b/images/string/constant-pool-02.png differ
diff --git a/images/string/intern-01.png b/images/string/intern-01.png
new file mode 100644
index 0000000000000000000000000000000000000000..d07a9b79994780f603861daa8cb7a41f482f14b7
Binary files /dev/null and b/images/string/intern-01.png differ
diff --git a/images/string/intern-02.png b/images/string/intern-02.png
new file mode 100644
index 0000000000000000000000000000000000000000..8c2d35b06ffeed8b8f7440c75ec9b4c13dac0158
Binary files /dev/null and b/images/string/intern-02.png differ