Java基礎知識(五)——Java字符串與數組

目錄

1、字符串創建與存儲的機制是什麼

引申:對於String類型的變量s,賦值語句s=null與s=“”是否相同?

2、“==”、equals和hashCode有什麼區別

1)“==”運算符用來比較兩個變量的值是否相等。

2)equals是Object類提供的方法之一。

3)hashCode()方法是從Object類中繼承過來的,它也用來鑑定兩個對象是否相等。

3、String、StringBuffer、StringBuilder 和StringTokenizer 有什麼區別

4、Java中數組是不是對象

5、數組的初始化方式有哪幾種

6、length 屬性與length()方法有什麼區別


1、字符串創建與存儲的機制是什麼

在Java語言中,字符串起着非常重要的作用,字符串的聲明與初始化主要有如下兩種情況:

1)對於String s1=new String("abc")語句與String s2=new String("abc")語句,存在兩個引用對象s1、s2,兩個內容相同的字符串對象”abc”,它們在內存中的地址是不同的。只要用到new總會生成新的對象

2)對於String s1="abc”語句與String s2="abc"語句,在JVM中存在着一個字符串池,其中保存着很多String對象,並且可以被共享使用,s1、s2引用的是同一個常量池中的對象。由於String的實現採用了Flyweight的設計模式,當創建一個字符串常量時,例如Strings="abc",會首先在字符串常量池中查找是否已經有相同的字符串被定義,其判斷依據是String類equals(Object obj)方法的返回值。若已經定義,則直接獲取對其的引用,此時不需要創建新的對象;若沒有定義,則首先創建這個對象,然後把它加入到字符串池中,再將它的引用返回。由於String是不可變類,一旦創建好了就不能被修改,因此String對象可以被共享而且不會導致程序的混亂。


爲了便於理解,可以把Strings=new String("abc")語句的執行人爲地分解成兩個過程:第一個過程是新建對象的過程,即new String("abc”)第二個過程是賦值的過程,即String s=。由於第二個過程只是定義了一個名爲s的String類型的變量,將一個String類型對象的引用賦值給s,因此在這個過程中不會創建新的對象。第一個過程中new String("abc")會調用String類的構造函數:

在調用這個構造函數時,傳入了一個字符串常量,因此語句new String(“abc")也就等價於“abc"和new String()兩個操作了。若在字符串池中不存在“abc”,則會創建一個字符串常量”abc”,並將其添加到字符串池中;若存在,則不創建,然後new String()會在堆中創建一個新的對象,所以s3與s4指向的是堆中不同的String對象,地址自然也不相同了,如圖4-5所示。

引申:對於String類型的變量s,賦值語句s=null與s=“”是否相同?

對於賦值語句s=null,其中s是一個字符串類型的引用,它不指向任何一個字符串。而賦值語句s=“”中的s是一個字符串類型的引用,它指向另外一個字符串(這個字符串的值爲“”,即空字符串),因此,這兩者是不同的。
 

常見筆試題:

new String(“abc”)創建了幾個對象?

答案:一個或兩個。如果常量池中原來有“abc”,那麼只創建一個對象;如果常量池中原來沒有字符串“abc”,那麼就會創建兩個對象。
 

2、“==”、equals和hashCode有什麼區別

1)“==”運算符用來比較兩個變量的是否相等。

也就是說,該運算符用於比較變量對應的內存中所存儲的數值是否相同,要比較兩個基本類型的數據或兩個引用變量是否相等,只能使用“==”運算符。

具體而言,如果兩個變量是基本數據類型,可以直接使用“==”運算符來比較其對應的值是否相等。如果一個變量指向的數據是對象(引用類型),那麼,此時涉及了兩塊內存,對象本身佔用一塊內存(堆內存),變量也佔用一塊內存,例如,對於賦值語句Strings=new String(),變量s佔用一塊存儲空間,而new String()則存儲在另外一塊存儲空間裏,此時,變量s所對應內存中存儲的數值就是對象佔用的那塊內存的首地址。對於指向對象類型的變量,如果要比較兩個變量是否指向同一個對象,即要看這兩個變量所對應內存中的數值是否相等(這兩個對象是否指向同一塊存儲空間),這時候就可以用“==”運算符進行比較。但是,如果要比較這兩個對象的內容是否相等,那麼用“==”運算符就無法實現了。

2)equals是Object類提供的方法之一。

每一個Java類都繼承自Object類,所以每一個對象都具有equals這個方法。Object類中定義的equals(Object)方法是直接使用“==”運算符比較的兩個對象,所以在沒有覆蓋equals(Object)方法的情況下,equals(Object)與“==”
運算符一樣,比較的是引用。

相比“==”運算符,equals(Object)方法的特殊之處就在於它可以被覆蓋,所以可以通過覆蓋的方法讓它不是比較引用而是比較數據內容,例如String類的equals方法是用於比較兩個獨立對象的內容是否相同,即堆中的內容是否相同,以下面的代碼爲例:

兩條new語句創建了兩個對象,然後用sl、s2這兩個變量分別指向一個對象,這是兩個不同的對象,它們的首地址是不同的,即a和b中存儲的數值是不相同的,所以,表達式a==b將返回false,而這兩個對象中的內容是相同的,所以,表達式a.equals(b)將返回true。如果一個類沒有自己定義equals()方法,那麼它將繼承Object類的equals()方法,Object類的equals()方法的實現代碼如下:

通過以上例子可以說明,如果一個類沒有自己定義equals()方法,它默認的equals()方法(從Object類繼承的)就是使用“==”運算符,也是在比較兩個變量指向的對象是否是同一對象,此時使用equals()方法和使用“==”運算符會得到同樣的結果。若比較的是兩個獨立的對象,則總返回false。如果編寫的類希望能夠比較該類創建的兩個實例對象的內容是否相同,那麼必須覆蓋equals()方法,由開發人員自己編寫代碼來決定在什麼情況下即可認爲兩個對象的內容是相同的

3)hashCode()方法是從Object類中繼承過來的,它也用來鑑定兩個對象是否相等。

Object類中的hashCode()方法返回對象在內存中地址轉換成的一個int值所以如果沒有重寫hashCode()方法,任何對象的hashCode()方法都是不相等的。

雖然equals()方法也是用來判斷兩個對象是否相等的,但是它與hashCode()方法是有區別的。一般來講,equals()方法是給用戶調用的,如果需要判斷兩個對象是否相等,可以重寫equals()方法,然後在代碼中調用,這樣就可以判斷它們是否相等了。對於hashCode()方法,用戶一般不會去調用它,例如在hashmap中,由於key是不可以重複的,它在判斷key是否重複時就判斷了hashCode()這個方法,而且也用到了equals()方法。此處“不可以重複”指的是equals()和hashCode()只要有一個不等就可以了。所以,hashCode()方法相當於是一個對象的編碼,就好像文件中的md5,它與equals()方法的不同之處就在於它返回的是int型,比較起來不直觀。

一般在覆蓋equals()方法的同時也要覆蓋hashCode()方法,否則,就會違反Object.hashCode的通用約定,從而導致該類無法與所有基於散列值(hash)的集合類(Hash-Map、HashSet和Hashtable)結合在一起正常運行。hashCode()方法的返回值和equals()方法的關係如下:如果xequals(y)返回true,即兩個對象根據equals方法比較是相等的,那麼調用這兩個對象中任意一個對象的hashCode()方法都必須產生同樣的整數結果。如果x.equals(y)返回false,即兩個對象根據equals()方法比較是不相等的,那麼x和y的hashCode()方法的返回值有可能相等,也有可能不相等。反之,hashCode()方法的返回值不相等,一定能推出equals()方法的返回值也不相等,而 hashCode()方法的返回值相等,equals方法的返回值則可能相等,也可能不相等。

3、String、StringBuffer、StringBuilder 和StringTokenizer 有什麼區別

Java語言有4個類可以對字符或字符串進行操作,它們分別是Character、String、StringBuffer 和StringTokenizer,其中Character用於單個字符操作,String用於字符串操作,屬於不可變類,而StringBuffer也是用於字符串操作,不同之處是StringBuffer屬於可變類

String是不可變類,也就是說,String對象一旦被創建,其值將不能被改變,而StringBuffer是可變類,當對象被創建後仍然可以對其值進行修改。由於String是不可變類,因此適合在需要被共享的場合中使用,而當一個字符串經常需要被修改時,最好使用StringBuffer來實現。

如果用String來保存一個經常被修改的字符串時,在字符串被修改時會比StringBuffer 多很多附加的操作,同時生成很多無用的對象,由於這些無用的對象會被垃圾回收器來回收,因此會影響程序的性能。在規模小的項目裏面這個影響很小,但是在一個規模大的項目裏面,這會對程序的運行效率帶來很大的影響。

String與StringBuffer的另外一個區別在於當實例化String時,可以利用構造函數(String sl=new String("world"))的方式來對其進行初始化,也可以用賦值(Strings="Hello")的方式來初始化,而StringBuffer 只能使用構造函數(StringBuffer s=new StringBuffer("Hello"))的方式來初始化。

String字符串修改實現的原理如下:當用String類型來對字符串進行修改時,其實現方法是首先創建一個StringBuffer,其次調用StringBuffer的append()方法,最後調用StringBuffer的toString()方法把結果返回,示例如下:


由此可以看出,上述過程比使用StringBuffer多了一些附加的操作,同時也生成了一些臨時的對象,從而導致程序的執行效率降低。爲了更好地說明這一問題,下面分析一個示例:

從程序的運行結果可以看出,當一個字符串需要經常被修改時,使用StringBufer比使用String要好很多。StringBuilder也可以被修改的字符串,它與StringBuffer類似,都是字符串緩衝區,但StringBuilder不是線程安全的,如果只是在單線程中使用字符串緩衝區,那麼StringBuilder的效率會更高些。因此在只有單線程訪問時可以使用StringBuilder,當有多個線程訪問時,最好使用線程安全的StringBuffer。因爲StringBuffer必要時可以對這些方法進行同步,所以任意特定實例上的所有操作就好像是以串行順序發生的,該順序與所涉及的每個線程進行的方法調用順序一致。

在執行效率方面,StringBuilder最高,StringBuffer次之,String最低,鑑於這一情況,一般而言,如果要操作的數據量比較小,應優先使用String類;如果是在單線程下操作大量數據,應優先使用StringBuilder類;如果是在多線程下操作大量數據,應優先考慮StringBuffer類。

StringTokenizer是用來分割字符串的工具類,示例如下:

4、Java中數組是不是對象

數組是指具有相同類型的數據的集合,它們一般具有固定的長度,並且在內存中佔據連續的空間。在C/C++語言中,數組名只是一個指針,這個指針指向了數組的首元素,既沒有屬性也沒有方法可以調用,而在Java語言中,數組不僅有其自己的屬性(例如length屬性),也有一些方法可以被調用(例如clone方法)。由於對象的特點是封裝了一些數據,同時提供了一些屬性和方法,從這個角度來講,數組是對象。每個數組類型都有其對應的類型,可以通過instanceof來判斷數據的類型,示例如下:

 

5、數組的初始化方式有哪幾種

在Java語言中,一維數組的聲明方式爲type arrayName[]或type[]arrayName 其中,type既可以是基本的數據類型,也可以是類,arrayName表示數組的名字,[]用來表示這個變量的類型爲一維數組。與C/C++語言不同的是,在Java語言中,數組被創建後會根據數組存放的數據類型初始化成對應的初始值(例如,int類型會初始化爲0,對象會初始化爲null)。另外一個不同之處是Java數組在定義時,並不會給數組元素分配存儲空間,因此[]中不需要指定數組的長度,對於使用上面方式定義的數組在使用時還必須爲之分配空間,分配方法爲:

以上主要介紹了一維數組的聲明與初始化的方式,下面介紹二維數組的聲明與初始化的方式,二維數組有3種聲明的方法:

需要注意的是,在聲明二維數組時,其中[]必須爲空。二維數組也可以用初始化列表的方式來進行初始化,其一般形式爲

除了以上介紹的方法以外,也可以通過new關鍵字來給數組申請存儲空間,形式如下:

與C/C++語言不同的是,在Java語言中,二維數組的第二維的長度可以不同。假如要定義一個有兩行的二維數組,第一行有兩列,第二行有三列,定義方法如下:

對二維數組的訪問也是通過下標來完成,一般形式爲arryName[行號][列號],下例介紹二維數組的遍歷方法:

 

6、length 屬性與length()方法有什麼區別

在C/C++語言中,每當調用一個方法需要傳遞數組時,就必須同時傳遞數組的長度,因爲在方法調用時傳遞的參數爲數組的首地址,而對數組的實際長度卻無法獲知,這樣會導致在對數組進行訪問時可能產生越界。而在Java語言中,數組提供了length屬性來獲取數組的長度

在Java語言中,length()方法是針對字符串而言的,String提供了length()方法來計算字符串的長度,示例如下例:

除了length屬性與length()方法外,Java中還有一個計算對象大小的方法—size()方法,該方法是針對泛型集合而言的,用於查看泛型中有多少個元素。(備註:泛型是對Java語言的類型系統的一種擴展,以支持創建可以按類型進行參數化的類,可以把類型參數看作是使用參數化類型時指定的類型的一個佔位符,就像方法的形式參數是運行時傳遞的值的佔位符一樣)
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章