創(chuàng)建一個(gè)Java的不可變對(duì)象
前言:
為什么 String
是 immutable
類(不可變對(duì)象)嗎?我想研究它,想知道為什么它就不可變了,這種強(qiáng)烈的愿望就像想研究浩瀚的星空一樣。但無奈自身功力有限,始終覺得霧里看花終隔一層。二哥你的文章總是充滿趣味性,我想一定能夠說明白,我也一定能夠看明白,能在接下來寫一寫嗎?
https://github.com/itwanger/toBeBetterJavaer
01、什么是不可變類
一個(gè)類的對(duì)象在通過構(gòu)造方法創(chuàng)建后如果狀態(tài)不會(huì)再被改變,那么它就是一個(gè)不可變(immutable
)類。它的所有成員變量的賦值僅在構(gòu)造方法中完成,不會(huì)提供任何 setter
方法供外部類去修改。
還記得《神雕俠侶》中小龍女的古墓嗎?隨著那一聲巨響,僅有的通道就被無情地關(guān)閉了。別較真那個(gè)密道,我這么說只是為了打開你的想象力,讓你對(duì)不可變類有一個(gè)更直觀的印象。
自從有了多線程,生產(chǎn)力就被無限地放大了,所有的程序員都愛它,因?yàn)閺?qiáng)大的硬件能力被充分地利用了。但與此同時(shí),所有的程序員都對(duì)它心生忌憚,因?yàn)橐徊恍⌒模嗑€程就會(huì)把對(duì)象的狀態(tài)變得混亂不堪。
為了保護(hù)狀態(tài)的原子性、可見性、有序性,我們程序員可以說是竭盡所能。其中,synchronized(同步)關(guān)鍵字是最簡單最入門的一種解決方案。
假如說類是不可變的,那么對(duì)象的狀態(tài)就也是不可變的。這樣的話,每次修改對(duì)象的狀態(tài),就會(huì)產(chǎn)生一個(gè)新的對(duì)象供不同的線程使用,我們程序員就不必再擔(dān)心并發(fā)問題了。
02、常見的不可變類
提到不可變類,幾乎所有的程序員第一個(gè)想到的,就是 String
類。那為什么 String
類要被設(shè)計(jì)成不可變的呢?
1)常量池的需要
字符串常量池是 Java
堆內(nèi)存中一個(gè)特殊的存儲(chǔ)區(qū)域,當(dāng)創(chuàng)建一個(gè) String
對(duì)象時(shí),假如此字符串在常量池中不存在,那么就創(chuàng)建一個(gè);假如已經(jīng)存,就不會(huì)再創(chuàng)建了,而是直接引用已經(jīng)存在的對(duì)象。這樣做能夠減少 JVM 的內(nèi)存開銷,提高效率。
2)hashCode 的需要
因?yàn)樽址遣豢勺兊?,所以在它?chuàng)建的時(shí)候,其 hashCode
就被緩存了,因此非常適合作為哈希值(比如說作為 HashMap 的鍵),多次調(diào)用只返回同一個(gè)值,來提高效率。
3)線程安全
就像之前說的那樣,如果對(duì)象的狀態(tài)是可變的,那么在多線程環(huán)境下,就很容易造成不可預(yù)期的結(jié)果。而 String 是不可變的,就可以在多個(gè)線程之間共享,不需要同步處理。
因此,當(dāng)我們調(diào)用 String
類的任何方法(比如說 trim()
、substring()
、toLowerCase())
時(shí),總會(huì)返回一個(gè)新的對(duì)象,而不影響之前的值。
String cmower = "沉默王二,一枚有趣的程序員"; cmower.substring(0,4); System.out.println(cmower);// 沉默王二,一枚有趣的程序員
雖然調(diào)用 substring()
方法對(duì) cmower
進(jìn)行了截取,但 cmower
的值沒有改變。
除了 String
類,包裝器類 Integer
、Long
等也是不可變類。
03、手?jǐn)]不可變類
看懂一個(gè)不可變類也許容易,但要?jiǎng)?chuàng)建一個(gè)自定義的不可變類恐怕就有點(diǎn)難了。但知難而進(jìn)是我們作為一名優(yōu)秀的程序員不可或缺的品質(zhì),正因?yàn)椴蝗菀?,我們才能真正地掌握它?/p>
接下來,就請和我一起,來自定義一個(gè)不可變類吧。一個(gè)不可變誒,必須要滿足以下 4 個(gè)條件:
- 1)確保類是
final
的,不允許被其他類繼承。 - 2)確保所有的成員變量(字段)是
final
的,這樣的話,它們就只能在構(gòu)造方法中初始化值,并且不會(huì)在隨后被修改。 - 3)不要提供任何
setter
方法。 - 4)如果要修改類的狀態(tài),必須返回一個(gè)新的對(duì)象。
按照以上條件,我們來自定義一個(gè)簡單的不可變類 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
的,name
和 age
也是 final
的,沒有 setter
方法。
OK,據(jù)說這個(gè)作者分享了很多博客,廣受讀者的喜愛,因此某某出版社找他寫了一本書(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 個(gè)字段,分別是 name
和 price
,以及 getter
和 setter
,重寫后的 toString()
方法。然后,在 Writer
類中追加一個(gè)可變對(duì)象字段 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; } }
并在構(gòu)造方法中追加了 Book
參數(shù),以及 Book
的 getter
方法。
完成以上工作后,我們來新建一個(gè)測試類,看看 Writer 類的狀態(tài)是否真的不可變。
public class WriterDemo { public static void main(String[] args) { Book book = new Book(); book.setName("Web全棧開發(fā)進(jìn)階之路"); book.setPrice(79); Writer writer = new Writer("沉默王二",18, book); System.out.println("定價(jià):" + writer.getBook()); writer.getBook().setPrice(59); System.out.println("促銷價(jià):" + writer.getBook()); } }
程序輸出的結(jié)果如下所示:
定價(jià):Book{name='Web全棧開發(fā)進(jìn)階之路', price=79}
促銷價(jià):Book{name='Web全棧開發(fā)進(jìn)階之路', price=59}
糟糕,Writer 類的不可變性被破壞了,價(jià)格發(fā)生了變化。為了解決這個(gè)問題,我們需要為不可變類的定義規(guī)則追加一條內(nèi)容:
如果一個(gè)不可變類中包含了可變類的對(duì)象,那么就需要確保返回的是可變對(duì)象的副本。也就是說,Writer
類中的 getBook()
方法應(yīng)該修改為:
public Book getBook() { Book clone = new Book(); clone.setPrice(this.book.getPrice()); clone.setName(this.book.getName()); return clone; }
這樣的話,構(gòu)造方法初始化后的 Book
對(duì)象就不會(huì)再被修改了。此時(shí),運(yùn)行 WriterDemo
,就會(huì)發(fā)現(xiàn)價(jià)格不再發(fā)生變化了。
定價(jià):Book{name='Web全棧開發(fā)進(jìn)階之路', price=79}
促銷價(jià):Book{name='Web全棧開發(fā)進(jìn)階之路', price=79}
04、總結(jié)
不可變類有很多優(yōu)點(diǎn),就像之前提到的 String
類那樣,尤其是在多線程環(huán)境下,它非常的安全。盡管每次修改都會(huì)創(chuàng)建一個(gè)新的對(duì)象,增加了內(nèi)存的消耗,但這個(gè)缺點(diǎn)相比它帶來的優(yōu)點(diǎn),顯然是微不足道的——無非就是撿了西瓜,丟了芝麻。
到此這篇關(guān)于創(chuàng)建一個(gè)Java的不可變對(duì)象的文章就介紹到這了,更多相關(guān)Java的不可變對(duì)象內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java編程思想里的泛型實(shí)現(xiàn)一個(gè)堆棧類 分享
這篇文章介紹了Java編程思想里的泛型實(shí)現(xiàn)一個(gè)堆棧類,有需要的朋友可以參考一下2013-07-07gateway與spring-boot-starter-web沖突問題的解決
這篇文章主要介紹了gateway與spring-boot-starter-web沖突問題的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07手?jǐn)]一個(gè) spring-boot-starter的全過程
這篇文章主要介紹了手?jǐn)]一個(gè) spring-boot-starter的全過程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01Springcloud Stream消息驅(qū)動(dòng)工具使用介紹
SpringCloud Stream由一個(gè)中間件中立的核組成,應(yīng)用通過SpringCloud Stream插入的input(相當(dāng)于消費(fèi)者consumer,它是從隊(duì)列中接收消息的)和output(相當(dāng)于生產(chǎn)者producer,它是發(fā)送消息到隊(duì)列中的)通道與外界交流2022-09-09JDK1.6“新“特性Instrumentation之JavaAgent(推薦)
這篇文章主要介紹了JDK1.6“新“特性Instrumentation之JavaAgent,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08