最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

走進(jìn)JDK之不可變類String

 更新時(shí)間:2019年04月12日 11:04:31   作者:秉心說  
這篇文章主要給大家介紹了JDK之不可變類String的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用JDK具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧

文中相關(guān)源碼: String.java

今天來說說 String。

貫穿全文,你需要始終記住這句話,String 是不可變類 。其實(shí)前面說過的所有基本數(shù)據(jù)類型包裝類都是不可變類,但是在 String 的源碼中,不可變類 的概念體現(xiàn)的更加淋漓盡致。所以,在閱讀 String 源碼的同時(shí),抽絲剝繭,你會(huì)對不可變類有更深的理解。

什么是不可變類 ?

首先來看一下什么是不可變類?Effective Java 第三版 第 17 條 使不可變性最小化 中對 不可變類 的解釋:

不可變類是指其實(shí)例不能被修改的類。每個(gè)實(shí)例中包含的所有信息都必須在創(chuàng)建該實(shí)例的時(shí)候就提供,并在對象的整個(gè)生命周期 (lifetime) 內(nèi)固定不變 。
為了使類成為不可變,要遵循下面五條規(guī)則:

不要提供任何會(huì)修改對象狀態(tài)的方法(也稱為設(shè)值方法) 。
保證類不會(huì)被擴(kuò)展。 為了防止子類化,一般做法是聲明這個(gè)類成為 final 的。
聲明所有的域都是 final 的。
聲明所有的域都為私有的。 這樣可以防止客戶端獲得訪問被域引用的可變對象的權(quán)限,并防止客戶端直接修改這些對象 。
確保對于任何可變組件的互斥訪問。 如果類具有指向可變對象的域,則必須確保該類的客戶端無法獲得指向這些對象的引用 。 并且,永遠(yuǎn)不要用客戶端提供的對象引用來初始化這樣的域,也不要從任何訪問方法( accessor)中返回該對象引用 。 在構(gòu)造器、訪問方法和 readObject 方法(詳見第 88 條)中請使用保護(hù)性拷貝( defensive copy )技術(shù)(詳見第50 條) 。

根據(jù)這五條原則,來品嘗一下 String.java 吧!

類定義

public final class String
 implements java.io.Serializable, Comparable<String>, CharSequence {}

對應(yīng)原則第二點(diǎn) 保證類不會(huì)被擴(kuò)展,使用 final 修飾。此外:

  • 實(shí)現(xiàn)了 Serializable 接口,具備序列化能力
  • 實(shí)現(xiàn)了 Comparable 接口,具備比較對象大小能力,根據(jù)單字符的大小比較。
  • 實(shí)現(xiàn)了 CharSequence 接口,表示是一個(gè)字符序列,實(shí)現(xiàn)了該接口下的一些方法。

字段

private final char value[]; // 儲(chǔ)存字符串
private int hash; // 哈希值,默認(rèn)為 0
private static final long serialVersionUID = -6849794470754667710L; // 序列化標(biāo)識

看起來 String 是一個(gè)獨(dú)立的對象,其實(shí)它是使用基本數(shù)據(jù)類型的數(shù)組 char[] 實(shí)現(xiàn)的。作為使用者,我們不需要打開 String 的黑匣子,直接根據(jù)它的 API 使用就可以了,這正是 Java 的封裝性的體現(xiàn)。但是作為開發(fā)者,我們就有必要一探究竟了。

private final char value[] , 對應(yīng)原則中第三條和第四條,聲明所有的域都是 final 的 ,聲明所有的域都為私有的??吹竭@里,你大概明白了一點(diǎn)為什么 String 不可變。因?yàn)檎嬲脕泶鎯?chǔ)字符串的字符數(shù)組是 final 修飾的,是不可變的。

構(gòu)造函數(shù)

String 的構(gòu)造函數(shù)很多,大致可以分為以下四種:

無參構(gòu)造

public String() {
 this.value = "".value;
}

無參構(gòu)造默認(rèn)構(gòu)建一個(gè)空字符串。鑒于 String 是不可變類,所以此構(gòu)造器并沒有什么意義,一般你也不會(huì)去構(gòu)建一個(gè)不可變的空字符串對象。

參數(shù)是 byte[]

public String(byte bytes[]) {}
public String(byte bytes[], int offset, int length) {}
public String(byte bytes[], Charset charset) {}
public String(byte bytes[], String charsetName) {}
public String(byte bytes[], int offset, int length, Charset charset) {}
public String(byte bytes[], int offset, int length, String charsetName) {}

已經(jīng)廢棄的就不再列舉了。上面這些構(gòu)造函數(shù)都差不多,最后都是調(diào)用 StringCoding.decode() 方法將字節(jié)數(shù)組轉(zhuǎn)換為字符數(shù)組,再賦值給 value[]。這里要注意一點(diǎn),參數(shù)未指定編碼格式的話,默認(rèn)使用系統(tǒng)的編碼格式,如果沒有獲取到系統(tǒng)編碼格式,則使用 ISO-8859-1 格式。

參數(shù)是 char[]

參數(shù)是 char[] 的構(gòu)造函數(shù)有 3 個(gè),逐個(gè)看一下:

public String(char value[]) {
 this.value = Arrays.copyOf(value, value.length);
}

為了保證不可變性,并沒有直接賦值,this.value = value。而是使用 Arrays.copy() 方法將參數(shù)中的字符數(shù)組內(nèi)容拷貝到 value[] 中。防止參數(shù)中字符數(shù)組的改變破壞了不可變性。

第二個(gè):

public String(char value[], int offset, int count) {
 if (offset < 0) {
 throw new StringIndexOutOfBoundsException(offset);
 }
 if (count <= 0) {
 if (count < 0) {
  throw new StringIndexOutOfBoundsException(count);
 }
 if (offset <= value.length) {
  this.value = "".value;
  return;
 }
 }
 // Note: offset or count might be near -1>>>1.
 if (offset > value.length - count) {
 throw new StringIndexOutOfBoundsException(offset + count);
 }
 this.value = Arrays.copyOfRange(value, offset, offset+count);
}

和上面的構(gòu)造函數(shù)一樣,只是截取了參數(shù)中字符數(shù)組的一部分來構(gòu)建字符串。

第三個(gè):

/*
 * Package private constructor which shares value array for speed.
 * this constructor is always expected to be called with share==true.
 * a separate constructor is needed because we already have a public
 * String(char[]) constructor that makes a copy of the given char[].
 *
 * 僅當(dāng)前包可使用。
 * 直接將 this.value 指向參數(shù)中的 char[],不再進(jìn)行 copy 操作
 * 性能好,節(jié)省內(nèi)存,外包不可使用,也不會(huì)破壞不可變性
 */
 String(char[] value, boolean share) {
 // assert share : "unshared not supported";
 this.value = value;
 }

這里的 share 一般只能為 true,雖然并沒有使用到。增加這個(gè)參數(shù)是為了和第一個(gè)構(gòu)造函數(shù)區(qū)分開來,表示 value[] 共享了參數(shù)中的字符數(shù)組,因?yàn)檫@里是直接賦值的,并沒有使用 Arrays.copy() 。那這不是破壞了 String 的不可變性嗎?其實(shí)并沒有,因?yàn)槟愀緵]法調(diào)用這個(gè)構(gòu)造函數(shù),它的包私有的。但是在 JDK 內(nèi)部你可以發(fā)現(xiàn)它的身影,

沒有了 copy 操作,大幅提高了效率。但是為了保證不可變性,外部是不能調(diào)用的。

其他構(gòu)造函數(shù)

// 基于代碼點(diǎn)
public String(int[] codePoints, int offset, int count) {} 

// 基于 StringBuffer,需要同步
public String(StringBuffer buffer) {
 synchronized(buffer) {
 this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
 }
}

// 基于 StringBuilder,不需要同步
public String(StringBuilder builder) {
 this.value = Arrays.copyOf(builder.getValue(), builder.length());
}

方法

回頭再看一下 String 的不可變性,value[] 是 private final 修飾的,這樣就真的可以保證不可變嗎?

final char[] value = {'a','b','c'};
value[1] = 'd';

這是不是就輕而易舉的打破了不可變性?final value[] 只能保證其引用不能再指向其他內(nèi)存地址,但是其真正的值還是可以改變的。所以僅僅通過一個(gè) final 是無法保證其值不變的,如果類本身提供方法修改實(shí)例值,那就沒有辦法保證不變性了。對應(yīng)原則中第一條,不要提供任何會(huì)修改對象狀態(tài)的方法,String 百分之百做到了這一點(diǎn),它沒有對外提供任何可以修改 value 的方法。

在 String 中有許多對字符串進(jìn)行操作的函數(shù),例如 substring concat replace replaceAll 等等,這些函數(shù)是否會(huì)修改類中的 value 域呢?下面就來看一看源碼。

substring(int beginIndex)

public String substring(int beginIndex) {
 if (beginIndex < 0) {
 throw new StringIndexOutOfBoundsException(beginIndex);
 }
 int subLen = value.length - beginIndex;
 if (subLen < 0) {
 throw new StringIndexOutOfBoundsException(subLen);
 }
 // beginIndex 不為 0, 返回一個(gè) 新的 String 對象
 return (beginIndex == 0) ? this : new String(value, beginIndex, subLen); 
}

concat(String str)

public String concat(String str) {
 int otherLen = str.length();
 if (otherLen == 0) {
 return this;
 }
 int len = value.length;
 char buf[] = Arrays.copyOf(value, len + otherLen);
 str.getChars(buf, len);
 return new String(buf, true); // 返回新的 String 對象
}

replace(char oldChar, char newChar)

public String replace(char oldChar, char newChar) {
 if (oldChar != newChar) {
 int len = value.length;
 int i = -1;
 char[] val = value; /* avoid getfield opcode */

  while (++i < len) {
  if (val[i] == oldChar) {
   break;
  }
  }
  if (i < len) {
  char buf[] = new char[len];
  for (int j = 0; j < i; j++) {
   buf[j] = val[j];
  }
  while (i < len) {
   char c = val[i];
   buf[i] = (c == oldChar) ? newChar : c;
   i++;
  }
  return new String(buf, true); // 返回新的 String 對象
  }
 }
 return this;
}

String 類的方法實(shí)現(xiàn)都相對簡單,但是無一例外,它們絕對不會(huì)去修改 value[] 的值,需要返回 String 對象的話,都會(huì)重新 new 一個(gè)。正像原則第五條中所說的,確保對于任何可變組件的互斥訪問。 如果類具有指向可變對象的域,則必須確保該類的客戶端無法獲得指向這些對象的引用。

String.intern()

public native String intern();

這個(gè)方法比較特殊,是個(gè)本地方法。如果該字符串在常量池中已經(jīng)存在,直接返回其引用。如果不存在,存入常量池再返回其引用。在下一篇文章中會(huì)進(jìn)行詳細(xì)介紹。

其他方法的源碼就不列舉了,感興趣的可以到我上傳的 jdk 源碼 看看,String.java,添加了部分注釋。

不可變類的好處

從頭到尾都在說不可變類,那么它有哪些好處呢?

  • 不可變對象比較簡單。
  • 不可變對象本質(zhì)上是線程安全的,它們不要求同步。不可變對象可以被自由地共享。
  • 不僅可以共享不可變對象,甚至也可以共享它們的內(nèi)部信息。
  • 不可變對象為其他對象提供了大量的構(gòu)件。無論是可變的還是不可變的對象。
  • 不可變對象無償?shù)靥峁┝耸〉脑有浴?/li>

不可變類真正唯一的缺點(diǎn)是,對于每個(gè)不同的值都需要一個(gè)單獨(dú)的對象。所以當(dāng)需要大量字符串對象的時(shí)候,String 就成了性能瓶頸,這也催生了 StringBuffer 和 StringBuilder。后面會(huì)單獨(dú)分析。

String 真的不可變嗎 ?

學(xué)習(xí)就是自己不斷打自己臉的過程。真的沒有辦法修改 String 對象的值嗎?答案肯定是否定的,反射機(jī)制可以做到很多平常做不到的事情。

String str = "123";
System.out.println(str);
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
char[] value = (char[]) field.get(str);
value[1] = '3';
System.out.println(str);

執(zhí)行結(jié)果:

123
133

通過反射,的確修改了 value[] 的值。

總結(jié)

借著 String 源碼,說了說 不可變類。簡單總結(jié)一下 String 做了哪些措施來保證不可變性:

  • value[] 使用 private final 修飾
  • 構(gòu)造函數(shù)中復(fù)制實(shí)參的值給 value[]
  • 不對外提供任何修改 value[] 值的方法
  • 需要返回 String 的方法,絕不返回原對象,都是重新 new 一個(gè) String 返回

下一篇還是寫 String , 說說 String 在內(nèi)存中的位置和字符串常量池的一些知識,以及 String 相關(guān)的常見面試題。

好了,以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對腳本之家的支持。

相關(guān)文章

  • Java8新特性之泛型的目標(biāo)類型推斷_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    Java8新特性之泛型的目標(biāo)類型推斷_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    泛型是Java SE 1.5的新特性,泛型的本質(zhì)是參數(shù)化類型,也就是說所操作的數(shù)據(jù)類型被指定為一個(gè)參數(shù)。下面通過本文給分享Java8新特性之泛型的目標(biāo)類型推斷,感興趣的朋友參考下吧
    2017-06-06
  • 解決IDEA報(bào)錯(cuò),無效的源發(fā)行版 無效的目標(biāo)發(fā)行版:22問題

    解決IDEA報(bào)錯(cuò),無效的源發(fā)行版 無效的目標(biāo)發(fā)行版:22問題

    在項(xiàng)目編譯過程中,可能會(huì)出現(xiàn)“無效的源發(fā)行版”或“無效的目標(biāo)發(fā)行版”的報(bào)錯(cuò)信息,原因通常是編譯使用的JDK版本與項(xiàng)目設(shè)置的發(fā)布版本不一致,解決這類問題的辦法是統(tǒng)一JDK版本,具體操作為:在IDE的項(xiàng)目設(shè)置中(如File->ProjectStructure->ProjectSettings)
    2024-10-10
  • Javafx利用fxml變換場景的實(shí)現(xiàn)示例

    Javafx利用fxml變換場景的實(shí)現(xiàn)示例

    本文主要介紹了Javafx利用fxml變換場景的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-07-07
  • Java并發(fā)編程之淺談ReentrantLock

    Java并發(fā)編程之淺談ReentrantLock

    今天帶大家學(xué)習(xí)Java并發(fā)編程的相關(guān)知識,文中對Java ReentrantLock作了非常詳細(xì)的圖文示例,對正在學(xué)習(xí)java的小伙伴們有很好地幫助,需要的朋友可以參考下
    2021-05-05
  • 教你如何使用Java實(shí)現(xiàn)WebSocket

    教你如何使用Java實(shí)現(xiàn)WebSocket

    這篇文章主要介紹了教你如何使用Java實(shí)現(xiàn)WebSocket問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • 實(shí)例講解Java中的synchronized

    實(shí)例講解Java中的synchronized

    這篇文章主要介紹了Java中synchronized的使用方法,文中講解非常細(xì)致,代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-07-07
  • java并發(fā)編程工具類PriorityBlockingQueue優(yōu)先級隊(duì)列

    java并發(fā)編程工具類PriorityBlockingQueue優(yōu)先級隊(duì)列

    這篇文章主要為大家介紹了java并發(fā)編程工具類PriorityBlockingQueue優(yōu)先級隊(duì)列的方法示例應(yīng)用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步
    2022-03-03
  • Java servlet通過事件驅(qū)動(dòng)進(jìn)行高性能長輪詢詳解

    Java servlet通過事件驅(qū)動(dòng)進(jìn)行高性能長輪詢詳解

    這篇文章主要介紹了基于servlet3.0+事件驅(qū)動(dòng)實(shí)現(xiàn)高性能長輪詢的操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2022-06-06
  • Spring Boot整合EasyExcel(完整版包含上傳解析excel和下載模板)

    Spring Boot整合EasyExcel(完整版包含上傳解析excel和下載模板)

    這篇文章主要介紹了Spring Boot整合EasyExcel(完整版包含上傳解析excel和下載模板),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12
  • 詳解Java實(shí)現(xiàn)簡單SPI流程

    詳解Java實(shí)現(xiàn)簡單SPI流程

    這篇文章主要介紹了Java實(shí)現(xiàn)簡單SPI流程,SPI英文全稱為Service Provider Interface,顧名思義,服務(wù)提供者接口,它是jdk提供給“服務(wù)提供廠商”或者“插件開發(fā)者”使用的接口
    2023-03-03

最新評論

苗栗市| 都江堰市| 富阳市| 娄底市| 神农架林区| 石嘴山市| 石河子市| 石城县| 甘泉县| 修水县| 临城县| 武穴市| 黄陵县| 锡林郭勒盟| 阿勒泰市| 弋阳县| 清涧县| 吴旗县| 琼中| 凉城县| 临澧县| 丁青县| 都江堰市| 中超| 台南市| 涿鹿县| 绥阳县| 莱西市| 泗阳县| 旬阳县| 木里| 饶平县| 阳东县| 武乡县| 隆昌县| 铜梁县| 清河县| 浙江省| 玉树县| 江陵县| 余庆县|