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

不可变对象

上级 ae19b200
......@@ -148,6 +148,7 @@
- [详解 static 关键字的作用:静态变量、静态方法、静态代码块、静态内部类](docs/oo/static.md)
- [一文彻底搞懂 final 关键字](docs/oo/final.md)
- [聊聊instanceof关键字](docs/basic-extra-meal/instanceof.md)
- [聊聊Java中的不可变对象](docs/basic-extra-meal/immutable.md)
- [Java 方法重写 Override 和方法重载 Overload 的区别,一下子就明白了](docs/basic-extra-meal/override-overload.md)
......@@ -215,7 +216,6 @@
- [详解Java中Comparable和Comparator接口的区别](docs/basic-extra-meal/comparable-omparator.md)
- [为什么JDK源码中,无限循环大多使用for(;;)而不是while(true)?](docs/basic-extra-meal/jdk-while-for-wuxian-xunhuan.md)
- [instanceof关键字是如何实现的?](docs/basic-extra-meal/instanceof-jvm.md)
- [深入理解Java中的不可变对象](docs/basic-extra-meal/immutable.md)
- [深入理解Java中的泛型](docs/basic-extra-meal/generic.md)
- [Java不能实现真正泛型的原因是什么?](docs/basic-extra-meal/true-generic.md)
- [深入理解Java中的注解](docs/basic-extra-meal/annotation.md)
......
......@@ -161,6 +161,7 @@ export const sidebarConfig = sidebar({
"oo/static",
"oo/final",
"basic-extra-meal/instanceof",
"basic-extra-meal/immutable",
"basic-extra-meal/override-overload",
],
},
......@@ -250,7 +251,6 @@ export const sidebarConfig = sidebar({
"comparable-omparator",
"jdk-while-for-wuxian-xunhuan",
"instanceof-jvm",
"immutable",
"generic",
"true-generic",
"annotation",
......
---
title: 深入理解Java中的不可变对象
shortTitle: 深入理解Java中的不可变对象
title: 聊聊Java中的不可变对象
shortTitle: Java中的不可变对象
category:
- Java核心
tag:
......@@ -12,14 +12,13 @@ head:
content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java,不可变对象,immutable
---
# 深入理解Java中的不可变对象
# 5.20 Java中的不可变对象
>二哥,你能给我说说为什么 String 是 immutable 类(不可变对象)吗?我想研究它,想知道为什么它就不可变了,这种强烈的愿望就像想研究浩瀚的星空一样。但无奈自身功力有限,始终觉得雾里看花终隔一层。二哥你的文章总是充满趣味性,我想一定能够说明白,我也一定能够看明白,能在接下来写一写吗?
“二哥,你能给我说说为什么 String 是 immutable 类(不可变对象)吗?我想研究它,想知道为什么它就不可变了,这种强烈的愿望就像想研究浩瀚的星空一样。但无奈自身功力有限,始终觉得雾里看花终隔一层。”三妹的这句话里满是彩虹屁的味道。
收到读者小 R 的私信后,我就总感觉自己有一种义不容辞的责任,非要把 immutable 类说明白不可!
“既然三妹你说话这么好听,那我们就开始吧!”我愉快的心情就好像吃了两罐蜂蜜一样(😂)。
## 01、什么是不可变类
### 01、什么是不可变类
一个类的对象在通过构造方法创建后如果状态不会再被改变,那么它就是一个不可变(immutable)类。它的所有成员变量的赋值仅在构造方法中完成,不会提供任何 setter 方法供外部类去修改。
......@@ -31,19 +30,19 @@ head:
假如说类是不可变的,那么对象的状态就也是不可变的。这样的话,每次修改对象的状态,就会产生一个新的对象供不同的线程使用,我们程序员就不必再担心并发问题了。
## 02、常见的不可变类
### 02、常见的不可变类
提到不可变类,几乎所有的程序员第一个想到的,就是 String 类。那为什么 String 类要被设计成不可变的呢?
1)常量池的需要
#### 1)常量池的需要
字符串常量池是 Java 堆内存中一个特殊的存储区域,当创建一个 String 对象时,假如此字符串在常量池中不存在,那么就创建一个;假如已经存,就不会再创建了,而是直接引用已经存在的对象。这样做能够减少 JVM 的内存开销,提高效率。
[字符串常量池](https://tobebetterjavaer.com/string/constant-pool.html)是 Java 堆内存中一个特殊的存储区域,当创建一个 String 对象时,假如此字符串在常量池中不存在,那么就创建一个;假如已经存,就不会再创建了,而是直接引用已经存在的对象。这样做能够减少 JVM 的内存开销,提高效率。
2)hashCode 的需要
#### 2)hashCode 需要
因为字符串是不可变的,所以在它创建的时候,其 hashCode 就被缓存了,因此非常适合作为哈希值(比如说作为 HashMap 的键),多次调用只返回同一个值,来提高效率。
因为字符串是不可变的,所以在它创建的时候,其 hashCode 就被缓存了,因此非常适合作为哈希值(比如说作为 [HashMap](https://tobebetterjavaer.com/collection/hashmap.html) 的键),多次调用只返回同一个值,来提高效率。
3)线程安全
#### 3)线程安全
就像之前说的那样,如果对象的状态是可变的,那么在多线程环境下,就很容易造成不可预期的结果。而 String 是不可变的,就可以在多个线程之间共享,不需要同步处理。
......@@ -59,19 +58,19 @@ System.out.println(cmower);// 沉默王二,一枚有趣的程序员
除了 String 类,包装器类 Integer、Long 等也是不可变类。
## 03、手撸不可变类
### 03、手撸一个不可变类
看懂一个不可变类也许容易,但要创建一个自定义的不可变类恐怕就有点难了。但知难而进是我们作为一名优秀的程序员不可或缺的品质,正因为不容易,我们才能真正地掌握它。
接下来,就请和我一起,来自定义一个不可变类吧。一个不可变类,必须要满足以下 4 个条件:
1)确保类是 final 的,不允许被其他类继承
**1)确保类是 final 的**,不允许被其他类继承*
2)确保所有的成员变量(字段)是 final 的,这样的话,它们就只能在构造方法中初始化值,并且不会在随后被修改。
**2)确保所有的成员变量(字段)是 final 的**,这样的话,它们就只能在构造方法中初始化值,并且不会在随后被修改。
3)不要提供任何 setter 方法
**3)不要提供任何 setter 方法**
4)如果要修改类的状态,必须返回一个新的对象
**4)如果要修改类的状态,必须返回一个新的对象**
按照以上条件,我们来自定义一个简单的不可变类 Writer。
......@@ -166,7 +165,7 @@ public final class Writer {
public class WriterDemo {
public static void main(String[] args) {
Book book = new Book();
book.setName("Web全栈开发进阶之路");
book.setName("二哥的 Java 进阶之路");
book.setPrice(79);
Writer writer = new Writer("沉默王二",18, book);
......@@ -180,8 +179,8 @@ public class WriterDemo {
程序输出的结果如下所示:
```java
定价Book{name='Web全栈开发进阶之路', price=79}
促销价Book{name='Web全栈开发进阶之路', price=59}
定价Book{name='二哥的 Java 进阶之路', price=79}
促销价Book{name='二哥的 Java 进阶之路', price=59}
```
糟糕,Writer 类的不可变性被破坏了,价格发生了变化。为了解决这个问题,我们需要为不可变类的定义规则追加一条内容:
......@@ -200,14 +199,22 @@ public Book getBook() {
这样的话,构造方法初始化后的 Book 对象就不会再被修改了。此时,运行 WriterDemo,就会发现价格不再发生变化了。
```
定价:Book{name='Web全栈开发进阶之路', price=79}
促销价:Book{name='Web全栈开发进阶之路', price=79}
定价:Book{name='二哥的 Java 进阶之路', price=79}
促销价:Book{name='二哥的 Java 进阶之路', price=79}
```
## 04、总结
### 04、总结
不可变类有很多优点,就像之前提到的 String 类那样,尤其是在多线程环境下,它非常的安全。尽管每次修改都会创建一个新的对象,增加了内存的消耗,但这个缺点相比它带来的优点,显然是微不足道的——无非就是捡了西瓜,丢了芝麻。
“好了,三妹,你应该理解了吧?”
“嗯,哥,你这本《Java 进阶之路》还没有出书吧?”三妹质疑道。
“害,出版社都找过来要签合同了,我只好推脱说 GitHub 破 1 万 star 再考虑,先优化吧,后面看机会。”
“哦哦,原来如此啊。”三妹释然道。
----
最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
......
......@@ -159,6 +159,7 @@ head:
- [详解 static 关键字的作用:静态变量、静态方法、静态代码块、静态内部类](oo/static.md)
- [一文彻底搞懂 final 关键字](oo/final.md)
- [聊聊instanceof关键字](basic-extra-meal/instanceof.md)
- [聊聊Java中的不可变对象](basic-extra-meal/immutable.md)
- [Java 方法重写 Override 和方法重载 Overload 的区别,一下子就明白了](basic-extra-meal/override-overload.md)
### 集合框架(容器)
......@@ -226,7 +227,6 @@ head:
- [详解Java中Comparable和Comparator接口的区别](basic-extra-meal/comparable-omparator.md)
- [为什么JDK源码中,无限循环大多使用for(;;)而不是while(true)?](basic-extra-meal/jdk-while-for-wuxian-xunhuan.md)
- [instanceof关键字是如何实现的?](basic-extra-meal/instanceof-jvm.md)
- [深入理解Java中的不可变对象](basic-extra-meal/immutable.md)
- [Java中可变参数的使用](basic-extra-meal/varables.md)
- [深入理解Java中的泛型](basic-extra-meal/generic.md)
- [Java不能实现真正泛型的原因是什么?](basic-extra-meal/true-generic.md)
......
......@@ -10399,6 +10399,217 @@ if (obj instanceof String s) {
“好了,关于 instanceof 操作符我们就先讲到这吧,难是一点都不难,希望哥也能够很好的掌握。”三妹笑嘻嘻地说,看来她很享受这个讲的过程嘛。
----
最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/gongzhonghao.png)
## 5.20 Java中的不可变对象
“二哥,你能给我说说为什么 String 是 immutable 类(不可变对象)吗?我想研究它,想知道为什么它就不可变了,这种强烈的愿望就像想研究浩瀚的星空一样。但无奈自身功力有限,始终觉得雾里看花终隔一层。”三妹的这句话里满是彩虹屁的味道。
“既然三妹你说话这么好听,那我们就开始吧!”我愉快的心情就好像吃了两罐蜂蜜一样(😂)。
### 01、什么是不可变类
一个类的对象在通过构造方法创建后如果状态不会再被改变,那么它就是一个不可变(immutable)类。它的所有成员变量的赋值仅在构造方法中完成,不会提供任何 setter 方法供外部类去修改。
还记得《神雕侠侣》中小龙女的古墓吗?随着那一声巨响,仅有的通道就被无情地关闭了。别较真那个密道,我这么说只是为了打开你的想象力,让你对不可变类有一个更直观的印象。
自从有了多线程,生产力就被无限地放大了,所有的程序员都爱它,因为强大的硬件能力被充分地利用了。但与此同时,所有的程序员都对它心生忌惮,因为一不小心,多线程就会把对象的状态变得混乱不堪。
为了保护状态的原子性、可见性、有序性,我们程序员可以说是竭尽所能。其中,synchronized(同步)关键字是最简单最入门的一种解决方案。
假如说类是不可变的,那么对象的状态就也是不可变的。这样的话,每次修改对象的状态,就会产生一个新的对象供不同的线程使用,我们程序员就不必再担心并发问题了。
### 02、常见的不可变类
提到不可变类,几乎所有的程序员第一个想到的,就是 String 类。那为什么 String 类要被设计成不可变的呢?
#### 1)常量池的需要
[字符串常量池](https://tobebetterjavaer.com/string/constant-pool.html)是 Java 堆内存中一个特殊的存储区域,当创建一个 String 对象时,假如此字符串在常量池中不存在,那么就创建一个;假如已经存,就不会再创建了,而是直接引用已经存在的对象。这样做能够减少 JVM 的内存开销,提高效率。
#### 2)hashCode 需要
因为字符串是不可变的,所以在它创建的时候,其 hashCode 就被缓存了,因此非常适合作为哈希值(比如说作为 [HashMap](https://tobebetterjavaer.com/collection/hashmap.html) 的键),多次调用只返回同一个值,来提高效率。
#### 3)线程安全
就像之前说的那样,如果对象的状态是可变的,那么在多线程环境下,就很容易造成不可预期的结果。而 String 是不可变的,就可以在多个线程之间共享,不需要同步处理。
因此,当我们调用 String 类的任何方法(比如说 `trim()`、`substring()`、`toLowerCase()`)时,总会返回一个新的对象,而不影响之前的值。
```java
String cmower = "沉默王二,一枚有趣的程序员";
cmower.substring(0,4);
System.out.println(cmower);// 沉默王二,一枚有趣的程序员
```
虽然调用 `substring()` 方法对 cmower 进行了截取,但 cmower 的值没有改变。
除了 String 类,包装器类 Integer、Long 等也是不可变类。
### 03、手撸一个不可变类
看懂一个不可变类也许容易,但要创建一个自定义的不可变类恐怕就有点难了。但知难而进是我们作为一名优秀的程序员不可或缺的品质,正因为不容易,我们才能真正地掌握它。
接下来,就请和我一起,来自定义一个不可变类吧。一个不可变类,必须要满足以下 4 个条件:
**1)确保类是 final 的**,不允许被其他类继承*。
**2)确保所有的成员变量(字段)是 final 的**,这样的话,它们就只能在构造方法中初始化值,并且不会在随后被修改。
**3)不要提供任何 setter 方法**。
**4)如果要修改类的状态,必须返回一个新的对象**。
按照以上条件,我们来自定义一个简单的不可变类 Writer。
```java
public final class Writer {
private final String name;
private final int age;
public Writer(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
}
```
Writer 类是 final 的,name 和 age 也是 final 的,没有 setter 方法。
OK,据说这个作者分享了很多博客,广受读者的喜爱,因此某某出版社找他写了一本书(Book)。Book 类是这样定义的:
```java
public class Book {
private String name;
private int price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
```
2 个字段,分别是 name 和 price,以及 getter 和 setter,重写后的 `toString()` 方法。然后,在 Writer 类中追加一个可变对象字段 book。
```java
public final class Writer {
private final String name;
private final int age;
private final Book book;
public Writer(String name, int age, Book book) {
this.name = name;
this.age = age;
this.book = book;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public Book getBook() {
return book;
}
}
```
并在构造方法中追加了 Book 参数,以及 Book 的 getter 方法。
完成以上工作后,我们来新建一个测试类,看看 Writer 类的状态是否真的不可变。
```java
public class WriterDemo {
public static void main(String[] args) {
Book book = new Book();
book.setName("二哥的 Java 进阶之路");
book.setPrice(79);
Writer writer = new Writer("沉默王二",18, book);
System.out.println("定价:" + writer.getBook());
writer.getBook().setPrice(59);
System.out.println("促销价:" + writer.getBook());
}
}
```
程序输出的结果如下所示:
```java
定价:Book{name='二哥的 Java 进阶之路', price=79}
促销价:Book{name='二哥的 Java 进阶之路', price=59}
```
糟糕,Writer 类的不可变性被破坏了,价格发生了变化。为了解决这个问题,我们需要为不可变类的定义规则追加一条内容:
如果一个不可变类中包含了可变类的对象,那么就需要确保返回的是可变对象的副本。也就是说,Writer 类中的 `getBook()` 方法应该修改为:
```java
public Book getBook() {
Book clone = new Book();
clone.setPrice(this.book.getPrice());
clone.setName(this.book.getName());
return clone;
}
```
这样的话,构造方法初始化后的 Book 对象就不会再被修改了。此时,运行 WriterDemo,就会发现价格不再发生变化了。
```
定价:Book{name='二哥的 Java 进阶之路', price=79}
促销价:Book{name='二哥的 Java 进阶之路', price=79}
```
### 04、总结
不可变类有很多优点,就像之前提到的 String 类那样,尤其是在多线程环境下,它非常的安全。尽管每次修改都会创建一个新的对象,增加了内存的消耗,但这个缺点相比它带来的优点,显然是微不足道的——无非就是捡了西瓜,丢了芝麻。
“好了,三妹,你应该理解了吧?”
“嗯,哥,你这本《Java 进阶之路》还没有出书吧?”三妹质疑道。
“害,出版社都找过来要签合同了,我只好推脱说 GitHub 破 1 万 star 再考虑,先优化吧,后面看机会。”
“哦哦,原来如此啊。”三妹释然道。
----
最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册