成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

你有沒有想過: 為什么Java中String是不可變的?

chavesgu / 3276人閱讀

摘要:有一種學(xué)得快的方法,就是一次不要學(xué)太多。用修飾的字符數(shù)組存儲字符串解答有三點(diǎn)在底層是用一個修飾的字符數(shù)組來存儲字符串的。修飾符保證了這個引用變量是不可變的,修飾符則保證了是類私有的,不能通過對象實(shí)例去訪問和更改數(shù)組里存放的字符。

有一種學(xué)得快的方法,就是一次不要學(xué)太多。
public final class String implements Serializable, Comparable, CharSequence {
    private final char[] value; // 用 private final 修飾的字符數(shù)組存儲字符串
    private int hash;
    private static final long serialVersionUID = -6849794470754667710L;
    
    public String() {
        this.value = "".value; 
    }

    public String(String var1) {
        this.value = var1.value;
        this.hash = var1.hash;
    }

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

有三點(diǎn):
1)String 在底層是用一個 private final 修飾的字符數(shù)組 value 來存儲字符串的。final 修飾符保證了 value 這個引用變量是不可變的,private 修飾符則保證了 value 是類私有的,不能通過對象實(shí)例去訪問和更改 value 數(shù)組里存放的字符。

注:有很多地方說 String 不可變是 final 起的作用,其實(shí)不嚴(yán)謹(jǐn)。因?yàn)榧词刮也挥?final 修改 value ,但初始化完成后我能保證以后都不更改 value 這個引用變量和 value[] 數(shù)組里存放的值,它也是從沒變化過的。final 只是保證了 value 這個引用變量是不能更改的,但不能保證 value[] 數(shù)組里存放的字符是不能更改的。如果把 private 改為 public 修飾,String類的對象是可以通過訪問 value 去更改 value[] 數(shù)組里存放的字符的,這時 String 就不再是不可變的了。所以不如說 private 起的作用更大一些。后面我們會通過 代碼1處 去驗(yàn)證。

2)String 類并沒有對外暴露可以修改 value[] 數(shù)組內(nèi)容的方法,并且 String 類內(nèi)部對字符串的操作和改變都是通過新建一個 String 對象去完成的,操作完返回的是新的 String 對象,并沒有改變原來對象的 value[] 數(shù)組。

注:String 類如果對外暴露可以更改 value[] 數(shù)組的方法,如 setter 方法,也是不能保證 String 是不可變的。后面我們會通過 代碼2處 去驗(yàn)證。

3)String 類是用 final 修飾的,保證了 String 類是不能通過子類繼承去破壞或更改它的不可變性的。

注:如果 String 類不是用 final 修飾的,也就是 String 類是可以被子類繼承的,那子類就可以改變父類原有的方法或?qū)傩?。后面我們會通過 代碼3處 去驗(yàn)證。

以上三個條件同時滿足,才讓 String 類成了不可變類,才讓 String 類具有了一旦實(shí)例化就不能改變它的內(nèi)容的屬性。

面試問題:String 類是用什么數(shù)據(jù)結(jié)構(gòu)來存儲字符串的?
由上面 String 的源碼可見,String 類是用數(shù)組的數(shù)據(jù)結(jié)構(gòu)來存儲字符串的。

代碼1處:

我們來看看如果把 private 修飾符換成 public,看看會發(fā)生什么?

// 先來模擬一個String類,初始化的時候?qū)?String 轉(zhuǎn)成 value 數(shù)組存儲
public final class WhyStringImutable {
   public final char[] value;  // 修飾符改成了 public 
   
   public WhyStringImutable() {
       this.value = "".toCharArray();
   }
   
   public WhyStringImutable(String str){
       this.value = str.toCharArray(); // 初始化時轉(zhuǎn)為字符數(shù)組
   }
   
   public char[] getValue(){
       return this.value;
   }
}
public class WhyStringImutableTest {
    public static void main(String[] args) {
        WhyStringImutable str = new WhyStringImutable("abcd");
        System.out.println("原str中value數(shù)組的內(nèi)容為:");
        System.out.println(str.getValue()); // 打印str對象中存放的字符數(shù)組
        System.out.println("----------");
        str.value[1] = "e"; // 通過對象實(shí)例訪問value數(shù)組并修改其內(nèi)容
        System.out.println("修改后str中value數(shù)組的內(nèi)容為:");
        System.out.println(str.getValue()); // 打印str對象中存放的字符數(shù)組
   }
}

輸出結(jié)果:

原str中value數(shù)組的內(nèi)容為:
abcd
----------
修改后str中value數(shù)組的內(nèi)容為:
aecd

由此可見,private 修改為 public 后,String 是可以通過對象實(shí)例訪問并修改所保存的value 數(shù)組的,并不能保證 String 的不可變性。

代碼2處:

我們?nèi)绻麑ν獗┞犊梢愿?value[] 數(shù)組的方法,如 setter 方法,看看又會發(fā)生什么?

public final class WhyStringImutable {
    private final char[] value;

    public WhyStringImutable() {
        this.value = "".toCharArray();
    }

    public WhyStringImutable(String str){
        this.value = str.toCharArray();
    }
    
    // 對外暴露可以修改 value 數(shù)組的方法
    public void setValue(int i, char ch){
        this.value[i] = ch;
    }
    
    public char[] getValue(){
        return this.value;
    }

}
public class WhyStringImutableTest {
    public static void main(String[] args) {
        WhyStringImutable str = new WhyStringImutable("abcd");
        System.out.println("原str中value數(shù)組的內(nèi)容為:");
        System.out.println(str.getValue()); // 打印str對象中存放的字符數(shù)組
        System.out.println("----------");
        str.setValue(1,"e"); // 通過set方法改變指定位置的value數(shù)組元素
        System.out.println("修改后str中value數(shù)組的內(nèi)容為:");
        System.out.println(str.getValue()); // 打印str對象中存放的字符數(shù)組
   }
}

輸出結(jié)果:

原str中value數(shù)組的內(nèi)容為:
abcd
----------
修改后str中value數(shù)組的內(nèi)容為:
aecd

由此可見,如果對外暴露了可以更改 value[] 數(shù)組內(nèi)容的方法,也是不能保證 String 的不可變性的。

代碼3處:

如果 WhyStringImutable 類去掉 final 修飾,其他的保持不變,又會怎樣呢?

public class WhyStringImutable {
    private final char[] value;
    
    public WhyStringImutable() {
        this.value = "".toCharArray();
    }
    
    public WhyStringImutable(String str){
        this.value = str.toCharArray(); // 初始化時轉(zhuǎn)為字符數(shù)組
    }
    
    public char[] getValue(){
        return this.value;
    }
}

寫一個子類繼承自WhyStringImutable 并修改原來父類的屬性,實(shí)現(xiàn)子類自己的邏輯:

public class WhyStringImutableChild extends WhyStringImutable {

    public char[] value; // 修改字符數(shù)組為 public 修飾,不要 final 

    public WhyStringImutableChild(String str){
        this.value = str.toCharArray();
    }

    public WhyStringImutableChild() {
        this.value = "".toCharArray();
    }

    @Override
    public char[] getValue() {
        return this.value;
    }
}
public class WhyStringImutableTest {
    public static void main(String[] args) {
        WhyStringImutableChild str = new WhyStringImutableChild("abcd");
        System.out.println("原str中value數(shù)組的內(nèi)容為:");
        System.out.println(str.getValue());
        System.out.println("----------");
        str.value[1] = "s";
        System.out.println("修改后str中value數(shù)組的內(nèi)容為:");
        System.out.println(str.getValue());
    }
}

運(yùn)行結(jié)果:

原str中value數(shù)組的內(nèi)容為:
abcd
----------
修改后str中value數(shù)組的內(nèi)容為:
ascd

由此可見,如果 String 類不是用 final 修飾的,是可以通過子類繼承來修改它原來的屬性的,所以也是不能保證它的不可變性的。

總結(jié)

綜上所分析,String 不可變的原因是 JDK 設(shè)計(jì)者巧妙的設(shè)計(jì)了如上三點(diǎn),保證了String 類是個不可變類,讓 String 具有了不可變的屬性。考驗(yàn)的是工程師構(gòu)造數(shù)據(jù)類型,封裝數(shù)據(jù)的功力,而不是簡單的用 final 來修飾,背后的設(shè)計(jì)思想值得我們理解和學(xué)習(xí)。

拓展

從上面的分析,我們知道,String 確實(shí)是個不可變的類,但我們就真的沒辦法改變 String 對象的值了嗎?不是的,通過反射可以改變 String 對象的值。

但是請謹(jǐn)慎那么做,因?yàn)橐坏┩ㄟ^反射改變對應(yīng)的 String 對象的值,后面再創(chuàng)建相同內(nèi)容的 String 對象時都會是反射改變后的值,這時候在后面的代碼邏輯執(zhí)行時就會出現(xiàn)讓你 “摸不著頭腦” 的現(xiàn)象,具有迷惑性,出了奇葩的問題你也很難排除到原因。后面在 代碼4處 我們會驗(yàn)證這個問題。

先來看看如何通過反射改變 String 對象的內(nèi)容:

public class WhyStringImutableTest {
    public static void main(String[] args) {
        String str = new String("123");
        System.out.println("反射前 str:"+str);
        try {
            Field field = String.class.getDeclaredField("value");
            field.setAccessible(true);
            char[] aa = (char[]) field.get(str);
            aa[1] = "1";
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
        System.out.println("反射后 str:"+str);
}

打印結(jié)果:

反射前 str:123
反射后 str:113 // 可見,反射后,str 的值確實(shí)改變了
代碼4處:

下面我們來驗(yàn)證因?yàn)橐坏┩ㄟ^反射改變對應(yīng)的 String 對象的值,后面再創(chuàng)建相同內(nèi)容的 String 對象時都會是反射改變后的值的問題:

public class WhyStringImutableTest {
    public static void main(String[] args) {
        String str = new String("123");
        System.out.println("反射前 str:"+str);
        try {
            Field field = String.class.getDeclaredField("value");
            field.setAccessible(true);
            char[] aa = (char[]) field.get(str);
            aa[1] = "1";
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
        System.out.println("反射后 str:"+str);
        
        String str2 = new String("123");
        System.out.println("str2:"+str2); // 我們來看 str2 會輸出什么,會輸出 113?
        System.out.println("判斷是否是同一對象:"+(str == str2)); // 判斷 str 和 str2 的內(nèi)存地址值是否相等
        System.out.println("判斷內(nèi)容是否相同:"+str.equals(str2)); // 判斷 str 和 str2 的內(nèi)容是否相等
}

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

反射前 str:123
反射后 str:113
str2:113 // 竟然不是123??而是輸出113,說明 str2 也是反射修改后的值。
判斷是否是同一對象:false // 輸出 false,說明在內(nèi)存中確實(shí)創(chuàng)建了兩個不同的對象
判斷內(nèi)容是否相同:true   // 輸出true,說明依然判斷為兩個對象內(nèi)容是相等的

由上面的輸出結(jié)果,我們可知,反射后再新建相同內(nèi)容的字符串對象時會是反射修改后的值,這就造成了很大迷惑性,在實(shí)際開發(fā)中要謹(jǐn)慎這么做。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/73357.html

相關(guān)文章

  • 第3章:抽象數(shù)據(jù)類型(ADT)和面向?qū)ο缶幊蹋∣OP) 3.1數(shù)據(jù)類型和類型檢查

    摘要:所有變量的類型在編譯時已知在程序運(yùn)行之前,因此編譯器也可以推導(dǎo)出所有表達(dá)式的類型。像變量的類型一樣,這些聲明是重要的文檔,對代碼讀者很有用,并由編譯器進(jìn)行靜態(tài)檢查。對象類型的值對象類型的值是由其類型標(biāo)記的圓。 大綱 1.編程語言中的數(shù)據(jù)類型2.靜態(tài)與動態(tài)數(shù)據(jù)類型3.類型檢查4.易變性和不變性5.快照圖6.復(fù)雜的數(shù)據(jù)類型:數(shù)組和集合7.有用的不可變類型8.空引用9.總結(jié) 編程語言中的數(shù)據(jù)...

    zhangqh 評論0 收藏0
  • 最最最常見Java面試題總結(jié)——第二周

    摘要:與都繼承自類,在中也是使用字符數(shù)組保存字符串,,這兩種對象都是可變的。采用字節(jié)碼的好處語言通過字節(jié)碼的方式,在一定程度上解決了傳統(tǒng)解釋型語言執(zhí)行效率低的問題,同時又保留了解釋型語言可移植的特點(diǎn)。 String和StringBuffer、StringBuilder的區(qū)別是什么?String為什么是不可變的? String和StringBuffer、StringBuilder的區(qū)別 可變性...

    yearsj 評論0 收藏0
  • StringString類型什么可變

    摘要:性能當(dāng)字符串是不可變時,字符串常量池才有意義。字符串常量池的出現(xiàn),可以減少創(chuàng)建相同字面量的字符串,讓不同的引用指向池中同一個字符串,為運(yùn)行時節(jié)約很多的堆內(nèi)存。 在學(xué)習(xí)Java的過程中,我們會被告知 String 被設(shè)計(jì)成不可變的類型。為什么 String 會被 Java 開發(fā)者有如此特殊的對待?他們的設(shè)計(jì)意圖和設(shè)計(jì)理念到底是什么?因此,我?guī)е韵氯齻€問題,對 String 進(jìn)行剖析: ...

    zhiwei 評論0 收藏0
  • Java系列】從JVM角度深度解析Java核心類String可變特性

    摘要:性能,大量運(yùn)用在哈希的處理中,由于的不可變性,可以只計(jì)算一次哈希值,然后緩存在內(nèi)部,后續(xù)直接取就好了。這是目前的一個底層字節(jié)碼的實(shí)現(xiàn),那么是不是沒有使用或者的必要了呢。 凱倫說,公眾號ID: KailunTalk,努力寫出最優(yōu)質(zhì)的技術(shù)文章,歡迎關(guān)注探討。 1. 前言 最近看到幾個有趣的關(guān)于Java核心類String的問題。 String類是如何實(shí)現(xiàn)其不可變的特性的,設(shè)計(jì)成不可變的好處...

    afishhhhh 評論0 收藏0
  • Java程序員常犯10個錯誤

    摘要:原文出自本文總結(jié)了程序員常犯的個錯誤??梢钥纯礊槭裁丛谥斜辉O(shè)計(jì)成不可變父類和子類的構(gòu)造函數(shù)以上這段代碼出現(xiàn)編譯錯誤,因?yàn)槟J(rèn)的父類構(gòu)造函數(shù)未定義。如果程序員定義構(gòu)造函數(shù),編譯器將不插入默認(rèn)的無參數(shù)構(gòu)造函數(shù)。 原文出自:http://www.programcreek.com/2014/05/top-10-mistakes-java-developers-make/ 本文總結(jié)了J...

    Andrman 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<