千家信息网

如何创建Java的不可变对象

发表于:2025-02-02 作者:千家信息网编辑
千家信息网最后更新 2025年02月02日,如何创建Java的不可变对象,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。前言:为什么 String 是 immutab
千家信息网最后更新 2025年02月02日如何创建Java的不可变对象

如何创建Java的不可变对象,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。

    前言:

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

    https://github.com/itwanger/toBeBetterJavaer

    01、什么是不可变类

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

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

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

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

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

    02、常见的不可变类

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

    1)常量池的需要

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

    2)hashCode 的需要

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

    3)线程安全

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

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

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

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

    除了 String 类,包装器类 IntegerLong 等也是不可变类。

    03、手撸不可变类

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

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

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

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

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

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

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

    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 的,nameage 也是 final 的,没有 setter 方法。

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

    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 个字段,分别是 nameprice,以及 gettersetter,重写后的 toString() 方法。然后,在 Writer 类中追加一个可变对象字段 book

    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 参数,以及 Bookgetter 方法。

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

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

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

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

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

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

    public Book getBook() {     Book clone = new Book();     clone.setPrice(this.book.getPrice());     clone.setName(this.book.getName());     return clone; }

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

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

    04、总结

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

    看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注行业资讯频道,感谢您对的支持。

    可变 对象 方法 程序 状态 程序员 线程 进阶 之路 开发 这样的话 内存 字段 字符 字符串 就是 常量 促销 定价 有趣 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 政府认可的乡村软件开发平台 音乐下载软件开发 河北品牌软件开发设计价格走势 守护青春网络安全听后感600字 成都点点通软件开发公司 软件开发定制外包服务商9 创建数据库表有哪些是必备的 软件开发工作指导规范 大学生网络安全教育竞赛 沈阳级差制软件开发哪家好 新加坡和洛杉矶的服务器哪个好 我的世界基石版怎么连接服务器 软件开发公司后缀 丽水云软件开发工具 服务器bios密码如何重置 服务器进程挂了怎么办 如何开软件开发公司 用友软件开发学习过程 幻塔悯雨岛在哪个服务器 椒江软件开发 java最常见的数据库 学前教育网络技术培训心得 河南安卓软件开发大概要多少钱 杨浦区上门软件开发定制要求 重庆水土腾讯数据库平面图 戴尔r230服务器磁盘阵列 科技互联网时代的社会趋势 高德 无法获取城市数据库 思科网络技术学校 软件开发工具包怎么购买
    0