首頁 Java java教程 Java String原始碼分析

Java String原始碼分析

Feb 27, 2017 pm 03:25 PM

Java String原始碼分析

什麼是不可變物件?

眾所周知, 在Java中, String類別是不可變的。那麼到底什麼是不可變的物件呢? 可以這樣認為:如果一個對象,在它創建完成之後,就不能再改變它的狀態,那麼這個對象就是不可變的。不能改變狀態的意思是,不能改變對象內的成員變量,包括基本資料類型的值不能改變,引用類型的變數不能指向其他的對象,引用型別指向的對象的​​狀態也不能改變。

區分物件與物件的參考

對於Java初學者, 對於String是不可變物件總是存在著疑惑。看下面程式碼:

String s = "ABCabc"; 
System.out.println("s = " + s); 
 
s = "123456"; 
System.out.println("s = " + s);
登入後複製

列印結果為:

s = ABCabc
s = 123456
登入後複製

先建立一個String物件s,然後讓s的值為“ABCabc”, 然後又讓s的值為“123456”。 從列印結果可以看出,s的值確實改變了。那怎麼還說String物件是不可變的呢? 其實這裡有一個迷思: s只是一個String物件的引用,並不是物件本身。物件在記憶體中是一塊記憶體區,成員變數越多,這塊記憶體區佔的空間越大。引用只是一個4位元組的數據,裡面存放了它所指向的物件的位址,透過這個位址可以存取物件。

也就是說,s只是一個引用,它指向了一個具體的對象,當s=“123456”; 這句代碼執行過之後,又創建了一個新的對象“123456”, 而引用s重新指向了這個心的對象,原來的物件「ABCabc」還在記憶體中存在,並沒有改變。記憶體結構如下圖:

Java String源码分析

Java和C++的一個不同點是, 在Java中不可能直接操作物件本身,所有的對象都由一個引用指向,必須透過這個引用才能存取物件本身,包括取得成員變數的值,改變物件的成員變量,呼叫物件的方法等。而在C++中存在引用,物件和指標三個東西,這三個東西都可以存取物件。其實,Java中的引用和C++中的指標在概念上是相似的,他們都是存放的物件在記憶體中的位址值,只是在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方法執行時的行為,這和本文的主題不相關。 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類別其實就是字元數組的封裝。 JDK6中, value是String封裝的數組,offset是String在這個value數組中的起始位置,count是String所佔的字元的個數。在JDK7中,只有一個value變量,也就是value中的所有字元都是屬於String這個物件的。這個改變不影響本文的討論。 除此之外還有一個hash成員變量,是該String物件的雜湊值的緩存,這個成員變數也和本文的討論無關。在Java中,陣列也是物件(可以參考我之前的文章java中陣列的特性)。 所以value也只是一個引用,它指向一個真正的陣列物件。其實執行了String s = “ABCabc”; 這句程式碼之後,真正的記憶體佈局應該是這樣的:


Java String源码分析

# #value,offset和count這三個變數都是private的,沒有提供setValue, setOffset和setCount等公共方法來修改這些值,所以在String類別的外部無法修改String。也就是說一旦初始化就不能修改, 且在String類別的外部不能存取這三個成員。另外,value,offset和count這三個變數都是final的, 也就是說在String類別內部,一旦這三個值初始化了, 也不能改變。所以可以認為String物件是不可變的了。

那麼在String中,明明存在一些方法,呼叫他們可以得到改變後的值。這些方法包括substring, replace, replaceAll, toLowerCase等。例如如下程式碼:

String a = "ABCabc"; 
System.out.println("a = " + a); 
a = a.replace(&#39;A&#39;, &#39;a&#39;); 
System.out.println("a = " + a);
登入後複製

列印結果為:

a = ABCabc
a = aBCabc
登入後複製

那么a的值看似改变了,其实也是同样的误区。再次说明, a只是一个引用, 不是真正的字符串对象,在调用a.replace('A', 'a')时, 方法内部创建了一个新的String对象,并把这个心的对象重新赋给了引用a。String中replace方法的源码可以说明问题:

Java String源码分析

读者可以自己查看其他方法,都是在方法内部重新创建新的String对象,并且返回这个新的对象,原来的对象是不会被改变的。这也是为什么像replace, substring,toLowerCase等方法都存在返回值的原因。也是为什么像下面这样调用不会改变对象的值:

String ss = "123456"; 
 
System.out.println("ss = " + ss); 
 
ss.replace(&#39;1&#39;, &#39;0&#39;); 
 
System.out.println("ss = " + ss);
登入後複製

打印结果:

ss = 123456
ss = 123456
登入後複製

String对象真的不可变吗?

从上文可知String的成员变量是private final 的,也就是初始化之后不可改变。那么在这几个成员中, value比较特殊,因为他是一个引用变量,而不是真正的对象。value是final修饰的,也就是说final不能再指向其他数组对象,那么我能改变value指向的数组吗? 比如将数组中的某个位置上的字符变为下划线“_”。 至少在我们自己写的普通代码中不能够做到,因为我们根本不能够访问到这个value引用,更不能通过这个引用去修改数组。

那么用什么方式可以访问私有成员呢? 没错,用反射, 可以反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。下面是实例代码:

public static void testReflection() throws Exception { 
   
  //创建字符串"Hello World", 并赋给引用s 
  String s = "Hello World";  
   
  System.out.println("s = " + s); //Hello World 
   
  //获取String类中的value字段 
  Field valueFieldOfString = String.class.getDeclaredField("value"); 
   
  //改变value属性的访问权限 
  valueFieldOfString.setAccessible(true); 
   
  //获取s对象上的value属性的值 
  char[] value = (char[]) valueFieldOfString.get(s); 
   
  //改变value所引用的数组中的第5个字符 
  value[5] = &#39;_&#39;; 
   
  System.out.println("s = " + s); //Hello_World 
}
登入後複製

打印结果为:

s = Hello World
s = Hello_World
登入後複製

在这个过程中,s始终引用的同一个String对象,但是再反射前后,这个String对象发生了变化, 也就是说,通过反射是可以修改所谓的“不可变”对象的。但是一般我们不这么做。这个反射的实例还可以说明一个问题:如果一个对象,他组合的其他对象的状态是可以改变的,那么这个对象很可能不是不可变对象。例如一个Car对象,它组合了一个Wheel对象,虽然这个Wheel对象声明成了private final 的,但是这个Wheel对象内部的状态可以改变, 那么就不能很好的保证Car对象不可变。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

更多Java String源码分析相关文章请关注PHP中文网!


本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

公司安全軟件導致應用無法運行?如何排查和解決? 公司安全軟件導致應用無法運行?如何排查和解決? Apr 19, 2025 pm 04:51 PM

公司安全軟件導致部分應用無法正常運行的排查與解決方法許多公司為了保障內部網絡安全,會部署安全軟件。 ...

如何使用MapStruct簡化系統對接中的字段映射問題? 如何使用MapStruct簡化系統對接中的字段映射問題? Apr 19, 2025 pm 06:21 PM

系統對接中的字段映射處理在進行系統對接時,常常會遇到一個棘手的問題:如何將A系統的接口字段有效地映�...

如何優雅地獲取實體類變量名構建數據庫查詢條件? 如何優雅地獲取實體類變量名構建數據庫查詢條件? Apr 19, 2025 pm 11:42 PM

在使用MyBatis-Plus或其他ORM框架進行數據庫操作時,經常需要根據實體類的屬性名構造查詢條件。如果每次都手動...

如何將姓名轉換為數字以實現排序並保持群組中的一致性? 如何將姓名轉換為數字以實現排序並保持群組中的一致性? Apr 19, 2025 pm 11:30 PM

將姓名轉換為數字以實現排序的解決方案在許多應用場景中,用戶可能需要在群組中進行排序,尤其是在一個用...

IntelliJ IDEA是如何在不輸出日誌的情況下識別Spring Boot項目的端口號的? IntelliJ IDEA是如何在不輸出日誌的情況下識別Spring Boot項目的端口號的? Apr 19, 2025 pm 11:45 PM

在使用IntelliJIDEAUltimate版本啟動Spring...

Java對像如何安全地轉換為數組? Java對像如何安全地轉換為數組? Apr 19, 2025 pm 11:33 PM

Java對象與數組的轉換:深入探討強制類型轉換的風險與正確方法很多Java初學者會遇到將一個對象轉換成數組的�...

電商平台SKU和SPU數據庫設計:如何兼顧用戶自定義屬性和無屬性商品? 電商平台SKU和SPU數據庫設計:如何兼顧用戶自定義屬性和無屬性商品? Apr 19, 2025 pm 11:27 PM

電商平台SKU和SPU表設計詳解本文將探討電商平台中SKU和SPU的數據庫設計問題,特別是如何處理用戶自定義銷售屬...

使用TKMyBatis進行數據庫查詢時,如何優雅地獲取實體類變量名構建查詢條件? 使用TKMyBatis進行數據庫查詢時,如何優雅地獲取實體類變量名構建查詢條件? Apr 19, 2025 pm 09:51 PM

在使用TKMyBatis進行數據庫查詢時,如何優雅地獲取實體類變量名以構建查詢條件,是一個常見的難題。本文將針...

See all articles