String爲何不可變?(Java源碼解析)

String的源碼解析

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

如何修改已經初始化的String字符串的值

即便是不可變類,經過反射仍然能夠改變其屬性的值。 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());//李華
    }
}
複製代碼
相關文章
相關標籤/搜索