文中相關(guān)源碼: String.java
我們提供的服務(wù)有:做網(wǎng)站、網(wǎng)站設(shè)計(jì)、微信公眾號(hào)開(kāi)發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、高安ssl等。為近千家企事業(yè)單位解決了網(wǎng)站和推廣的問(wèn)題。提供周到的售前咨詢(xún)和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的高安網(wǎng)站制作公司
今天來(lái)說(shuō)說(shuō) String。
貫穿全文,你需要始終記住這句話,String 是不可變類(lèi) 。其實(shí)前面說(shuō)過(guò)的所有基本數(shù)據(jù)類(lèi)型包裝類(lèi)都是不可變類(lèi),但是在 String 的源碼中,不可變類(lèi) 的概念體現(xiàn)的更加淋漓盡致。所以,在閱讀 String 源碼的同時(shí),抽絲剝繭,你會(huì)對(duì)不可變類(lèi)有更深的理解。
什么是不可變類(lèi) ?
首先來(lái)看一下什么是不可變類(lèi)?Effective Java 第三版 第 17 條 使不可變性最小化 中對(duì) 不可變類(lèi) 的解釋?zhuān)?/p>
不可變類(lèi)是指其實(shí)例不能被修改的類(lèi)。每個(gè)實(shí)例中包含的所有信息都必須在創(chuàng)建該實(shí)例的時(shí)候就提供,并在對(duì)象的整個(gè)生命周期 (lifetime) 內(nèi)固定不變 。
為了使類(lèi)成為不可變,要遵循下面五條規(guī)則:不要提供任何會(huì)修改對(duì)象狀態(tài)的方法(也稱(chēng)為設(shè)值方法) 。
保證類(lèi)不會(huì)被擴(kuò)展。 為了防止子類(lèi)化,一般做法是聲明這個(gè)類(lèi)成為 final 的。
聲明所有的域都是 final 的。
聲明所有的域都為私有的。 這樣可以防止客戶(hù)端獲得訪問(wèn)被域引用的可變對(duì)象的權(quán)限,并防止客戶(hù)端直接修改這些對(duì)象 。
確保對(duì)于任何可變組件的互斥訪問(wèn)。 如果類(lèi)具有指向可變對(duì)象的域,則必須確保該類(lèi)的客戶(hù)端無(wú)法獲得指向這些對(duì)象的引用 。 并且,永遠(yuǎn)不要用客戶(hù)端提供的對(duì)象引用來(lái)初始化這樣的域,也不要從任何訪問(wèn)方法( accessor)中返回該對(duì)象引用 。 在構(gòu)造器、訪問(wèn)方法和 readObject 方法(詳見(jiàn)第 88 條)中請(qǐng)使用保護(hù)性拷貝( defensive copy )技術(shù)(詳見(jiàn)第50 條) 。
根據(jù)這五條原則,來(lái)品嘗一下 String.java 吧!
類(lèi)定義
public final class String implements java.io.Serializable, Comparable, CharSequence {}
對(duì)應(yīng)原則第二點(diǎn) 保證類(lèi)不會(huì)被擴(kuò)展,使用 final 修飾。此外:
字段
private final char value[]; // 儲(chǔ)存字符串 private int hash; // 哈希值,默認(rèn)為 0 private static final long serialVersionUID = -6849794470754667710L; // 序列化標(biāo)識(shí)
看起來(lái) String 是一個(gè)獨(dú)立的對(duì)象,其實(shí)它是使用基本數(shù)據(jù)類(lèi)型的數(shù)組 char[] 實(shí)現(xiàn)的。作為使用者,我們不需要打開(kāi) String 的黑匣子,直接根據(jù)它的 API 使用就可以了,這正是 Java 的封裝性的體現(xiàn)。但是作為開(kāi)發(fā)者,我們就有必要一探究竟了。
private final char value[]
, 對(duì)應(yīng)原則中第三條和第四條,聲明所有的域都是 final 的 ,聲明所有的域都為私有的??吹竭@里,你大概明白了一點(diǎn)為什么 String 不可變。因?yàn)檎嬲脕?lái)存儲(chǔ)字符串的字符數(shù)組是 final 修飾的,是不可變的。
構(gòu)造函數(shù)
String 的構(gòu)造函數(shù)很多,大致可以分為以下四種:
無(wú)參構(gòu)造
public String() { this.value = "".value; }
無(wú)參構(gòu)造默認(rèn)構(gòu)建一個(gè)空字符串。鑒于 String 是不可變類(lèi),所以此構(gòu)造器并沒(méi)有什么意義,一般你也不會(huì)去構(gòu)建一個(gè)不可變的空字符串對(duì)象。
參數(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)的編碼格式,如果沒(méi)有獲取到系統(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); }
為了保證不可變性,并沒(méi)有直接賦值,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ù)組的一部分來(lái)構(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,雖然并沒(méi)有使用到。增加這個(gè)參數(shù)是為了和第一個(gè)構(gòu)造函數(shù)區(qū)分開(kāi)來(lái),表示 value[] 共享了參數(shù)中的字符數(shù)組,因?yàn)檫@里是直接賦值的,并沒(méi)有使用 Arrays.copy()
。那這不是破壞了 String 的不可變性嗎?其實(shí)并沒(méi)有,因?yàn)槟愀緵](méi)法調(diào)用這個(gè)構(gòu)造函數(shù),它的包私有的。但是在 JDK 內(nèi)部你可以發(fā)現(xiàn)它的身影,
沒(méi)有了 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)存地址,但是其真正的值還是可以改變的。所以?xún)H僅通過(guò)一個(gè) final 是無(wú)法保證其值不變的,如果類(lèi)本身提供方法修改實(shí)例值,那就沒(méi)有辦法保證不變性了。對(duì)應(yīng)原則中第一條,不要提供任何會(huì)修改對(duì)象狀態(tài)的方法,String 百分之百做到了這一點(diǎn),它沒(méi)有對(duì)外提供任何可以修改 value 的方法。
在 String 中有許多對(duì)字符串進(jìn)行操作的函數(shù),例如 substring concat replace replaceAll 等等,這些函數(shù)是否會(huì)修改類(lèi)中的 value 域呢?下面就來(lái)看一看源碼。
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 對(duì)象 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 對(duì)象 }
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 對(duì)象 } } return this; }
String 類(lèi)的方法實(shí)現(xiàn)都相對(duì)簡(jiǎn)單,但是無(wú)一例外,它們絕對(duì)不會(huì)去修改 value[] 的值,需要返回 String 對(duì)象的話,都會(huì)重新 new 一個(gè)。正像原則第五條中所說(shuō)的,確保對(duì)于任何可變組件的互斥訪問(wèn)。 如果類(lèi)具有指向可變對(duì)象的域,則必須確保該類(lèi)的客戶(hù)端無(wú)法獲得指向這些對(duì)象的引用。
String.intern()
public native String intern();
這個(gè)方法比較特殊,是個(gè)本地方法。如果該字符串在常量池中已經(jīng)存在,直接返回其引用。如果不存在,存入常量池再返回其引用。在下一篇文章中會(huì)進(jìn)行詳細(xì)介紹。
其他方法的源碼就不列舉了,感興趣的可以到我上傳的 jdk 源碼 看看,String.java,添加了部分注釋。
不可變類(lèi)的好處
從頭到尾都在說(shuō)不可變類(lèi),那么它有哪些好處呢?
不可變類(lèi)真正唯一的缺點(diǎn)是,對(duì)于每個(gè)不同的值都需要一個(gè)單獨(dú)的對(duì)象。所以當(dāng)需要大量字符串對(duì)象的時(shí)候,String 就成了性能瓶頸,這也催生了 StringBuffer 和 StringBuilder。后面會(huì)單獨(dú)分析。
String 真的不可變嗎 ?
學(xué)習(xí)就是自己不斷打自己臉的過(guò)程。真的沒(méi)有辦法修改 String 對(duì)象的值嗎?答案肯定是否定的,反射機(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
通過(guò)反射,的確修改了 value[] 的值。
總結(jié)
借著 String 源碼,說(shuō)了說(shuō) 不可變類(lèi)。簡(jiǎn)單總結(jié)一下 String 做了哪些措施來(lái)保證不可變性:
下一篇還是寫(xiě) String , 說(shuō)說(shuō) String 在內(nèi)存中的位置和字符串常量池的一些知識(shí),以及 String 相關(guān)的常見(jiàn)面試題。
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)創(chuàng)新互聯(lián)的支持。