Java-String類最全匯總(上篇)
創(chuàng)建字符串
常見的構(gòu)造 String 的方式
// 方式一 String str = "Hello Bit"; // 方式二 String str2 = new String("Hello Bit"); // 方式三 char[] array = {'a', 'b', 'c'}; String str3 = new String(array);
注意事項:
- “hello” 這樣的字符串字面值常量, 類型也是 String.
- String 也是引用類型. String str = “Hello”; 這樣的代碼內(nèi)存布局如下
回憶 “引用”
我們曾經(jīng)在講數(shù)組的時候就提到了引用的概念.
引用類似于 C 語言中的指針, 只是在棧上開辟了一小塊內(nèi)存空間保存一個地址. 但是引用和指針又不太相同, 指針能進(jìn)行各種數(shù)字運(yùn)算(指針+1)之類的, 但是引用不能, 這是一種 “沒那么靈活” 的指針.
另外, 也可以把引用想象成一個標(biāo)簽, “貼” 到一個對象上. 一個對象可以貼一個標(biāo)簽, 也可以貼多個. 如果一個對象上面一個標(biāo)簽都沒有, 那么這個對象就會被 JVM 當(dāng)做垃圾對象回收掉.
Java 中數(shù)組, String, 以及自定義的類都是引用類型.
由于 String 是引用類型, 因此對于以下代碼
String str1 = "Hello"; String str2 = str1;
內(nèi)存布局如圖
那么有同學(xué)可能會說, 是不是修改 str1 , str2 也會隨之變化呢?
str1 = "world"; System.out.println(str2); // 執(zhí)行結(jié)果 //Hello
我們發(fā)現(xiàn), “修改” str1 之后, str2 也沒發(fā)生變化, 還是 hello?
事實(shí)上,
str1 = "world"
這樣的代碼并不算 “修改” 字符串, 而是讓 str1 這個引用指向了一個新的 String 對象.(這里我們修改的是指向,而非字符串本身)
提問:我們是否可以通過str1修改"Hello" --> “World”
答:做不到!??!
下面為大家討論一下傳字符串和字符數(shù)組返回時我們本身的字符串和字符數(shù)組是否會修改的問題:
public class TestDemo { public static void func(String s,char[] array) { s = "xiangxinhang"; array[0] = 'p'; } public static void main(String[] args) { String str = "abcdef"; char[] chars = {'b','i','t'}; func(str,chars); System.out.println(str); System.out.println(Arrays.toString(chars)); }
下面是內(nèi)存圖 圖解:
下面圖中為大家講解了為何字符串str和字符數(shù)組chars改變的值不一樣。
字符串比較相等
如果現(xiàn)在有兩個int型變量,判斷其相等可以使用 == 完成。
public static void main(String[] args) { int x = 10 ; int y = 10 ; System.out.println(x == y); }
運(yùn)行結(jié)果如下圖所示:
如果說現(xiàn)在在String類對象上使用 == ?
代碼1
public static void main(String[] args) { String str1 = "Hello"; String str2 = "Hello"; System.out.println(str1 == str2); }
運(yùn)行結(jié)果如下圖所示:
這里我們str1的"hello"在使用完后放入常量池,str2在賦值時我們直接取常量池中的"hello",所以這里我們的str1和str2相等。
代碼2
public static void main(String[] args) { String str1 = "Hello"; String str2 = "He" + "llo"; System.out.println(str1 == str2); }
運(yùn)行結(jié)果如下圖所示:
這里我們str2的"hello"雖然是拼接起來的,但是我們編譯器還是會認(rèn)為它就是"hello",然后直接從常量池中取出我們str1之前存放的"hello",從而str1和str2相等。
代碼3
public static void main(String[] args) { String str1 = "Hello"; String str2 = "He" + "llo"; String str3 = "He"; String str4 = str3 + "llo"; System.out.println(str1 == str4); }
運(yùn)行結(jié)果如下圖所示:
此時str3是一個變量–>編譯的時候不知道是啥,所以我們的str4再創(chuàng)建"He"時用的字符串不是從常量池拿的,因此我們str1和str4不相等。
看起來貌似沒啥問題, 再換個代碼試試, 發(fā)現(xiàn)情況不太妙.
代碼4
public static void main(String[] args) { String str1 = new String("Hello"); String str2 = new String("Hello"); System.out.println(str1 == str2); }
我們來分析兩種創(chuàng)建 String 方式的差異.
代碼1內(nèi)存布局
我們發(fā)現(xiàn), str1 和 str2 是指向同一個對象的. 此時如 “Hello” 這樣的字符串常量是在 字符串常量池 中.
關(guān)于字符串常量池
如 “Hello” 這樣的字符串字面值常量, 也是需要一定的內(nèi)存空間來存儲的. 這樣的常量具有一個特點(diǎn), 就是不需要修改(常量嘛). 所以如果代碼中有多個地方引用都需要使用 “Hello” 的話, 就直接引用到常量池的這個位置就行了, 而沒必要把 “Hello” 在內(nèi)存中存儲兩次.
代碼4內(nèi)存布局
通過
String str1 = new String("Hello");
這樣的方式創(chuàng)建的 String 對象相當(dāng)于再堆上另外開辟了空間來存儲"Hello" 的內(nèi)容, 也就是內(nèi)存中存在兩份 “Hello”.
String 使用 == 比較并不是在比較字符串內(nèi)容, 而是比較兩個引用是否是指向同一個對象.
關(guān)于對象的比較
面向?qū)ο缶幊陶Z言中, 涉及到對象的比較, 有三種不同的方式, 比較身份, 比較值, 比較類型.
在大部分編程語言中 == 是用來比較比較值的. 但是 Java 中的 == 是用來比較身份的.
如何理解比較值和比較身份呢?
可以想象一個場景, 現(xiàn)在取快遞, 都有包裹儲物柜. 上面有很多的格子. 每個格子里面都放著東西.
例如, “第二行, 左數(shù)第一列” 這個柜子和 “第二行, 右數(shù)第二列” 這個柜子是同一個柜子, 就是 身份相同. 如果身份相同, 那么里面放的東西一定也相同 (值一定也相同).
例如, “第一行, 左數(shù)第一列” 這個柜子和 “第一行, 左數(shù)第二列” 這兩個柜子不是同一個柜子, 但是柜子打開后發(fā)現(xiàn)里面放著的是完全一模一樣的兩雙鞋子. 這個時候就是 值相同.
Java 中要想比較字符串的內(nèi)容, 必須采用String類提供的equals方法.
public static void main(String[] args) { String str1 = new String("Hello"); String str2 = new String("Hello"); System.out.println(str1.equals(str2)); // System.out.println(str2.equals(str1)); // 或者這樣寫也行 }
equals 使用注意事項
現(xiàn)在需要比較 str 和 “Hello” 兩個字符串是否相等, 我們該如何來寫呢?
String str = new String("Hello"); // 方式一 System.out.println(str.equals("Hello")); // 方式二 System.out.println("Hello".equals(str));
在上面的代碼中, 哪種方式更好呢?
我們更推薦使用 “方式二”. 一旦 str 是 null, 方式一的代碼會拋出異常, 而方式二不會.(即equals前面的字符串必須不為null,否則會空指針異常)
String str = null; // 方式一 System.out.println(str.equals("Hello")); // 執(zhí)行結(jié)果 拋出 java.lang.NullPointerException 異常 // 方式二 System.out.println("Hello".equals(str)); // 執(zhí)行結(jié)果 false
方式一:
方式二:
注意事項: “Hello” 這樣的字面值常量, 本質(zhì)上也是一個 String 對象, 完全可以使用 equals 等 String 對象的方法.
字符串常量池
在上面的例子中, String類的兩種實(shí)例化操作, 直接賦值和 new 一個新的 String.
a) 直接賦值
String str1 = "hello" ; String str2 = "hello" ; String str3 = "hello" ; System.out.println(str1 == str2); // true System.out.println(str1 == str3); // true System.out.println(str2 == str3); // true
為什么現(xiàn)在并沒有開辟新的堆內(nèi)存空間呢?
String類的設(shè)計使用了共享設(shè)計模式
在JVM底層實(shí)際上會自動維護(hù)一個對象池(字符串常量池)
- 如果現(xiàn)在采用了直接賦值的模式進(jìn)行String類的對象實(shí)例化操作,那么該實(shí)例化對象(字符串內(nèi)容)將自動保存到這個對象池之中.
- 如果下次繼續(xù)使用直接賦值的模式聲明String類對象,此時對象池之中如若有指定內(nèi)容,將直接進(jìn)行引用
- 如若沒有,則開辟新的字符串對象而后將其保存在對象池之中以供下次使用
理解 “池” (pool)
“池” 是編程中的一種常見的, 重要的提升效率的方式, 我們會在未來的學(xué)習(xí)中遇到各種 “內(nèi)存池”, “線程池”, “數(shù)據(jù)庫連接池” …
然而池這樣的概念不是計算機(jī)獨(dú)有, 也是來自于生活中. 舉個栗子:
現(xiàn)實(shí)生活中有一種女神, 稱為 “綠茶”, 在和高富帥談著對象的同時, 還可能和別的屌絲搞曖昧. 這時候這個屌絲被稱為 “備胎”. 那么為啥要有備胎? 因為一旦和高富帥分手了, 就可以立刻找備胎接盤, 這樣 效率比較高.
如果這個女神, 同時在和很多個屌絲搞曖昧, 那么這些備胎就稱為 備胎池.
b) 采用構(gòu)造方法
類對象使用構(gòu)造方法實(shí)例化是標(biāo)準(zhǔn)做法。分析如下程序:
String str = new String("hello");
這樣的做法有兩個缺點(diǎn):
1.如果使用String構(gòu)造方法就會開辟兩塊堆內(nèi)存空間,并且其中一塊堆內(nèi)存將成為垃圾空間(字符串常量 “hello” 也是一個匿名對象, 用了一次之后就不再使用了, 就成為垃圾空間, 會被 JVM 自動回收掉).
2.字符串共享問題. 同一個字符串可能會被存儲多次, 比較浪費(fèi)空間.
我們可以使用 String 的 intern 方法來手動把 String 對象加入到字符串常量池中
// 該字符串常量并沒有保存在對象池之中 String str1 = new String("hello") ; String str2 = "hello" ; System.out.println(str1 == str2); // 執(zhí)行結(jié)果 //false String str1 = new String("hello").intern() ; String str2 = "hello" ; System.out.println(str1 == str2); // 執(zhí)行結(jié)果 //true
面試題:請解釋String類中兩種對象實(shí)例化的區(qū)別
1.直接賦值:只會開辟一塊堆內(nèi)存空間,并且該字符串對象可以自動保存在對象池中以供下次使用。
2.構(gòu)造方法:會開辟兩塊堆內(nèi)存空間,不會自動保存在對象池中,可以使用intern()方法手工入池。
綜上, 我們一般采取直接賦值的方式創(chuàng)建 String 對象.
理解字符串不可變
字符串是一種不可變對象. 它的內(nèi)容不可改變.
String 類的內(nèi)部實(shí)現(xiàn)也是基于 char[] 來實(shí)現(xiàn)的, 但是 String 類并沒有提供 set 方法之類的來修改內(nèi)部的字符數(shù)組.
public static void main(String[] args) { String str = "hello"; str = str + "world"; str += "!!!"; System.out.println(str); }
形如 += 這樣的操作, 表面上好像是修改了字符串, 其實(shí)不是. 內(nèi)存變化如下:
+= 之后 str 打印的結(jié)果卻是變了, 但是不是 String 對象本身發(fā)生改變, 而是 str 引用到了其他的對象.
回顧引用
引用相當(dāng)于一個指針, 里面存的內(nèi)容是一個地址. 我們要區(qū)分清楚當(dāng)前修改到底是修改了地址對應(yīng)內(nèi)存的內(nèi)容發(fā)生改變了, 還是引用中存的地址改變了.
那么如果實(shí)在需要修改字符串, 例如, 現(xiàn)有字符串 str = “Hello” , 想改成 str = “hello” , 該怎么辦?
a) 常見辦法: 借助原字符串, 創(chuàng)建新的字符串
public class TestDemo { public static void main(String[] args) { String str = "Hello"; str = "h" + str.substring(1); System.out.println(str); }
b) 特殊辦法: 使用 “反射” 這樣的操作可以破壞封裝, 訪問一個類內(nèi)部的 private 成員.
IDEA 中 ctrl + 左鍵 跳轉(zhuǎn)到 String 類的定義, 可以看到內(nèi)部包含了一個 char[] , 保存了字符串的內(nèi)容.
public class TestDemo { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { String str = "Hello"; // 獲取 String 類中的 value 字段. 這個 value 和 String 源碼中的 value 是匹配的. Field valueField = String.class.getDeclaredField("value"); // 將這個字段的訪問屬性設(shè)為 true valueField.setAccessible(true); // 把 str 中的 value 屬性獲取到. char[] value = (char[]) valueField.get(str); // 修改 value 的值 value[0] = 'h'; System.out.println(str); }
關(guān)于反射
反射是面向?qū)ο缶幊痰囊环N重要特性, 有些編程語言也稱為 “自省”.
指的是程序運(yùn)行過程中, 獲取/修改某個對象的詳細(xì)信息(類型信息, 屬性信息等), 相當(dāng)于讓一個對象更好的 “認(rèn)清自己” .
為什么 String 要不可變?(不可變對象的好處是什么?)
1.方便實(shí)現(xiàn)字符串對象池. 如果 String 可變, 那么對象池就需要考慮何時深拷貝字符串的問題了.
2.不可變對象是線程安全的.
3.不可變對象更方便緩存 hash code, 作為 key 時可以更高效的保存到 HashMap 中.
注意事項: 如下代碼不應(yīng)該在你的開發(fā)中出, 會產(chǎn)生大量的臨時對象, 效率比較低.
public static void main(String[] args) { String str = "hello" ; for(int x = 0; x < 1000; x++) { str += x ; } System.out.println(str); }
注意:字符串的拼接 會被優(yōu)化為 StringBuilder對象
到此這篇關(guān)于Java-String類最全匯總(上篇)的文章就介紹到這了,下篇的內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java中String類的常用方法總結(jié)
- Java?String類的理解及字符串常量池介紹
- Java中String類常用方法總結(jié)詳解
- Java基礎(chǔ)之String類使用與字符串比較
- Java中String類常用方法使用詳解
- 帶你一文深入認(rèn)識Java?String類
- Java String類常用方法梳理總結(jié)
- Java深入淺出講解String類常見方法
- Java詳細(xì)分析String類與StringBuffer和StringBuilder的使用方法
- Java?String類和StringBuffer類的區(qū)別介紹
- Java全面解析string類型的xml字符串
相關(guān)文章
Java啟動參數(shù)(-,?-X,?-XX參數(shù))的使用
本文主要介紹了Java啟動參數(shù)(-,?-X,?-XX參數(shù))的使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06Spring Boot之內(nèi)嵌tomcat版本升級操作示例
這篇文章主要為大家介紹了Spring Boot之內(nèi)嵌tomcat版本升級操作示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06Java 和 Javascript 的 Date 與 .Net 的 DateTime 之間的相互轉(zhuǎn)換
這篇文章主要介紹了Java 和 Javascript 的 Date 與 .Net 的 DateTime 之間的相互轉(zhuǎn)換的相關(guān)資料,非常不錯具有參考借鑒價值,需要的朋友可以參考下2016-06-06