JAVA不可變類(lèi)(immutable)機(jī)制與String的不可變性(推薦)
一、不可變類(lèi)簡(jiǎn)介
不可變類(lèi):所謂的不可變類(lèi)是指這個(gè)類(lèi)的實(shí)例一旦創(chuàng)建完成后,就不能改變其成員變量值。如JDK內(nèi)部自帶的很多不可變類(lèi):Interger、Long和String等。
可變類(lèi):相對(duì)于不可變類(lèi),可變類(lèi)創(chuàng)建實(shí)例后可以改變其成員變量值,開(kāi)發(fā)中創(chuàng)建的大部分類(lèi)都屬于可變類(lèi)。
二、不可變類(lèi)的優(yōu)點(diǎn)
說(shuō)完可變類(lèi)和不可變類(lèi)的區(qū)別,我們需要進(jìn)一步了解為什么要有不可變類(lèi)?這樣的特性對(duì)JAVA來(lái)說(shuō)帶來(lái)怎樣的好處?
1.線(xiàn)程安全
不可變對(duì)象是線(xiàn)程安全的,在線(xiàn)程之間可以相互共享,不需要利用特殊機(jī)制來(lái)保證同步問(wèn)題,因?yàn)閷?duì)象的值無(wú)法改變??梢越档筒l(fā)錯(cuò)誤的可能性,因?yàn)椴恍枰靡恍╂i機(jī)制等保證內(nèi)存一致性問(wèn)題也減少了同步開(kāi)銷(xiāo)。
2.易于構(gòu)造、使用和測(cè)試
3....
三、不可變類(lèi)的設(shè)計(jì)方法
對(duì)于設(shè)計(jì)不可變類(lèi),個(gè)人總結(jié)出以下原則:
1. 類(lèi)添加final修飾符,保證類(lèi)不被繼承。
如果類(lèi)可以被繼承會(huì)破壞類(lèi)的不可變性機(jī)制,只要繼承類(lèi)覆蓋父類(lèi)的方法并且繼承類(lèi)可以改變成員變量值,那么一旦子類(lèi)以父類(lèi)的形式出現(xiàn)時(shí),不能保證當(dāng)前類(lèi)是否可變。
2. 保證所有成員變量必須私有,并且加上final修飾
通過(guò)這種方式保證成員變量不可改變。但只做到這一步還不夠,因?yàn)槿绻菍?duì)象成員變量有可能再外部改變其值。所以第4點(diǎn)彌補(bǔ)這個(gè)不足。
3. 不提供改變成員變量的方法,包括setter
避免通過(guò)其他接口改變成員變量的值,破壞不可變特性。
4.通過(guò)構(gòu)造器初始化所有成員,進(jìn)行深拷貝(deep copy)
如果構(gòu)造器傳入的對(duì)象直接賦值給成員變量,還是可以通過(guò)對(duì)傳入對(duì)象的修改進(jìn)而導(dǎo)致改變內(nèi)部變量的值。例如:
public final class ImmutableDemo {
private final int[] myArray;
public ImmutableDemo(int[] array) {
this.myArray = array; // wrong
}
}
這種方式不能保證不可變性,myArray和array指向同一塊內(nèi)存地址,用戶(hù)可以在ImmutableDemo之外通過(guò)修改array對(duì)象的值來(lái)改變myArray內(nèi)部的值。
為了保證內(nèi)部的值不被修改,可以采用深度copy來(lái)創(chuàng)建一個(gè)新內(nèi)存保存?zhèn)魅氲闹?。正確做法:
public final class MyImmutableDemo {
private final int[] myArray;
public MyImmutableDemo(int[] array) {
this.myArray = array.clone();
}
}
5. 在getter方法中,不要直接返回對(duì)象本身,而是克隆對(duì)象,并返回對(duì)象的拷貝
這種做法也是防止對(duì)象外泄,防止通過(guò)getter獲得內(nèi)部可變成員對(duì)象后對(duì)成員變量直接操作,導(dǎo)致成員變量發(fā)生改變。
四、String對(duì)象的不可變性
string對(duì)象在內(nèi)存創(chuàng)建后就不可改變,不可變對(duì)象的創(chuàng)建一般滿(mǎn)足以上5個(gè)原則,我們看看String代碼是如何實(shí)現(xiàn)的。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
{
/** The value is used for character storage. */
private final char value[];
/** The offset is the first index of the storage that is used. */
private final int offset;
/** The count is the number of characters in the String. */
private final int count;
/** Cache the hash code for the string */
private int hash; // Default to 0
....
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length); // deep copy操作
}
...
public char[] toCharArray() {
// Cannot use Arrays.copyOf because of class initialization order issues
char result[] = new char[value.length];
System.arraycopy(value, 0, result, 0, value.length);
return result;
}
...
}
如上代碼所示,可以觀察到以下設(shè)計(jì)細(xì)節(jié):
1.String類(lèi)被final修飾,不可繼承
2.string內(nèi)部所有成員都設(shè)置為私有變量
3.不存在value的setter
4.并將value和offset設(shè)置為final。
5.當(dāng)傳入可變數(shù)組value[]時(shí),進(jìn)行copy而不是直接將value[]復(fù)制給內(nèi)部變量.
6.獲取value時(shí)不是直接返回對(duì)象引用,而是返回對(duì)象的copy.
這都符合上面總結(jié)的不變類(lèi)型的特性,也保證了String類(lèi)型是不可變的類(lèi)。
五、String對(duì)象的不可變性的優(yōu)缺點(diǎn)
從上一節(jié)分析,String數(shù)據(jù)不可變類(lèi),那設(shè)置這樣的特性有什么好處呢?我總結(jié)為以下幾點(diǎn):
1.字符串常量池的需要.
字符串常量池可以將一些字符常量放在常量池中重復(fù)使用,避免每次都重新創(chuàng)建相同的對(duì)象、節(jié)省存儲(chǔ)空間。但如果字符串是可變的,此時(shí)相同內(nèi)容的String還指向常量池的同一個(gè)內(nèi)存空間,當(dāng)某個(gè)變量改變了該內(nèi)存的值時(shí),其他遍歷的值也會(huì)發(fā)生改變。所以不符合常量池設(shè)計(jì)的初衷。
2. 線(xiàn)程安全考慮。
同一個(gè)字符串實(shí)例可以被多個(gè)線(xiàn)程共享。這樣便不用因?yàn)榫€(xiàn)程安全問(wèn)題而使用同步。字符串自己便是線(xiàn)程安全的。
3. 類(lèi)加載器要用到字符串,不可變性提供了安全性,以便正確的類(lèi)被加載。譬如你想加載java.sql.Connection類(lèi),而這個(gè)值被改成了myhacked.Connection,那么會(huì)對(duì)你的數(shù)據(jù)庫(kù)造成不可知的破壞。
4. 支持hash映射和緩存。
因?yàn)樽址遣豢勺兊模栽谒鼊?chuàng)建的時(shí)候hashcode就被緩存了,不需要重新計(jì)算。這就使得字符串很適合作為Map中的鍵,字符串的處理速度要快過(guò)其它的鍵對(duì)象。這就是HashMap中的鍵往往都使用字符串。
缺點(diǎn):
1.如果有對(duì)String對(duì)象值改變的需求,那么會(huì)創(chuàng)建大量的String對(duì)象。
六、String對(duì)象的是否真的不可變
雖然String對(duì)象將value設(shè)置為final,并且還通過(guò)各種機(jī)制保證其成員變量不可改變。但是還是可以通過(guò)反射機(jī)制的手段改變其值。例如:
//創(chuàng)建字符串"Hello World", 并賦給引用s
String s = "Hello World";
System.out.println("s = " + s); //Hello World
//獲取String類(lèi)中的value字段
Field valueFieldOfString = String.class.getDeclaredField("value");
//改變value屬性的訪(fǎng)問(wèn)權(quán)限
valueFieldOfString.setAccessible(true);
//獲取s對(duì)象上的value屬性的值
char[] value = (char[]) valueFieldOfString.get(s);
//改變value所引用的數(shù)組中的第5個(gè)字符
value[5] = '_';
System.out.println("s = " + s); //Hello_World
打印結(jié)果為:
s = Hello World s = Hello_World
發(fā)現(xiàn)String的值已經(jīng)發(fā)生了改變。也就是說(shuō),通過(guò)反射是可以修改所謂的“不可變”對(duì)象的
總結(jié)
不可變類(lèi)是實(shí)例創(chuàng)建后就不可以改變成員遍歷的值。這種特性使得不可變類(lèi)提供了線(xiàn)程安全的特性但同時(shí)也帶來(lái)了對(duì)象創(chuàng)建的開(kāi)銷(xiāo),每更改一個(gè)屬性都是重新創(chuàng)建一個(gè)新的對(duì)象。JDK內(nèi)部也提供了很多不可變類(lèi)如Integer、Double、String等。String的不可變特性主要為了滿(mǎn)足常量池、線(xiàn)程安全、類(lèi)加載的需求。合理使用不可變類(lèi)可以帶來(lái)極大的好處。
以上所述是小編給大家介紹的JAVA不可變類(lèi)(immutable)機(jī)制與String的不可變性(推薦),希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
Java的帶GUI界面猜數(shù)字游戲的實(shí)現(xiàn)示例
這篇文章主要介紹了Java的帶GUI界面猜數(shù)字游戲的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12
springboot打印接口調(diào)用日志的實(shí)例
這篇文章主要介紹了springboot打印接口調(diào)用日志的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09
spring boot 監(jiān)聽(tīng)容器啟動(dòng)代碼實(shí)例
這篇文章主要介紹了spring boot 監(jiān)聽(tīng)容器啟動(dòng)代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10
java后端如何調(diào)用第三方接口(往header和body中的參數(shù)傳參)
這篇文章主要介紹了java后端如何調(diào)用第三方接口(往header和body中的參數(shù)傳參),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12
Spring Batch讀取txt文件并寫(xiě)入數(shù)據(jù)庫(kù)的方法教程
這篇文章主要給大家介紹了Spring Batch讀取txt文件并寫(xiě)入數(shù)據(jù)庫(kù)的方法,SpringBatch 是一個(gè)輕量級(jí)、全面的批處理框架。這里我們用它來(lái)實(shí)現(xiàn)文件的讀取并將讀取的結(jié)果作處理,處理之后再寫(xiě)入數(shù)據(jù)庫(kù)中的功能。需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-04-04
mybatis查詢(xún)oracle long類(lèi)型的踩坑記錄
這篇文章主要介紹了mybatis查詢(xún)oracle long類(lèi)型的踩坑記錄,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
解決調(diào)用ftpClient.retrieveFileStream(String?remoteFilePath)第二次讀
這篇文章主要給大家介紹了關(guān)于如何解決調(diào)用ftpClient.retrieveFileStream(String?remoteFilePath)第二次讀取為空問(wèn)題的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-08-08
Intellij IDEA中啟動(dòng)多個(gè)微服務(wù)(開(kāi)啟Run Dashboard管理)
這篇文章主要介紹了Intellij IDEA中啟動(dòng)多個(gè)微服務(wù)(開(kāi)啟Run Dashboard管理),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
Java基于Servlet和JSP實(shí)現(xiàn)登錄功能
在 Web 開(kāi)發(fā)中,用戶(hù)登錄功能是非常常見(jiàn)的模塊之一,本文將通過(guò)使用 Java Servlet 和 JSP 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的用戶(hù)登錄功能,展示如何創(chuàng)建登錄頁(yè)面、處理用戶(hù)登錄請(qǐng)求,并使用數(shù)據(jù)庫(kù)驗(yàn)證用戶(hù)信息,需要的朋友可以參考下2024-11-11

