亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java 中的 String對(duì)象為什么是不可變的

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

什么是不可變對(duì)象?

String對(duì)象是不可變的,但這僅意味著你無(wú)法通過(guò)調(diào)用它的公有方法來(lái)改變它的值。

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

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

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

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

打印結(jié)果為:

s = ABCabc
 s = 123456

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

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

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

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

為什么String對(duì)象是不可變的?

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

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類做了一些改動(dòng),主要是改變了substring方法執(zhí)行時(shí)的行為,這和本文的主題不相關(guān)。JDK1.7中String類的主要成員變量就剩下了兩個(gè):

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

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

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

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

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

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

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

打印結(jié)果:

ss = 123456
 ss = 123456

String對(duì)象真的不可變嗎?

從上文可知String的成員變量是private final 的,也就是初始化之后不可改變。那么在這幾個(gè)成員中, value比較特殊,因?yàn)樗且粋€(gè)引用變量,而不是真正的對(duì)象。value是final修飾的,也就是說(shuō)final不能再指向其他數(shù)組對(duì)象,那么我能改變value指向的數(shù)組嗎? 比如將數(shù)組中的某個(gè)位置上的字符變?yōu)橄聞澗€“_”。 至少在我們自己寫(xiě)的普通代碼中不能夠做到,因?yàn)槲覀兏静荒軌蛟L問(wèn)到這個(gè)value引用,更不能通過(guò)這個(gè)引用去修改數(shù)組。

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

 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屬性的訪問(wèn)權(quán)限
 valueFieldOfString.setAccessible(true);
 //獲取s對(duì)象上的value屬性的值
 char[] value = (char[]) valueFieldOfString.get(s);
 //改變value所引用的數(shù)組中的第5個(gè)字符
 value[5] = '_';
 System.out.println("s = " + s); //Hello_World
 }

打印結(jié)果為:

s = Hello World
 s = Hello_World

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

相關(guān)文章

  • Spring使用三級(jí)緩存解決循環(huán)依賴的問(wèn)題

    Spring使用三級(jí)緩存解決循環(huán)依賴的問(wèn)題

    本文給大家分享Spring使用三級(jí)緩存解決循環(huán)依賴的問(wèn)題,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2021-06-06
  • SpringBoot超詳細(xì)講解yaml配置文件

    SpringBoot超詳細(xì)講解yaml配置文件

    這篇文章主要介紹了SpringBoot中的yaml配置文件問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-06-06
  • mybatis 集合嵌套查詢和集合嵌套結(jié)果的區(qū)別說(shuō)明

    mybatis 集合嵌套查詢和集合嵌套結(jié)果的區(qū)別說(shuō)明

    這篇文章主要介紹了mybatis 集合嵌套查詢和集合嵌套結(jié)果的區(qū)別說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • springboot設(shè)置了server.port但是沒(méi)有用,還是8080問(wèn)題

    springboot設(shè)置了server.port但是沒(méi)有用,還是8080問(wèn)題

    這篇文章主要介紹了springboot設(shè)置了server.port但是沒(méi)有用,還是8080問(wèn)題的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • Netty分布式ByteBuf使用page級(jí)別的內(nèi)存分配解析

    Netty分布式ByteBuf使用page級(jí)別的內(nèi)存分配解析

    這篇文章主要介紹了Netty分布式ByteBuf使用page級(jí)別的內(nèi)存分配解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-03-03
  • 詳解Spring事件發(fā)布與監(jiān)聽(tīng)機(jī)制

    詳解Spring事件發(fā)布與監(jiān)聽(tīng)機(jī)制

    Spring提供了ApplicationContext事件機(jī)制,可以發(fā)布和監(jiān)聽(tīng)事件,這個(gè)特性非常有用。Spring內(nèi)置了一些事件和監(jiān)聽(tīng)器,例如在Spring容器啟動(dòng)前,Spring容器啟動(dòng)后,應(yīng)用啟動(dòng)失敗后等事件發(fā)生后,監(jiān)聽(tīng)在這些事件上的監(jiān)聽(tīng)器會(huì)做出相應(yīng)的響應(yīng)處理
    2021-06-06
  • 深入理解Java垃圾回收機(jī)制以及內(nèi)存泄漏

    深入理解Java垃圾回收機(jī)制以及內(nèi)存泄漏

    下面小編就為大家?guī)?lái)一篇深入理解Java垃圾回收機(jī)制以及內(nèi)存泄漏。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給的大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2016-05-05
  • java 遠(yuǎn)程文件url如何轉(zhuǎn)為輸入流

    java 遠(yuǎn)程文件url如何轉(zhuǎn)為輸入流

    這篇文章主要介紹了java 遠(yuǎn)程文件url如何轉(zhuǎn)為輸入流方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • 帶你快速搞定java并發(fā)庫(kù)

    帶你快速搞定java并發(fā)庫(kù)

    本文主要介紹了java高并發(fā)寫(xiě)入用戶信息到數(shù)據(jù)庫(kù)的幾種方法,具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧,希望能給你帶來(lái)幫助
    2021-07-07
  • JVM分配和回收堆外內(nèi)存的方式與注意點(diǎn)

    JVM分配和回收堆外內(nèi)存的方式與注意點(diǎn)

    JVM啟動(dòng)時(shí)分配的內(nèi)存稱為堆內(nèi)存,與之相對(duì)的,在代碼中還可以使用堆外內(nèi)存,比如Netty,廣泛使用了堆外內(nèi)存,下面這篇文章主要給大家介紹了關(guān)于JVM分配和回收堆外內(nèi)存的方式與注意點(diǎn),需要的朋友可以參考下
    2022-07-07

最新評(píng)論