public final class String{
private final char value[];//容器,存放字符串的
private int hash;//哈希值
private static final long serialVersionUID = -6849794470754667710L;
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
//分配一個新的 String,將參數value[]的內容拷貝到String的value[]中
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
public String(int[] codePoints, int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= codePoints.length) {
this.value = "".value;
return;
}
}
// count+offset超過原數組的長度
if (offset > codePoints.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
final int end = offset + count;
// Pass 1: 計算char []的精確大小
int n = count;
for (int i = offset; i < end; i++) {
int c = codePoints[i];
if (Character.isBmpCodePoint(c))//判斷codePoints[i] 是否爲BMP範圍內的編碼,即c是否在['\u0000','\uFFFF']
continue;
else if (Character.isValidCodePoint(c))//肯定c是否有效Unicode指定值[0x000000,0X10FFFF]
n++;//有效就+1.
else throw new IllegalArgumentException(Integer.toString(c));
}
// Pass 2:分配並填寫char []
final char[] v = new char[n];//新建一個不可變的數組大小爲n(上步求得)的數組
for (int i = offset, j = 0; i < end; i++, j++) {
int c = codePoints[i];
if (Character.isBmpCodePoint(c))//判斷codePoints[i] 是否爲BMP範圍內的編碼
v[j] = (char)c;//賦值給v[j]
else //不然須要用兩個char來表示
Character.toSurrogates(c, v, j++);//將指定字符(Unicode代碼點)轉換爲存儲在{@code char}數組中的* UTF-16表示形式。
}
this.value = v;//賦值,內容可變,可是value引用的地址不變
/*
char[] c = new char[]{'1','2','3','4','5'};
final char value[];
value = c;
System.out.println(value);//12345
*/
}
...
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);
}
}
return this;
}
...
}
複製代碼
從源碼可見,咱們能夠知道一下信息:
a)String是最終類,由於是final修飾的class,不可被繼承,也無重寫一說。
b)實際存儲字符串的是一個數組,而且是final修飾的,分配空間以後內存地址不變。
c)全部成員變量都是private final修飾的,而且沒有提供對應的XXXSetter方法,不容許外部修改這些字段,而且 只能對它們賦值一次。
d)涉及value數組的操做(上面只提供了部分源碼)都使用了拷貝數組元素的方法,保證了不能在內部修改字符數組
因此說String在初始化以後是不可變的。java
即便是不可變類,經過反射仍然能夠改變其屬性的值。 IllegalArgumentException - 若是指定對象不是聲明底層字段(或者其子類或實現者)的類或接口的實例,或者解包轉換失敗。由於JVM在編譯時期, 就把final類型的String進行了優化, 在編譯時期就會把String處理成常量。,因此沒法直接修改String str = "123"值,而是經過爲聲明底層字段(或者其子類或實現者)的類或接口的實例來修改String str = "123"。 示例:數組
import java.lang.reflect.Field;
class People{
String str = "123";
}
public class StringDemo {
public static void main(String[] args) {
People p = new People();
System.err.println(p.str);//123
try {
Field field = People.class.getDeclaredField("str");
field.setAccessible(true);
field.set(p,"0");
System.err.println(p.str);//0 修改String值成功
} catch (Exception e) {
e.printStackTrace();
}
}
}
複製代碼
總結一下,如何才能自定義一個不可變類呢?bash
(1) 類使用final修飾符修飾。
(2)類的全部字段使用private final修飾。
(3)XXXSetter方法,getXXX方法返回拷貝的對象,不返回對象自己。
(4)構造器初始化成員變量時,使用深拷貝。ide
‘深拷貝是一個整個獨立的對象拷貝,深拷貝會拷貝全部的屬性,並拷貝屬性指向的動態分配的內存。當對象和它所引用的對象一塊兒拷貝時即發生深拷貝。深拷貝相比於淺拷貝速度較慢而且花銷較大。 簡而言之,深拷貝把要複製的對象所引用的對象都複製了一遍。優化
實現對象拷貝的類,必須實現Cloneable接口,並覆寫clone(). 注:若是沒有實現Cloneable接口,將出現 CloneNotSupportedException運行時異常。 示例:ui
/*
* 實現深拷貝
* */
class Teacher implements Cloneable {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Object clone() throws CloneNotSupportedException
{
return super.clone();
}
}
class Student_One implements Cloneable{
private String name;
private Teacher teacher;//添加教師的引用
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();//調用obeject的clone默認爲淺拷貝
}
}
class Student_Two implements Cloneable{
private String name;
private Teacher teacher;//添加教師的引用
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Object clone() throws CloneNotSupportedException {
//return super.clone();//調用obeject的clone默認爲淺拷貝
Student_Two student = (Student_Two) super.clone();
student.setTeacher((Teacher) student.getTeacher().clone());//T複製一份eacher對象並從新set進來
return student;
}
}
public class StringDemo1 {
public static void main(String[] args) throws CloneNotSupportedException {
//新建一個老師對象
Teacher teacher = new Teacher();
teacher.setAge(25);
teacher.setName("李華");
Student_One student_one = new Student_One();
student_one.setName("同窗甲");
System.err.println();
student_one.setTeacher(teacher);
//拷貝一個Student_One對象(淺拷貝)
Student_One student_one1 = (Student_One)student_one.clone();
System.err.println(student_one1.getTeacher().getName());//李華 原拷貝對象
//修改老師的名字,會把拷貝的對象的老師名稱也一同修改了,由於它們指向的是同一塊地址,也就是同一個對象
teacher.setName("黃珊");
System.err.println(student_one1.getTeacher().getName());//黃珊
//從新設置老師名爲爲李華
teacher.setName("李華");
Student_Two student_two = new Student_Two();
student_two.setTeacher(teacher);
student_two.setName("同窗乙");
//拷貝一個Student_Two對象
Student_Two student_two1 = (Student_Two) student_two.clone();
System.err.println(student_two1.getTeacher().getName());//李華 原拷貝對象
//修改老師的名字,打印發現並無影響原拷貝對象的值,因此爲深拷貝,是不一樣的兩個對象
teacher.setName("黃珊");
System.err.println(student_two1.getTeacher().getName());//李華
}
}
複製代碼