小魯班今年計算機專業大四了,在學校可學了不少軟件開發的東西,也自學了一些JAVA的后台框架,躊躇滿志,一心想着找個好單位實習。當投遞了無數份簡歷后,終於收到了一個公司發來的面試通知,小魯班欣喜若狂。
到了人家單位后,前台小姐姐給了小魯班一份筆試題目,要求在一個小時內完成,小魯班雙手接過題目后,粗略的看了一下題目,心里暗喜,嘻嘻這個還不簡單。一頓操作猛如虎,做完了感覺也沒什么錯誤。就交卷了,等待片刻后,小姐姐親切的說需要一周內等通知哦。於是呢,小魯班就回去耐心的等待了。可是半個月都快過去了,什么消息都沒有,小魯班就納悶了,明明我做的挺好的呀,為什么連面試的機會都不給我。
小魯班於是找到了他表哥魯班大師,正准備吐槽這件事,並把一些當時面試的題目重現了一些,並把自己對題目的理解也說了遍,魯班大師一看他填的答案就開始嘲諷他,前5道題目關於String類的判斷題可真是完全避開了正確答案呀,而且后邊的題目也是大部分都是錯了,人家當然不給你機會呀。
小魯班你可要虛心學習了,就拿下邊最簡單的一題來說,你怎么連==對於非基本數據類型是比較引用而不是比較值的都不知道呀
String str1 = new String("AA"); String str2 = new String("AA"); System.out.println(str1 == str2); 這里的正確答案是false
魯班大師:感覺你的JAVA基礎不咋地呀,你說說你在學校學習你所掌握的關於String類的知識點,你表哥今天有空幫你惡補一波吧。
小魯班垂頭喪氣的說到:
-
String類有如下這些特點
- String類是final類,也即意味着String類不能被繼承,並且它的成員方法都默認為final方法。
- String類其實是通過char數組來保存字符串的。
- String對象一旦被創建就是固定不變的了,對String對象的任何改變都不影響到原對象,相關的任何change操作都會生成新的對象。
魯班大師:嗯,不錯嘛,那有沒有深入一點的理解呢,比如關於字符串常量池
小魯班:這個我~~忘記了!
魯班大師:沒關系,那你得認真聽講了
小魯班:emmm
-
字符串常量池
- 我們知道字符串的分配和其他對象分配一樣,是需要消耗高昂的時間和空間的,而且字符串我們使用的非常多。JVM為了提高性能和減少內存的開銷,在實例化字符串的時候進行了一些優化:使用字符串常量池。每當我們創建字符串常量時,JVM會首先檢查字符串常量池,如果該字符串已經存在常量池中,那么就直接返回常量池中的實例引用。如果字符串不存在常量池中,就會實例化該字符串並且將其放到常量池中。由於String字符串的不可變性我們可以十分肯定常量池中一定不存在兩個相同的字符串。
- 字符串池的出現避免了相同內容的字符串的創建,節省了內存,省去了創建相同字符串的時間,同時提升了性能;另一方面,字符串池的缺點就是犧牲了JVM在常量池中遍歷對象所需要的時間,不過其時間成本相比而言比較低。
String a="AA";
String b="AA";
String c=new String("AA");
a、b和堆中創建的AA都是指向JVM字符串常量池中的"AA"對象,他們指向同一個對象。
new關鍵字一定會產生一個對象AA,同時這個對象是存儲在堆中。所以String c=new String("AA")這一句應該產生了兩個對象:保存在方法區中字符串常量池的AA和保存堆中AA。但是在Java中根本就不存在兩個完全一模一樣的字符串對象。故堆中的AA應該是引用字符串常量池中AA。所以c、堆AA、池AA的關系應該是:c--->堆AA--->池AA。
雖然a、b、c是不同的引用,但是從String的內部結構我們是可以理解上面的。String c = new String("AA");雖然c的內容是創建在堆中,但是他的內部value還是指向JVM常量池的AA的value,它構造AA時所用的參數依然是AA字符串常量。所以a==b是ture,因為內存地址是一樣的 a==c是false,因為c的內存地址指向是在堆中new的是新的地址,而不是在常量池的地址。

魯班大師又問了:我看你還挺懵的,你知道==和equals嗎
小魯班:這個我知道。
- 對於==,如果作用於基本數據類型的變量(byte,short,char,int,long,float,double,boolean ),則直接比較其存儲的"值"是否相等;如果作用於引用類型的變量(String),則比較的是所指向的對象的地址(即是否指向同一個對象)。
- 對於equals方法,注意:equals方法不能作用於基本數據類型的變量。如果沒有對equals方法進行重寫,則比較的是引用類型的變量所指向的對象的地址;而String類對equals方法進行了重寫,用來比較指向的字符串對象所存儲的字符串是否相等。其他的一些類諸如Double,Date,Integer等,都對equals方法進行了重寫用來比較指向的對象所存儲的內容是否相等。
魯班大師:嗯,答的不錯,但是要應付一些面試題,你還要知道這些。
- 單獨使用""引號創建的字符串都是常量,編譯期就已經確定存儲到String Pool中;
- 使用new String("")創建的對象會存儲到heap中,是運行期新創建的;
- 使用只包含常量的字符串連接符如"aa" + "aa"創建的也是常量,編譯期就能確定,已經確定存儲到String Pool中;
- 使用包含變量(引用)的字符串連接符如"aa" + s1創建的對象是運行期才創建的,存儲在heap中;
- 但是如果s1是被final修飾的話,則s1是屬於常量。結果存在String Pool,但是 final修飾的是一個方法返回的值也是在編譯器確定。
好了,這些你都知道了,那你把剛那份題目在做一次看看
String str1 = "aaa"; String str2 = "aaa"; System.out.println(str1 == str2);// true 因為String有常量池 String str3 = new String("aaa"); String str4 = new String("aaa"); System.out.println(str3 == str4);// false 可以看出用new的方式是生成不同的對象,比較堆上的 String s0="helloworld"; String s1="helloworld"; String s2="hello"+"world"; System.out.println(s0==s1); //true 可以看出s0跟s1是指向同一個對象 System.out.println(s0==s2); //true 可以看出s0跟s2是指向同一個對象 String st0="helloworld"; String st1=new String("helloworld"); String st2="hello" + new String("world"); System.out.println( st0==st1 ); //false 用new String() 創建的字符串不是常量,不能在編譯期就確定 System.out.println( st0==st2 ); //false st2地址存在堆中,不可能相同 System.out.println( st1==st2 ); //false String stri1="abc"; String stri2="def"; String stri3=stri1+stri2; System.out.println(stri3=="abcdef"); //false 變量相+是在的堆內存中創建 String strin0 = "a1"; String strin1 = "a" + 1; //這種不是變量,是常量 System.out.println((strin0 == strin1)); //result = true String strin2 = "atrue"; String strin3= "a" + "true"; System.out.println((strin2 == strin3)); //result = true String strin4 = "a3.4"; String strin5 = "a" + 3.4; System.out.println((strin4 == strin5)); //result = true String string0 = "ab"; String string1 = "b"; String string2 = "a" + string1; System.out.println((string0 == string2)); //result = false 在字符串的"+"連接中,有字符串引用存在,而引用的值在程序編譯期是無法確定的 String test="javalanguagespecification"; String test2="java"; String test3="language"; String test4="specification"; System.out.println(test == "java" + "language" + "specification"); //true 字符串字面量拼接操作是在Java編譯器編譯期間就執行了 System.out.println(test == test2 + test3 + test4); //false 字符串引用的"+"運算是在Java運行期間執行的 String ss0 = "ab"; final String ss1 = "b"; String ss2 = "a" + ss1; System.out.println((ss0 == ss2)); //result = true 對於final修飾的變量,它在編譯時被解析為常量值的一個本地拷貝存儲到自己的常量池中或嵌入到它的字節碼流中。所以此時的"a" + s1和"a" + "b"效果是一樣的 String ss10 = "ab"; final String ss11 = getS1(); String ss12 = "a" + ss11; System.out.println((ss10 == ss12)); //result = false 這里面雖然將s1用final修飾了,但是由於其賦值是通過方法調用返回的,那么它的值只能在運行期間確定 public static String getS1(){ return "b"; }
String 的 intern() 方法在運行過程中將字符串添加到 String Pool 中。
當一個字符串調用 intern() 方法時,如果 String Pool 中已經存在一個字符串和該字符串值相等(使用 equals() 方法進行確定),
那么就會返回 String Pool 中字符串的引用;否則,就會在 String Pool 中添加一個新的字符串,並返回這個新字符串的引用。
-
String s1 = new String("aaa"); -
String s2 = new String("aaa"); -
System.out.println(s1 == s2); // false -
String s3 = s1.intern(); -
String s4 = s1.intern(); -
System.out.println(s3 == s4); // true
魯班大師:優秀呀,小魯班!不過呢我們既然都研究了String,那么關於StringBuffer和StringBuilder也得知道,給你布置個作業,把他們3者的區別寫一下自己的見解,發到我的郵箱,今天就到此為止了,表哥得去開黑了。
小魯班:謝謝表哥,我一定好好整理的!小魯班回到家后,立馬奮筆疾書。。。
send to 魯班大師@qq.com
String、StringBuffer、StringBuilder的區別?
- 可變與不可變:String是不可變字符串對象,StringBuilder和StringBuffer是可變字符串對象(其內部的字符數組長度可變)。
- 是否多線程安全:String中的對象是不可變的,也就可以理解為常量,顯然線程安全。StringBuffer 與 StringBuilder 中的方法和功能完全是等價的,只是StringBuffer 中的方法大都采用了synchronized 關鍵字進行修飾,因此是線程安全的,而 StringBuilder 沒有這個修飾,可以被認為是非線程安全的。
- String、StringBuilder、StringBuffer三者的執行效率如下:
- StringBuilder > StringBuffer > String 當然這個是相對的,不一定在所有情況下都是這樣。比如String str = "hello"+ "world"的效率就比 StringBuilder st = new StringBuilder().append("hello").append("world")要高。因此,這三個類是各有利弊,應當根據不同的情況來進行選擇使用:
- 當字符串相加操作或者改動較少的情況下,建議使用 String str="hello"這種形式;
- 當字符串相加操作較多的情況下,建議使用StringBuilder,如果采用了多線程,則使用StringBuffer。
好累呀,因為虛心的小魯班除了整理了這些之外,同時自己繼續學習封裝類的比較,因為也被其他筆試題坑慘了呀!
1.兩個基本類型的只能用 ==
2.基本型和封裝型用==,封裝型將會自動拆箱變為基本型后再進行比較
3.用==來比較兩個封裝類的話,比較的是地址。(其中-127到127之間的Integer地址相同,超出這個范圍則不同)
4.至少有一個封裝型的建議使用.equals。用==對基本型和封裝性比較必須保證封裝型不為null。如果為null則不能轉化為基本型就會報錯。
5.兩個封裝型進行equals()比較,首先equals()會比較類型,如果類型相同,則繼續比較值,如果值也相同,返回true
6.封裝類型調用equals(),但是參數是基本類型,這時候,先會進行自動裝箱,基本型轉換為其封裝類型,若類型不同返回false,若裝箱后類型相同,則比較值,如果值相同,則返回true
int a=128; int a2=127; Integer b=128; Integer b2=127; Integer c=128; Integer c2=127; Integer d=new Integer(a); Integer d2=new Integer(a); Integer b2=57; Integer c2=57; System.out.println(a==b);//true System.out.println(b==c);//false System.out.println(b2==c2);//3true System.out.println(a==d);//true System.out.println(b==d);//false System.out.println(d==d2);//false //由強類型向弱類型轉換需要強制轉換,而由弱類型向強類型轉換則系統自動轉換。 //double 類型相比int類型是屬於強類型,則由double類型的數據向int類型數據轉換就需要強制轉換,反之則自動轉換。數據類型的強弱關系如下:
//byte<short=char<int<long<float<double,同級之間相互轉換也需要強制轉換。 //對於未聲明數據類型的整形,其默認類型為int型。 //在浮點類型(float/double)中,對於未聲明數據類型的浮點型,默認為double型。 System.out.println(b.equals(128.0));//false
-
new Integer(123) 每次都會新建一個對象;
-
Integer.valueOf(123) 會使用緩存池( -128~127)中的對象,多次調用會取得同一個對象的引用,如果在緩存池沒有,則NEW
寫完這些后小魯班,小魯班關了台燈,合上了電腦,休息一下准備明天的面試~
小魯班面試回來了,遇到比較坑的一道題
X a=.....;
X b=a
System.out.print(a==b); 要結果是false,X是一個給定類型
答案:
float a =0f/0;
float b=a;
System.out.println(a==b);
原理:NaN 不等於任何浮點數值,包括它自身在內;即它與任何數比較均返回false,但是可以用Float.compare()來比較NaN
延伸:infinity無窮大 float a=3.0f/0; float b=a+1;
無窮大加上一個數還是無窮大。 System.out.println(a==b);
