Skip to content

Commit 030850b

Browse files
committed
重要知识点
1 parent 043a64f commit 030850b

18 files changed

+770
-316
lines changed

README.md

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@
9898

9999
## Java概述及环境配置
100100

101-
- [Java简介](docs/overview/what-is-java.md)
101+
- [《二哥Java进阶之路》小册简介](overview/readme.md)
102+
- [一文让你彻底了解Java(简史、特性、前景)](docs/overview/what-is-java.md)
102103
- [安装Java开发工具包JDK](docs/overview/jdk-install-config.md)
103104
- [安装集成开发环境Intellij IDEA](docs/overview/IDEA-install-config.md)
104105
- [编写第一个Java程序:Hello World](docs/overview/hello-world.md)
@@ -228,18 +229,14 @@
228229

229230
## 重要知识点
230231

231-
- [Java命名规范](docs/basic-extra-meal/java-naming.md)
232-
- [彻底弄懂Java中的Unicode和UTF-8编码](docs/basic-extra-meal/java-unicode.md)
233-
- [深入剖析Java中的拆箱和装箱](docs/basic-extra-meal/box.md)
234-
- [一文彻底讲明白的Java中的浅拷贝与深拷贝](docs/basic-extra-meal/deep-copy.md)
235-
- [深入理解Java中的hashCode方法](docs/basic-extra-meal/hashcode.md)
236-
- [为什么重写equals方法的时候必须要重写hashCode方法?](docs/basic-extra-meal/equals-hashcode.md)
237-
- [Java重写(Overriding)时应当遵守的11条规则](docs/basic-extra-meal/Overriding.md)
232+
- [Java命名规范:编写可读性强的代码](docs/basic-extra-meal/java-naming.md)
233+
- [解决中文乱码:字符编码全攻略 - ASCII、Unicode、UTF-8、GB2312详解](docs/basic-extra-meal/java-unicode.md)
234+
- [深入浅出Java拆箱与装箱:理解自动类型转换与包装类的关系](docs/basic-extra-meal/box.md)
235+
- [深入理解Java浅拷贝与深拷贝:实战案例与技巧](docs/basic-extra-meal/deep-copy.md)
236+
- [Java hashCode方法解析:C++实现的高效本地方法](docs/basic-extra-meal/hashcode.md)
238237
- [Java到底是值传递还是引用传递?](docs/basic-extra-meal/pass-by-value.md)
239-
- [为什么JDK源码中,无限循环大多使用for(;;)而不是while(true)?](docs/basic-extra-meal/jdk-while-for-wuxian-xunhuan.md)
240-
- [instanceof关键字是如何实现的?](docs/basic-extra-meal/instanceof-jvm.md)
241-
- [Java不能实现真正泛型的原因是什么?](docs/basic-extra-meal/true-generic.md)
242-
- [大白话说清楚Java反射:入门、使用、原理](docs/basic-extra-meal/fanshe.md)
238+
- [Java 泛型背后的秘密:为什么无法实现真正的泛型?](docs/basic-extra-meal/true-generic.md)
239+
- [Java 反射详解:动态创建实例、调用方法和访问字段](docs/basic-extra-meal/fanshe.md)
243240

244241
## Java并发编程
245242

docs/.vuepress/sidebar.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -273,11 +273,7 @@ export const sidebarConfig = sidebar({
273273
"box",
274274
"deep-copy",
275275
"hashcode",
276-
"equals-hashcode",
277-
"Overriding",
278276
"pass-by-value",
279-
"jdk-while-for-wuxian-xunhuan",
280-
"instanceof-jvm",
281277
"true-generic",
282278
"fanshe",
283279
],

docs/basic-extra-meal/box.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
---
2-
title: 深入剖析Java中的拆箱和装箱
2+
title: 深入浅出Java拆箱与装箱:理解自动类型转换与包装类的关系
33
shortTitle: 深入剖析Java中的拆箱和装箱
44
category:
55
- Java核心
66
tag:
77
- Java重要知识点
8-
description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,深入剖析Java中的拆箱和装箱
8+
description: 拆箱与装箱是Java自动类型转换的重要概念。拆箱是将包装类对象转换为其对应的基本数据类型,而装箱是将基本数据类型转换为相应的包装类对象。本文详细介绍了拆箱和装箱的过程、原理以及Java中的包装类,以帮助您更好地理解这两个概念
9+
author: 沉默王二
910
head:
1011
- - meta
1112
- name: keywords
1213
content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java进阶之路,Java入门,教程,装箱,拆箱,包装类型
1314
---
1415

16+
# 13.3 深入剖析Java中的拆箱和装箱
1517

16-
“哥,听说 Java 的每个基本类型都对应了一个包装类型,比如说 int 的包装类型为 Integer,double 的包装类型为 Double,是这样吗?”从三妹这句话当中,能听得出来,她已经提前预习这块内容了。
18+
“哥,听说 Java 的每个[基本类型](https://tobebetterjavaer.com/basic-grammar/basic-data-type.html)都对应了一个包装类型,比如说 int 的包装类型为 Integer,double 的包装类型为 Double,是这样吗?”从三妹这句话当中,能听得出来,她已经提前预习这块内容了。
1719

1820
“是的,三妹。基本类型和包装类型的区别主要有以下 4 点,我来带你学习一下。”我回答说。我们家的斜对面刚好是一所小学,所以时不时还能听到朗朗的读书声,让人心情非常愉快。
1921

@@ -52,9 +54,9 @@ class Writer {
5254

5355
“那为什么 POJO 的字段必须要用包装类型呢?”三妹问。
5456

55-
“《阿里巴巴 Java 开发手册》上有详细的说明,你看。”我打开 PDF,并翻到了对应的内容,指着屏幕念道。
57+
“《[阿里巴巴 Java 开发手册](https://tobebetterjavaer.com/pdf/ali-java-shouce.html)》上有详细的说明,你看。”我打开 PDF,并翻到了对应的内容,指着屏幕念道。
5658

57-
>数据库的查询结果可能是 null,如果使用基本类型的话,因为要自动拆箱,就会抛出 NullPointerException 的异常。
59+
>数据库的查询结果可能是 null,如果使用基本类型的话,因为要自动拆箱,就会抛出 [NullPointerException 的异常](https://tobebetterjavaer.com/exception/npe.html)
5860
5961
“什么是自动拆箱呢?”
6062

@@ -73,11 +75,11 @@ List<Integer> list = new ArrayList<>();
7375

7476
“为什么呢?”三妹及时地问道。
7577

76-
因为泛型在编译时会进行类型擦除,最后只保留原始类型,而原始类型只能是 Object 类及其子类——基本类型是个例外。”
78+
因为[泛型](https://tobebetterjavaer.com/basic-extra-meal/generic.html)在编译时会进行类型擦除,最后只保留原始类型,而原始类型只能是 Object 类及其子类——基本类型是个例外。”
7779

7880
“那,接下来,我们来说第三点,**基本类型比包装类型更高效**。”我喝了一口茶继续说道。
7981

80-
“作为局部变量时,基本类型在栈中直接存储的具体数值,而包装类型则存储的是堆中的引用。”我一边说着,一边打开 `draw.io` 画起了图。
82+
“作为局部变量时,基本类型在栈中直接存储的具体数值,而包装类型则存储的是堆中的引用。”我一边说着,一边打开 [`draw.io`](https://app.diagrams.net/) 画起了图。
8183

8284
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/core-points/box-01.png)
8385

@@ -113,7 +115,7 @@ System.out.println(chenmo.equals(wanger )); // true
113115

114116
“两个包装类型在使用“==”进行判断的时候,判断的是其指向的地址是否相等,由于是两个对象,所以地址是不同的。”
115117

116-
“而 chenmo.equals(wanger) 的输出结果为 true,是因为 equals() 方法内部比较的是两个 int 值是否相等。”
118+
“而 `chenmo.equals(wanger)` 的输出结果为 true,是因为 `equals()` 方法内部比较的是两个 int 值是否相等。”
117119

118120
```java
119121
private final int value;
@@ -193,7 +195,7 @@ System.out.println(c == d);
193195

194196
“为什么会这样呀?”三妹急切地问。
195197

196-
“你说的没错,自动装箱是通过 Integer.valueOf() 完成的,我们来看看这个方法的源码就明白为什么了。”
198+
“你说的没错,自动装箱是通过 `Integer.valueOf()` 完成的,我们来看看这个方法的源码就明白为什么了。”
197199

198200
```java
199201
public static Integer valueOf(int i) {
@@ -203,7 +205,7 @@ public static Integer valueOf(int i) {
203205
}
204206
```
205207

206-
是不是看到了一个之前从来没见过的类——IntegerCache?
208+
是不是看到了一个之前从来没见过的类——[IntegerCache](https://tobebetterjavaer.com/basic-extra-meal/int-cache.html)
207209

208210
“难道说是 Integer 的缓存类?”三妹做出了自己的判断。
209211

docs/basic-extra-meal/deep-copy.md

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,33 @@
11
---
2-
title: 一文彻底讲明白的Java中的浅拷贝与深拷贝
3-
shortTitle: Java中的浅拷贝与深拷贝
2+
title: 深入理解Java浅拷贝与深拷贝:实战案例与技巧
3+
shortTitle: 深入理解Java浅拷贝与深拷贝
44
category:
55
- Java核心
66
tag:
77
- Java重要知识点
8-
description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,一文彻底讲明白的Java中的浅拷贝与深拷贝
8+
description: 本文详细讨论了Java中的浅拷贝和深拷贝概念,解析了它们如何在实际编程中应用。文章通过实例演示了如何实现浅拷贝与深拷贝,以帮助读者更好地理解这两种拷贝方式在Java编程中的作用与应用场景。
9+
author: 沉默王二
910
head:
1011
- - meta
1112
- name: keywords
1213
content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java进阶之路,Java入门,教程,java,深拷贝,浅拷贝
1314
---
1415

16+
# 13.4 深入理解Java浅拷贝与深拷贝
17+
1518
“哥,听说浅拷贝和深拷贝是 Java 面试中经常会被问到的一个问题,是这样吗?”
1619

17-
“还真的是,而且了解浅拷贝和深拷贝的原理,对 Java 是值传递还是引用传递也会有更深的理解。”我肯定地回答。
20+
“还真的是,而且了解浅拷贝和深拷贝的原理,对 [Java 是值传递还是引用传递](https://tobebetterjavaer.com/basic-extra-meal/pass-by-value.html)也会有更深的理解。”我肯定地回答。
1821

1922
“不管是浅拷贝还是深拷贝,都可以通过调用 Object 类的 `clone()` 方法来完成。”我一边说,一边打开 Intellij IDEA,并找到了 `clone()` 方法的源码。
2023

2124
```java
22-
@HotSpotIntrinsicCandidate
2325
protected native Object clone() throws CloneNotSupportedException;
2426
```
2527

26-
其中 `@HotSpotIntrinsicCandidate` 是 Java 9 引入的一个注解,被它标注的方法,在 HotSpot 虚拟机中会有一套高效的实现。需要注意的是,`clone()` 方法同时是一个本地(`native`)方法,它的具体实现会交给 HotSpot 虚拟机,那就意味着虚拟机在运行该方法的时候,会将其替换为更高效的 C/C++ 代码,进而调用操作系统去完成对象的克隆工作。
28+
需要注意的是,`clone()` 方法同时是一个本地(`native`)方法,它的具体实现会交给 HotSpot 虚拟机,那就意味着虚拟机在运行该方法的时候,会将其替换为更高效的 C/C++ 代码,进而调用操作系统去完成对象的克隆工作。
29+
30+
>Java 9 后,该方法会被标注 `@HotSpotIntrinsicCandidate` 注解,被该注解标注的方法,在 HotSpot 虚拟机中会有一套高效的实现。
2731
2832
“哥,那你就先说浅拷贝吧!”
2933

@@ -48,7 +52,6 @@ class Writer implements Cloneable{
4852

4953
Writer 类有两个字段,分别是 int 类型的 age,和 String 类型的 name。然后重写了 `toString()` 方法,方便打印对象的具体信息。
5054

51-
5255
“为什么要实现 Cloneable 接口呢?”三妹开启了十万个为什么的模式。
5356

5457
Cloneable 接口是一个标记接口,它肚子里面是空的:
@@ -314,9 +317,9 @@ writer2:Writer@6d00a15d age=18, name='二哥', book=Book@51efea79 bookName='
314317

315318
“那有没有好的办法呢?”三妹急切的问。
316319

317-
“当然有了,利用序列化。”我胸有成竹的回答,“序列化是将对象写到流中便于传输,而反序列化则是将对象从流中读取出来。”
320+
“当然有了,利用[序列化](https://tobebetterjavaer.com/io/serialize.html)。”我胸有成竹的回答,“序列化是将对象写到流中便于传输,而反序列化则是将对象从流中读取出来。”
318321

319-
“写入流中的对象就是对原始对象的拷贝。需要注意的是,每个要序列化的类都要实现 Serializable 接口,该接口和 Cloneable 接口类似,都是标记型接口。”
322+
“写入流中的对象就是对原始对象的拷贝。需要注意的是,每个要序列化的类都要实现 [Serializable 接口](https://tobebetterjavaer.com/io/Serializbale.html),该接口和 Cloneable 接口类似,都是标记型接口。”
320323

321324
来看例子。
322325

docs/basic-extra-meal/equals-hashcode.md

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,29 @@ head:
1212
content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java进阶之路,Java入门,教程,java,hashcode,equals
1313
---
1414

15+
# 13.5 为什么重写equals的时候必须重写hashCode
16+
1517
“二哥,我在读《Effective Java》 的时候,第 11 条规约说重写 equals 的时候必须要重写 hashCode 方法,这是为什么呀?”三妹单刀直入地问。
1618

1719
“三妹啊,这个问题问得非常好,因为它也是面试中经常考的一个知识点。今天哥就带你来梳理一下。”我说。
1820

19-
Java 是一门面向对象的编程语言,所有的类都会默认继承自 Object 类,而 Object 的中文意思就是“对象”。
21+
Java 是一门面向对象的编程语言,所有的类都会默认继承自 [Object 类](https://tobebetterjavaer.com/oo/object-class.html),而 Object 的中文意思就是“对象”。
2022

2123
Object 类中有这么两个方法:
2224

2325
```java
2426
public native int hashCode();
2527

2628
public boolean equals(Object obj) {
27-
return (this == obj);
29+
return (this == obj);
2830
}
2931
```
30-
1)hashCode 方法
32+
33+
**1)hashCode 方法**
3134

3235
这是一个本地方法,用来返回对象的哈希值(一个整数)。在 Java 程序执行期间,对同一个对象多次调用该方法必须返回相同的哈希值。
3336

34-
2)equals 方法
37+
**2)equals 方法**
3538

3639
对于任何非空引用 x 和 y,当且仅当 x 和 y 引用的是同一个对象时,equals 方法才返回 true。
3740

@@ -41,13 +44,13 @@ public boolean equals(Object obj) {
4144

4245
第一,如果两个对象调用 equals 方法返回的结果为 true,那么两个对象调用 hashCode 方法返回的结果也必然相同——来自 hashCode 方法的 doc 文档。
4346

44-
第二,每当重写 equals 方法时,hashCode 方法也需要重写,以便维护上一条规约。
47+
第二,每当重写 [equals 方法](https://tobebetterjavaer.com/string/equals.html)时,[hashCode 方法](https://tobebetterjavaer.com/basic-extra-meal/hashcode.html)也需要重写,以便维护上一条规约。
4548

4649
“哦,这样讲的话,两个方法确实关联上了,但究竟是为什么呢?”三妹抛出了终极一问。
4750

48-
“hashCode 方法的作用是用来获取哈希值,而该哈希值的作用是用来确定对象在哈希表中的索引位置。”我说。
51+
“hashCode 方法的作用是用来获取哈希值,而该哈希值的作用是用来确定[对象在哈希表中的索引位置(HashMap 的时候会讲)](https://tobebetterjavaer.com/collection/hashmap.html)。”我说。
4952

50-
哈希表的典型代表就是 HashMap,它存储的是键值对,能根据键快速地检索出对应的值。
53+
哈希表的典型代表就是 [HashMap](https://tobebetterjavaer.com/collection/hashmap.html),它存储的是键值对,能根据键快速地检索出对应的值。
5154

5255
```java
5356
public V get(Object key) {
@@ -61,21 +64,28 @@ public V get(Object key) {
6164
```java
6265
final HashMap.Node<K,V> getNode(int hash, Object key) {
6366
HashMap.Node<K,V>[] tab; HashMap.Node<K,V> first, e; int n; K k;
67+
// 判断 HashMap 的 table 是否为 null 以及 table 长度是否大于 0
6468
if ((tab = table) != null && (n = tab.length) > 0 &&
69+
// 根据 hash 值计算出在 table 中的索引位置,并获取第一个节点
6570
(first = tab[(n - 1) & hash]) != null) {
66-
if (first.hash == hash && // always check first node
71+
// 判断第一个节点的 hash 值是否与给定 hash 相等,如果相等,则检查 key 是否相等
72+
if (first.hash == hash &&
6773
((k = first.key) == key || (key != null && key.equals(k))))
6874
return first;
75+
// 如果不相等,获取当前节点的下一个节点
6976
if ((e = first.next) != null) {
77+
// 如果当前节点为 TreeNode 类型(红黑树),则调用 TreeNode 的 getTreeNode 方法查找
7078
if (first instanceof HashMap.TreeNode)
7179
return ((HashMap.TreeNode<K,V>)first).getTreeNode(hash, key);
80+
// 如果不是红黑树节点,遍历链表查找
7281
do {
7382
if (e.hash == hash &&
7483
((k = e.key) == key || (key != null && key.equals(k))))
7584
return e;
7685
} while ((e = e.next) != null);
7786
}
7887
}
88+
// 如果没有找到对应的节点,则返回 null
7989
return null;
8090
}
8191
```
@@ -90,15 +100,15 @@ HashMap 是通过拉链法来解决哈希冲突的,也就是如果发生哈希
90100

91101
“O(n) 和 O(1) 是什么呀?”三妹有些不解。
92102

93-
这是时间复杂度的一种表示方法,随后二哥专门给你讲一下。简单说一下 n 和 1 的意思,很显然,n 和 1 都代表的是代码执行的次数,假如数据规模为 n,n 就代表需要执行 n 次,1 就代表只需要执行一次。”我解释道。
103+
这是[时间复杂度](https://tobebetterjavaer.com/collection/time-complexity.html)的一种表示方法,随后二哥专门给你讲一下。简单说一下 n 和 1 的意思,很显然,n 和 1 都代表的是代码执行的次数,假如数据规模为 n,n 就代表需要执行 n 次,1 就代表只需要执行一次。”我解释道。
94104

95105
“三妹,你想一下,如果没有哈希表,但又需要这样一个数据结构,它里面存放的数据是不允许重复的,该怎么办呢?”我问。
96106

97107
“要不使用 equals 方法进行逐个比较?”三妹有些不太确定。
98108

99109
“这种方法当然是可行的,就像 `if ((e = first.next) != null) {}` 子句中那样,但如果数据量特别特别大,性能就会很差,最好的解决方案还是 HashMap。”
100110

101-
HashMap 本质上是通过数组实现的,当我们要从 HashMap 中获取某个值时,实际上是要获取数组中某个位置的元素,而位置是通过键来确定的。
111+
HashMap 本质上是通过[数组](https://tobebetterjavaer.com/array/array.html)实现的,当我们要从 HashMap 中获取某个值时,实际上是要获取数组中某个位置的元素,而位置是通过键来确定的。
102112

103113
```java
104114
public V put(K key, V value) {
@@ -109,16 +119,30 @@ public V put(K key, V value) {
109119
这是 HashMap 的 put 方法,会将键值对放入到数组当中。它会调用 putVal 方法:
110120

111121
```java
112-
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
113-
boolean evict) {
114-
HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i;
115-
if ((tab = table) == null || (n = tab.length) == 0)
116-
n = (tab = resize()).length;
117-
if ((p = tab[i = (n - 1) & hash]) == null)
118-
tab[i] = newNode(hash, key, value, null);
119-
else {
120-
// 拉链
122+
final HashMap.Node<K,V> getNode(int hash, Object key) {
123+
HashMap.Node<K,V>[] tab; HashMap.Node<K,V> first, e; int n; K k;
124+
// 判断 HashMap 的 table 是否为 null 以及 table 长度是否大于 0
125+
if ((tab = table) != null && (n = tab.length) > 0 &&
126+
// 根据 hash 值计算出在 table 中的索引位置,并获取第一个节点
127+
(first = tab[(n - 1) & hash]) != null) {
128+
// 判断第一个节点的 hash 值是否与给定 hash 相等,如果相等,则检查 key 是否相等
129+
if (first.hash == hash &&
130+
((k = first.key) == key || (key != null && key.equals(k))))
131+
return first;
132+
// 如果不相等,获取当前节点的下一个节点
133+
if ((e = first.next) != null) {
134+
// 如果当前节点为 TreeNode 类型(红黑树),则调用 TreeNode 的 getTreeNode 方法查找
135+
if (first instanceof HashMap.TreeNode)
136+
return ((HashMap.TreeNode<K,V>)first).getTreeNode(hash, key);
137+
// 如果不是红黑树节点,遍历链表查找
138+
do {
139+
if (e.hash == hash &&
140+
((k = e.key) == key || (key != null && key.equals(k))))
141+
return e;
142+
} while ((e = e.next) != null);
143+
}
121144
}
145+
// 如果没有找到对应的节点,则返回 null
122146
return null;
123147
}
124148
```

0 commit comments

Comments
 (0)