作者 | 沉默王二

头图 | CSDN 下载自视觉中国

二哥,你能给我说说为什么 String 是 immutable 类吗?我想研究它,想知道为什么它就不可变了,这种强烈的愿望就像想研究浩瀚的星空一样。但无奈自身功力有限,始终觉得雾里看花终隔一层。二哥你的文章总是充满趣味性,我想一定能够说明白,我也一定能够看明白,能在接下来写一写吗?

收到读者小 R 的私信后,我就总感觉自己有一种义不容辞的责任,非要把 immutable 类说明白,否则我就怎么地——你说了算!

什么是不可变类

一个类的对象在通过构造方法创建后如果状态不会再被改变,那么它就是一个不可变(immutable)类。它的所有成员变量的赋值仅在构造方法中完成,不会提供任何 setter 方法供外部类去修改。

还记得《神雕侠侣》中小龙女的古墓吗?随着那一声巨响,仅有的通道就被无情地关闭了。别较真那个密道,我这么说只是为了打开你的想象力,让你对不可变类有一个更直观的印象。

自从有了多线程,生产力就被无限地放大了,所有的程序员都爱它,因为强大的硬件能力被充分地利用了。但与此同时,所有的程序员都对它心生忌惮,因为一不小心,多线程就会把对象的状态变得混乱不堪。

为了保护状态的原子性、可见性、有序性,我们程序员可以说是竭尽所能。其中,synchronized(同步)关键字是最简单最入门的一种解决方案。

假如说类是不可变的,那么对象的状态就也是不可变的。这样的话,每次修改对象的状态,就会产生一个新的对象供不同的线程使用,我们程序员就不必再担心并发问题了。

常见的不可变类

提到不可变类,几乎所有的程序员第一个想到的,就是 String 类。那为什么 String 类要被设计成不可变的呢?

1)常量池的需要

字符串常量池是 Java 堆内存中一个特殊的存储区域,当创建一个 String 对象时,假如此字符串在常量池中不存在,那么就创建一个;假如已经存,就不会再创建了,而是直接引用已经存在的对象。这样做能够减少 JVM 的内存开销,提高效率。

2)hashCode 的需要

因为字符串是不可变的,所以在它创建的时候,其 hashCode 就被缓存了,因此非常适合作为哈希值(比如说作为 HashMap 的键),多次调用只返回同一个值,来提高效率。

3)线程安全

就像之前说的那样,如果对象的状态是可变的,那么在多线程环境下,就很容易造成不可预期的结果。而 String 是不可变的,就可以在多个线程之间共享,不需要同步处理。

因此,当我们调用 String 类的任何方法(比如说 trim()、substring()、toLowerCase())时,总会返回一个新的对象,而不影响之前的值。

1String cmower = "沉默王二,一枚有趣的程序员";
2cmower.substring(0,4);
3System.out.println(cmower);// 沉默王二,一枚有趣的程序员

虽然调用 substring() 方法对 cmower 进行了截取,但 cmower 的值没有改变。

除了 String 类,包装器类 Integer、Long 等也是不可变类。

自定义不可变类

看懂一个不可变类也许容易,但要创建一个自定义的不可变类恐怕就有点难了。但知难而进是我们作为一名优秀的程序员不可或缺的品质,正因为不容易,我们才能真正地掌握它。

接下来,就请和我一起,来自定义一个不可变类吧。一个不可变诶,必须要满足以下 4 个条件:

1)确保类是 final 的,不允许被其他类继承。

2)确保所有的成员变量(字段)是 final 的,这样的话,它们就只能在构造方法中初始化值,并且不会在随后被修改。

3)不要提供任何 setter 方法。

4)如果要修改类的状态,必须返回一个新的对象。

按照以上条件,我们来自定义一个简单的不可变类 Writer。

 1public final class Writer {2    private final String name;3    private final int age;45    public Writer(String name, int age) {6        this.name = name;7        this.age = age;8    }9
10    public int getAge() {
11        return age;
12    }
13
14    public String getName() {
15        return name;
16    }
17}

Writer 类是 final 的,name 和 age 也是 final 的,没有 setter 方法。

OK,据说这个作者分享了很多博客,广受读者的喜爱,因此某某出版社找他写了一本书(Book)。Book 类是这样定义的:

 1public class Book {2    private String name;3    private int price;45    public String getName() {6        return name;7    }89    public void setName(String name) {
10        this.name = name;
11    }
12
13    public int getPrice() {
14        return price;
15    }
16
17    public void setPrice(int price) {
18        this.price = price;
19    }
20
21    @Override
22    public String toString() {
23        return "Book{" +
24                "name='" + name + '\'' +
25                ", price=" + price +
26                '}';
27    }
28}

2 个字段,分别是 name 和 price,以及 getter 和 setter,重写后的 toString() 方法。然后,在 Writer 类中追加一个可变对象字段 book。

 1public final class Writer {2    private final String name;3    private final int age;4    private final Book book;56    public Writer(String name, int age, Book book) {7        this.name = name;8        this.age = age;9        this.book = book;
10    }
11
12    public int getAge() {
13        return age;
14    }
15
16    public String getName() {
17        return name;
18    }
19
20    public Book getBook() {
21        return book;
22    }
23}

并在构造方法中追加了 Book 参数,以及 Book 的 getter 方法。

完成以上工作后,我们来新建一个测试类,看看 Writer 类的状态是否真的不可变。

 1public class WriterDemo {2    public static void main(String[] args) {3        Book book = new Book();4        book.setName("Web全栈开发进阶之路");5        book.setPrice(79);67        Writer writer = new Writer("沉默王二",18, book);8        System.out.println("定价:" + writer.getBook());9        writer.getBook().setPrice(59);
10        System.out.println("促销价:" + writer.getBook());
11    }
12}

程序输出的结果如下所示:

1定价:Book{name='Web全栈开发进阶之路', price=79}
2促销价:Book{name='Web全栈开发进阶之路', price=59}

糟糕,Writer 类的不可变性被破坏了,价格发生了变化。为了解决这个问题,我们需要为不可变类的定义规则追加一条内容:

如果一个不可变类中包含了可变类的对象,那么就需要确保返回的是可变对象的副本。也就是说,Writer 类中的 getBook() 方法应该修改为:

1public Book getBook() {
2    Book clone = new Book();
3    clone.setPrice(this.book.getPrice());
4    clone.setName(this.book.getName());
5    return clone;
6}

这样的话,构造方法初始化后的 Book 对象就不会再被修改了。此时,运行 WriterDemo,就会发现价格不再发生变化了。

1定价:Book{name='Web全栈开发进阶之路', price=79}
2促销价:Book{name='Web全栈开发进阶之路', price=79}

总结

不可变类有很多优点,就像之前提到的 String 类那样,尤其是在多线程环境下,它非常的安全。尽管每次修改都会创建一个新的对象,增加了内存的消耗,但这个缺点相比它带来的优点,显然是微不足道的——无非就是捡了西瓜,丢了芝麻。

【End】

推荐阅读 

☞GitHub接连封杀开源项目惹众怒,CEO亲自道歉

☞360 回应安全云盘出现交易异常;苹果官网陆续限购 iPhone;GitHub 屏蔽微软工程师的开源项目 | 极客头条

☞2020年,5种将死的编程语言

☞抗住百万人直播、被联合国推荐,起底飞书技术演进之路!

☞还不知道 AWS 是什么?这 11 个重点带你认识 AWS !

☞智能合约编写之 Solidity 的设计模式

你点的每一个在看,我认真当成了喜欢

这次要说不明白 immutable 类,我就怎么地!相关推荐

  1. 给我半首歌的时间,给你说明白Immutable List

    Immutable List,顾名思义,就是,啥,不明白 Immutable 是什么意思?一成不变的意思,所以 Immutable List 就是一个不可变的 List 类,这意味着该 List 声明 ...

  2. java immutable系列_Java Immutable类代码示例

    import com.jcabi.aspects.Immutable; //导入依赖的package包/类 /** * This class is immutable? * @param type T ...

  3. SpringBoot+Vue前后端分离项目的搭建及简单开发(这次保证看明白~)

    文章目录 概述 一.搭建SpringBoot后端 1.sql脚本 2.新建SpringBoot项目 3.MP代码生成 4.编写Controller 二.搭建Vue前端 1.IDEA安装Vue.js插件 ...

  4. 一张图看明白UML类图中的6种关系

    在UML类图中,有6种关系,分别是: (1)泛化(Generalization),简单说就是父类和子类的关系,extends/: (2) 实现(Realization),简单说就是接口和实现的关系,i ...

  5. java uml类图虚线实线_终于明白六大类UML类图关系了

    UML,全称Unified Modeling Language,统一建模语言.而UML图分为用例图.类图.对象图.状态图.活动图.时序图.协作图.构件图.部署图等9种图. 在面向对象语言中,我们经常看 ...

  6. php类与对象听不明白,PHP类与对象使用---技巧总结

    public(公有):公有类可以在任何地方被访问: protected(受保护):受保护的类成员则可以被其自身及其子类和父类访问: private(私有):只能被自身的类访问. 1.类常量const ...

  7. 面试官:连Spring AOP都说不明白,自己走还是我送你?

    前言 因为假期原因,有一段时间没给大家更新了!和大家说个事吧,放假的时候一位粉丝和我说了下自己的被虐经历,在假期前他去某互联网公司面试,结果直接被人家面试官Spring AOP三连问给问的一脸懵逼!其 ...

  8. 一张图,讲明白UML类图

  9. FPGA是什么?这次算弄明白了

    万物皆可FPGA! FPGA (Field-Programmable Gate Array),中文全称现场可编程门阵列,究竟是什么? 本体是一种数字集成电路,一个可以通过编程来改变内部结构的芯片. F ...

最新文章

  1. pandas使用replace函数替换dataframe中的值:replace函数对dataframe中的多个值进行替换、即一次性同时对多个值进行替换操作
  2. 检测跟踪分割网络笔记
  3. Python+selenium 自动化-切换窗口页签、切换iframe框架。确定页面是否包含iframe方法。
  4. 【Qt】QModbusReply类
  5. PHP的SPFA,由于是之前的c代码,风格你懂的........(夹带php队列实现)
  6. mysql 字符串取前缀_mysql截取字符串的函数总结
  7. java 实现WebService 以及不同的调用方式
  8. Halcon深度学习超参数
  9. Ubuntu 11.10文本文档乱码
  10. Axure实战:滑动拼图解锁教程
  11. 简体中文转换为繁体中文
  12. 第14节 三个败家子(14)——在辉煌中走向深渊
  13. 水果店开业怎样宣传自己的水果店,新开水果店怎么发朋友圈宣传
  14. 北洋UAM-05LX(网口系列适用)ROS节点
  15. php elasticsearch更新文档
  16. opencv录制视频 python_Python-OpenCV录制H264编码的MP4视频
  17. Numbers 储存格换行、表格自动换列
  18. 实现Office的COM插件
  19. linux镜像包含redis吗,Linux虚拟机镜像\redis安装
  20. CSDN获取积分规则

热门文章

  1. es6 let和const
  2. javaSE_06Java中的数组(array)-思维导图
  3. 阅读下面程序,请回答如下问题:
  4. URAL 1346. Intervals of Monotonicity(DP)
  5. 向VMware中的Linux虚拟机共享Windows宿主机的文件/文件夹
  6. 双系统ubuntu 删除后重装
  7. Tensorflow卷积神经网络识别MINST手写数字
  8. Gnome 3.x 安装悬浮菜单栏
  9. Flutter进阶—质感设计之标签栏
  10. Flutter进阶—读取与写入文件