最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java 中的 String對象為什么是不可變的

 更新時間:2015年10月12日 11:50:37   投稿:mrr  
String對象是不可變的,但這僅意味著你無法通過調(diào)用它的公有方法來改變它的值。本文給大家介紹java中的string對象為什么是不可變的,需要的朋友一起了解了解吧

什么是不可變對象?

String對象是不可變的,但這僅意味著你無法通過調(diào)用它的公有方法來改變它的值。

眾所周知, 在Java中, String類是不可變的。那么到底什么是不可變的對象呢? 可以這樣認(rèn)為:如果一個對象,在它創(chuàng)建完成之后,不能再改變它的狀態(tài),那么這個對象就是不可變的。不能改變狀態(tài)的意思是,不能改變對象內(nèi)的成員變量,包括基本數(shù)據(jù)類型的值不能改變,引用類型的變量不能指向其他的對象,引用類型指向的對象的狀態(tài)也不能改變。

區(qū)分對象和對象的引用

對于Java初學(xué)者, 對于String是不可變對象總是存有疑惑??聪旅娲a:

String s = "ABCabc";
System.out.println("s = " + s);
s = "123456";
System.out.println("s = " + s);

打印結(jié)果為:

s = ABCabc
 s = 123456

首先創(chuàng)建一個String對象s,然后讓s的值為“ABCabc”, 然后又讓s的值為“123456”。 從打印結(jié)果可以看出,s的值確實改變了。那么怎么還說String對象是不可變的呢? 其實這里存在一個誤區(qū): s只是一個String對象的引用,并不是對象本身。對象在內(nèi)存中是一塊內(nèi)存區(qū),成員變量越多,這塊內(nèi)存區(qū)占的空間越大。引用只是一個4字節(jié)的數(shù)據(jù),里面存放了它所指向的對象的地址,通過這個地址可以訪問對象。

也就是說,s只是一個引用,它指向了一個具體的對象,當(dāng)s=“123456”; 這句代碼執(zhí)行過之后,又創(chuàng)建了一個新的對象“123456”, 而引用s重新指向了這個心的對象,原來的對象“ABCabc”還在內(nèi)存中存在,并沒有改變。內(nèi)存結(jié)構(gòu)如下圖所示:

Java中的String為什么是不可變的? — String源碼分析

Java和C++的一個不同點是, 在Java中不可能直接操作對象本身,所有的對象都由一個引用指向,必須通過這個引用才能訪問對象本身,包括獲取成員變量的值,改變對象的成員變量,調(diào)用對象的方法等。而在C++中存在引用,對象和指針三個東西,這三個東西都可以訪問對象。其實,Java中的引用和C++中的指針在概念上是相似的,他們都是存放的對象在內(nèi)存中的地址值,只是在Java中,引用喪失了部分靈活性,比如Java中的引用不能像C++中的指針那樣進行加減運算。

為什么String對象是不可變的?

要理解String的不可變性,首先看一下String類中都有哪些成員變量。 在JDK1.6中,String的成員變量有以下幾個:

public final class String
  implements java.io.Serializable, Comparable<String>, CharSequence
{
  /** The value is used for character storage. */
  private final char value[];
  /** The offset is the first index of the storage that is used. */
  private final int offset;
  /** The count is the number of characters in the String. */
  private final int count;
  /** Cache the hash code for the string */
  private int hash; // Default to 0

在JDK1.7中,String類做了一些改動,主要是改變了substring方法執(zhí)行時的行為,這和本文的主題不相關(guān)。JDK1.7中String類的主要成員變量就剩下了兩個:

public final class String 
  implements java.io.Serializable, Comparable<String>, CharSequence { 
  /** The value is used for character storage. */ 
  private final char value[]; 
  /** Cache the hash code for the string */ 
  private int hash; // Default to 0

由以上的代碼可以看出, 在Java中String類其實就是對字符數(shù)組的封裝。JDK6中, value是String封裝的數(shù)組,offset是String在這個value數(shù)組中的起始位置,count是String所占的字符的個數(shù)。在JDK7中,只有一個value變量,也就是value中的所有字符都是屬于String這個對象的。這個改變不影響本文的討論。 除此之外還有一個hash成員變量,是該String對象的哈希值的緩存,這個成員變量也和本文的討論無關(guān)。在Java中,數(shù)組也是對象(可以參考我之前的文章java中數(shù)組的特性)。 所以value也只是一個引用,它指向一個真正的數(shù)組對象。其實執(zhí)行了String s = “ABCabc”; 這句代碼之后,真正的內(nèi)存布局應(yīng)該是這樣的:

Java中的String為什么是不可變的? &#8212; String源碼分析

value,offset和count這三個變量都是private的,并且沒有提供setValue, setOffset和setCount等公共方法來修改這些值,所以在String類的外部無法修改String。也就是說一旦初始化就不能修改, 并且在String類的外部不能訪問這三個成員。此外,value,offset和count這三個變量都是final的, 也就是說在String類內(nèi)部,一旦這三個值初始化了, 也不能被改變。所以可以認(rèn)為String對象是不可變的了。

那么在String中,明明存在一些方法,調(diào)用他們可以得到改變后的值。這些方法包括substring, replace, replaceAll, toLowerCase等。例如如下代碼:

String a = "ABCabc"; 
System.out.println("a = " + a); 
a = a.replace('A', 'a'); 
System.out.println("a = " + a);

打印結(jié)果為:

a = ABCabc
a = aBCabc

那么a的值看似改變了,其實也是同樣的誤區(qū)。再次說明, a只是一個引用, 不是真正的字符串對象,在調(diào)用a.replace(‘A', ‘a(chǎn)')時, 方法內(nèi)部創(chuàng)建了一個新的String對象,并把這個心的對象重新賦給了引用a。String中replace方法的源碼可以說明問題:

Java中的String為什么是不可變的? &#8212; String源碼分析

讀者可以自己查看其他方法,都是在方法內(nèi)部重新創(chuàng)建新的String對象,并且返回這個新的對象,原來的對象是不會被改變的。這也是為什么像replace, substring,toLowerCase等方法都存在返回值的原因。也是為什么像下面這樣調(diào)用不會改變對象的值:

String ss = "123456";
System.out.println("ss = " + ss);
ss.replace('1', '0');
System.out.println("ss = " + ss);

打印結(jié)果:

ss = 123456
 ss = 123456

String對象真的不可變嗎?

從上文可知String的成員變量是private final 的,也就是初始化之后不可改變。那么在這幾個成員中, value比較特殊,因為他是一個引用變量,而不是真正的對象。value是final修飾的,也就是說final不能再指向其他數(shù)組對象,那么我能改變value指向的數(shù)組嗎? 比如將數(shù)組中的某個位置上的字符變?yōu)橄聞澗€“_”。 至少在我們自己寫的普通代碼中不能夠做到,因為我們根本不能夠訪問到這個value引用,更不能通過這個引用去修改數(shù)組。

那么用什么方式可以訪問私有成員呢? 沒錯,用反射, 可以反射出String對象中的value屬性, 進而改變通過獲得的value引用改變數(shù)組的結(jié)構(gòu)。下面是實例代碼:

 public static void testReflection() throws Exception {
 //創(chuàng)建字符串"Hello World", 并賦給引用s
 String s = "Hello World"; 
 System.out.println("s = " + s); //Hello World
 //獲取String類中的value字段
 Field valueFieldOfString = String.class.getDeclaredField("value");
 //改變value屬性的訪問權(quán)限
 valueFieldOfString.setAccessible(true);
 //獲取s對象上的value屬性的值
 char[] value = (char[]) valueFieldOfString.get(s);
 //改變value所引用的數(shù)組中的第5個字符
 value[5] = '_';
 System.out.println("s = " + s); //Hello_World
 }

打印結(jié)果為:

s = Hello World
 s = Hello_World

在這個過程中,s始終引用的同一個String對象,但是再反射前后,這個String對象發(fā)生了變化, 也就是說,通過反射是可以修改所謂的“不可變”對象的。但是一般我們不這么做。這個反射的實例還可以說明一個問題:如果一個對象,他組合的其他對象的狀態(tài)是可以改變的,那么這個對象很可能不是不可變對象。例如一個Car對象,它組合了一個Wheel對象,雖然這個Wheel對象聲明成了private final 的,但是這個Wheel對象內(nèi)部的狀態(tài)可以改變, 那么就不能很好的保證Car對象不可變。

相關(guān)文章

  • Spring boot使用spring retry重試機制的方法示例

    Spring boot使用spring retry重試機制的方法示例

    這篇文章主要介紹了Spring boot使用spring retry重試機制的方法示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-01-01
  • java中instanceof與Class的等價性代碼示例

    java中instanceof與Class的等價性代碼示例

    這篇文章主要介紹了java中instanceof與Class的等價性代碼示例,小編覺得還是挺不錯的,具有一定借鑒價值,需要的朋友可以參考下
    2018-01-01
  • idea編譯器工程out目錄修改方法步驟

    idea編譯器工程out目錄修改方法步驟

    多個工程在一個文件夾下,有時會變?yōu)樗械墓こ讨挥靡粋€out文件夾,這時運行會出錯,所以本文就來介紹一下out目錄修改,具有一定的參考價值,感興趣的可以了解一下
    2023-09-09
  • 調(diào)用java.lang.Runtime.exec的正確姿勢分享

    調(diào)用java.lang.Runtime.exec的正確姿勢分享

    這篇文章主要介紹了調(diào)用java.lang.Runtime.exec的正確姿勢,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Java多線程 Producer and Consumer設(shè)計模式

    Java多線程 Producer and Consumer設(shè)計模式

    這篇文章主要介紹了Java多線程 Producer and Consumer設(shè)計模式,producer是生產(chǎn)者的意思:指生產(chǎn)數(shù)據(jù)的線程,consumer是消費者的意思,指的是使用數(shù)據(jù)的線程,下文圍繞Producer及Consumer展開話題,需要的朋友可以參考一下
    2021-10-10
  • 基于Java字符串 "==" 與 "equals" 的深入理解

    基于Java字符串 "==" 與 "equals" 的深入理解

    本篇文章是對Java中的字符串"=="與"equals"進行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-06-06
  • Java基礎(chǔ)之多線程

    Java基礎(chǔ)之多線程

    以下是我們Java基礎(chǔ)多線程的一些知識點總結(jié),看完以后會覺得多線程也可以這么簡單,小編精心推薦,希望能對大家有所幫助
    2018-09-09
  • 淺談Java中浮點型數(shù)據(jù)保留兩位小數(shù)的四種方法

    淺談Java中浮點型數(shù)據(jù)保留兩位小數(shù)的四種方法

    今天在進行開發(fā)的過程中遇到了一個小問題,是關(guān)于如何將double類型的數(shù)據(jù)保留兩位小數(shù)。具有一定的參考價值,本文就詳細(xì)的介紹一下
    2021-09-09
  • springBoot中觀察者模式詳解

    springBoot中觀察者模式詳解

    觀察者模式增強應(yīng)用的靈活性和可擴展性,利用java.util.Observable和java.util.Observer實現(xiàn),Spring通過ApplicationEvent類和事件廣播器管理事件,支持應(yīng)用上下文和請求相關(guān)的事件處理,實現(xiàn)松耦合和動態(tài)通知,優(yōu)點包括松耦合、動態(tài)通知、易于擴展
    2024-11-11
  • java中jar包的生成與使用方式

    java中jar包的生成與使用方式

    這篇文章主要介紹了java中jar包的生成與使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-09-09

最新評論

昌乐县| 高碑店市| 宁南县| 玉田县| 远安县| 富平县| 东阳市| 丰原市| 青浦区| 达尔| 出国| 佛学| 乌兰浩特市| 和平区| 白山市| 华池县| 石阡县| 涟源市| 西乌珠穆沁旗| 贺州市| 隆安县| 夏津县| 运城市| 伊宁县| 鄂托克前旗| 平远县| 清苑县| 柳林县| 景宁| 巩留县| 榆树市| 九龙坡区| 永修县| 金山区| 射阳县| 麦盖提县| 垣曲县| 沾益县| 凤冈县| 碌曲县| 安宁市|