前置
首先我們都知道final關鍵字修飾的變量必須初始化,且值不能改變。如果是基本類型則值不能改變,如果是引用類型,則引用地址不能改變,但是這個引用所指向的對象裏面的內容還是可以改變的。
我們先來猜猜看,以下哪一句會通不過編譯器編譯:
public class FinalBean {
private final int i = 0;
private final int j;
private final String name = "";
public FinalBean(){
j = 1;
this.name.concat("123");
this.name = "123";
}
}
this.name = "123";
這句,記住final的原理即可理解,那爲什麼this.name.concat(“123”);不會報錯呢,因爲底層實現是返回一個新的String對象。
看看concat()底層源碼
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
由源碼知道string是由final修飾的,並且其中的char數組value[](以字符數組的形式用來保存字符串)也是由final修飾的。但是我們知道數組是引用變量類型,當final修飾引用變量的時候,變量中保存的地址是不能更改的,但是指針指向的數組本身是可以更改的。如果分析到這裏有錯的話請指出,沒錯的話接着看核心問題。
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);
}
當我們調用concat方法試圖從一個字符串後面增添字符,根據這段源碼。它先是通過Array.copyOf()方法new出一個長度合適的新字符數組。
public static char[] copyOf(char[] original, int newLength) {
char[] copy = new char[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
然後可以看到,調用了System類中的arraycopy()方法。
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
最後調用了這個native方法來進行數組拷貝,所以爲什麼要採用這樣一個思路來處理string的concat操作?(其實可以拓展到所有對string的改變操作,這也是我真正想弄懂的地方,只是用concat來描述問題比較方便。)
特別是前面提到的,既然final修飾並不能限制數組本身的改變,爲什麼不對字符數組進行修改以達到字符串內容的修改?就算指針受final限制而不能更改,但這樣的方式一樣可以達到改動string的目的不是嗎?
我的思考
1、
我真特麼蠢,剛纔瞄了一眼AbstractStringBuilder源碼就想通了,我提出的這個問題恰好迎合了StringBuffer或StringBuilder的目的。啥都被你String做完了,還要別人來幹嘛?況且你String還有更多其它的用處呢。
以append方法爲例。它就是採用的擴容數組、然後拷貝字符串數組、直接返回當前字符數組。從而達到不產生新對象的目的。
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
2、
還有更重要的一點被我忽略了。數組的大小是不可變的,特別是concat等會改變被保存字符串長度的操作,更是無法說通。因此這種情況下必須重新new一個字符數組,而原數組變量不能改變指針的指向(已經被final修飾),因此必須要生成一個新的string對象,調用相應構造方法,將字符數組數據傳進去。