1.泛型和類型安全的容器
@SuppressWarnings註解及其參數表示只有有關「不受檢查的異常」的警告信息應該被抑制。
例子:
class Apple{
private static long counter;
private final long id = counter++;
public long id(){return id;}
}
class Orange{}
public class ApplesAndOrangesWithoutGenerics{
@SuppressWarnings("unchecked")
public static void main(String[] args){
ArrayList apples = new ArrayList();
for(int i=0; i<3; i++){
apples.add(new Apple());
}
apples.add(new Orange());
for(int i=0;i<apples.size();i++){
((Apple)apples.get(i)).id();
//orange is detected only at run time
}
}
}/*(Execute to see output)*///:~
由於ArrayList保存的是Object,所以你不只能夠經過ArrayList的add()方法將Apple對象放進這個容器,還能夠添加Orange對象,並且不管在編譯期仍是運行時都不會有問題。當你使用ArrayList的get()方法取出你認爲是Apple的對象時,你獲得的只是Object引用,必須將其轉型爲Apple,所以,須要將整個表達式括起來,在調用Apple的id()方法以前,強制執行轉型。不然,你就會獲得語法錯誤。在運行時,當你試圖將Orange對象轉型爲Apple時,你就會之前面說起的異常的形式獲得一個錯誤。
要想定義用來保存Apple對象的ArrayList,你能夠聲明ArrayList<Apple>,而不只僅只是ArrayList,其中尖括號括起來的是類型參數(能夠有多個),它指定了這個容器實例能夠保存的類型。經過使用泛型,就能夠在編譯期防止將錯誤類型的對象放置到容器中。如上面的例子,編譯期能夠阻止你將Orange放置到apples中,所以它變成一個編譯期錯誤,而再也不是運行時錯誤。你還應該注意到,在將元素從List中取出時,類型轉換也再也不是必需的了。由於List知道它保存的是什麼類型,所以它會在調用get()時替你執行轉型。這樣,經過使用泛型,你不只知道編譯器將會檢查你放置到容器中的對象類型,並且在使用容器中的對象時,可使用更加清晰的語法。
當你指定了某個類型做爲泛型參數時,你並不只限於只能將該確切類型的對象放置到容器中。向上轉型也能夠象做用於其餘類型同樣做用於泛型。如上例,你能夠將Apple的子類型添加到被指定爲保存Apple對象的容器中。
當打印對象時,若是是默認從Object繼承的toString()方法時,將打印出類名,後面跟隨該對象的散列碼的無符號十六進制表示(這個散列碼是經過hashCode()方法產生的)。
2.持有對象基本概念
java容器類類庫的用途是「保存對象」,並將其劃分爲兩個不一樣的概念:
1)Collection。一個獨立元素的序列,這些元素都服從一條或多條規則。List必須按照插入的順序保存元素,而Set不能有重複元素。Quence按照排隊規則來肯定對象產生的順序(一般與它們被插入的順序相同)。
2)Map。一組成對的「鍵值對」對象,容許你使用鍵來查找值。ArrayList容許你使用數字來查找值,所以在某種意義上講,它將數字與對象關聯在一塊兒。映射表容許咱們使用另外一個對象來查找某個對象,它也被稱爲「關聯數組」,由於它將某些對象與另一些對象關聯在了一塊兒;或者被稱爲「字典」,所以你可使用鍵對象來查找值對象,就像在字典中使用單詞來定義同樣。Map是強大的編程工具。
Collection接口歸納了序列的概念-一種存放一組對象的方式。ArrayList是最基本的序列類型。
3.添加一組元素
在java.util包中的Arrays和Collections類中都有不少實用的方法,能夠在一個Collection中添加一組元素。Arrays.asList()方法接受一個數組或是一個用逗號分隔的元素列表(使用可變參數),並將其轉換爲一個List對象。Collections.addAll()方法接受一個Collection對象,以及一個數組或是一個用逗號分隔的列表,將元素添加到Collection中。
Collection.addAll()成員方法只能接受另外一個Collection對象做爲參數,所以它不如Arrays.asList()或Collections.addAll()靈活,這兩個方法使用的都是可變參數列表。
你也能夠直接使用Arrays.asList()的輸出,將其看成List,可是在這種狀況下,其底層表示的是數組,所以不能調整尺寸。若是你試圖用add()或delete()方法在這種列表中添加或刪除元素,就有可能會引起去改變數組尺寸的嘗試,所以你將在運行時得到「Unsupported Operation(不支持的操做)」錯誤。
Arrays.asList()方法的限制是它對所產生的List的類型作出了最理想的假設,而並無注意你對它會賦予什麼樣的類型。
class Snow{}
class Powder extends Snow{}
class Light extends Powder{}
class Heavy extends Powder{}
class Crusty extends Snow{}
class Slush extends Snow{}
public class AsListInference{
public static void main(String[] args){
List<Snow> snow1 = Arrays.asList(new Crusty(),new Slush(),new Powder());
//Won't compile:
//List<Snow> snow2 = Arrays.asList(new Light(),new Heavy());
//Compiler says:
//fond:java.util.List<Powder> required:java.util.List<Snow>
//Collections.addAll() doesn't get confused;
List<Snow> snow3 = new ArrayList<Snow>();
Collections.addAll(snow3,new Light(),new Heavy());
//Give a hint using an explicit type argument specification:
List<Snow> snow4 = Arrays.<Snow>asList(new Light(),new Heavy());
}
}
當試圖建立snow2時,Arrays.asList()中只有Powder類型,所以它會建立List<Powder>而不是List<Snow>,儘管Collections.addAll()工做的很好,由於它從第一個參數中瞭解到了目標類型是什麼。
正如你從建立snow4的操做中所看到的,能夠在Arrays.asList()中間插入一條「線索」,以告訴編譯器對於有Arrays.asList()產生的List類型,實際的目標類型應該是什麼。這稱爲顯式類型參數說明。
正如你所見,Map更加複雜,而且除了用另外一個Map以外,java標準庫沒有提供其餘任何自動初始化它們的方式。
4.容器的打印
你必須使用Arrays.toString()來產生數組的可打印表示,可是打印容器無需任何幫助。
java容器類庫中的兩種主要類型,它們的區別在於容器中每一個「槽」保存的元素個數。Collection在每一個槽中只能保存一個元素。此類容器包括:List,它以特定的順序保存一組元素;Set,元素不能重複;Quence,只容許在容器的一「端」插入對象,並從另外一「端」移除對象。Map在每一個槽內保存了兩個對象,即鍵和與之相關聯的值。
ArrayList和LinkedList都是List類型,從輸出能夠看出,它們都按照被插入的順序保存元素。二者的不一樣之處不只在於執行某些類型的操做時的性能,並且LinkedList包含的操做也多於ArrayList。
HashSet、TreeSet和LinkedHashSet都是Set類型,輸出顯示在Set中,每一個相同的項只有保存一次,可是輸出也顯示了不一樣的Set實現存儲元素的方式也不一樣。HashSet使用的是至關複雜的方式來存儲元素,這種方式將在第17章中介紹,此刻你只須要知道這種技術是最快的獲取元素方式,所以,存儲的順序看起來並沒有實際意義。而TreeSet,它按照比較結果的升序保存對象;LinkedHashSet,它按照被添加的順序保存對象。
note:你沒必要指定(或考慮)Map的尺寸,由於它本身會自動地調整尺寸。鍵和值在Map中的保存順序並非它們的插入順序,由於HashMap實現使用的是一種很是快的算法來控制順序。
HashMap提供了最快的查找技術,也沒有按照任何明顯的順序來保存其元素。TreeMap按照比較結果的升序保存鍵。
LinkedHashMap則按照插入順序保存鍵,同時還保留了HashMap的查找速度。
5.List
List接口在Collection的基礎上添加了大量的方法,使得能夠在List的中間插入和移除元素。
有兩種類型的List:
1)基本的ArrayList,它擅長於隨機訪問元素,可是在List的中間插入和移除元素時較慢。
2)LinkedList,它經過代價較低的在List中間進行的插入和刪除操做,提供了優化的順序訪問。LinkedList在隨機訪問方面相對比較慢,可是它的特性集較ArrayList更大。
List容許在它被建立以後添加元素、移除元素,或者自我調整尺寸。這正是它的重要價值所在:一種可修改的序列。
當肯定一個元素是否屬於某個List,發現某個元素的索引,以及從某個List中移除一個元素時,都會用到equals()方法(它是根類Object的一部分)。詳見11.5例子,每一個Pet都被定義爲惟一的對象,所以即便在列表中已經有兩個Cymric,若是我再新建立一個Cymric,並把它傳遞給indexOf()方法,其結果仍會是-1(表示沒找到它),並且嘗試調用remove()來刪除這個對象,也會返回false。對於其餘的類,equals()的定義可能有所不一樣。例如,兩個String只有在內容徹底同樣的狀況下才會是等價的。所以爲了防止意外,就必須意識到List的行爲根據equals()的行爲而有所變化。
對於LinkedList,在列表中間插入和刪除都是廉價操做,可是對於ArrayList,這但是代價高昂的操做。這是否意味着你應該永遠不要在ArrayList的中間插入元素,並最好是切換到LinkedList?不,這僅僅意味着,你應該意識到這個問題,若是你開始在某個ArrayList的中間執行不少插入操做,而且你的程序開始變慢,那麼你應該看看你的List實現有多是罪魁禍首(發現此類瓶頸的最佳方式是使用仿真器)。優化是一個很棘手的問題,最好的策略就是置之不顧,直到你發現須要擔憂它了(儘管理解這些問題老是一種好的思路)。
remove(),若是參數是對象引用的話,它所產生的行爲依賴於equals()方法,但若是參數是索引值時,就沒必要擔憂equals()的行爲。
6.迭代器
任何容器類,都必須有某種方式能夠插入元素並將它們再次取回。畢竟,持有事物是容器最基本的工做。
迭代器(也是一種設計模式)是一個對象,它的工做是遍歷並選擇序列中的對象,而客戶端程序員沒必要知道或關心該序列底層的結構。此外,迭代器一般被稱爲輕量級對象:建立它的代價小。所以,常常能夠見到對迭代器有些奇怪的限制;例如,java的Iterator只能單向移動,這個Iterator只能用來:
1)使用方法iterator()要求容器返回一個Iterator。Iterator將準備好返回序列的第一個元素。
2)使用next()得到序列中的下一個元素。
3)使用hasNext()檢查序列中是否還有元素。
4)使用remove()將迭代器新近返回的元素刪除。
若是你只是向前遍歷List,並不打算修改List對象自己,那麼你能夠看到foreach語法會顯得更加簡潔。
Iterator還能夠移除由next()產生的最後一個元素,這意味着在調用remove()以前必須先調用next()(remove()是所謂的「可選」方法(還有一些其它的這種方法),即不是全部的Iterator實現都必須實現該方法)。
Iterator的真正威力:可以將遍歷序列的操做與序列底層的結構分離。正因爲此,咱們有時會說:迭代器統一了對容器的訪問方式。詳見11.6第二個例子的display()方法。
7.ListIterator
ListIterator是一個更增強大的Iterator的子類型,它只能用於各類List類的訪問。儘管Iterator只能向前移動,可是ListIterator能夠雙向移動。它還能夠產生相對於迭代器在列表中指向的當前位置的前一個和後一個元素的索引,而且可使用set()方法替換它訪問過的最後一個元素。你能夠經過調用listIterator()方法產生一個指向List開始處的ListIterator,而且還能夠經過調用listIterator(n)方法建立一個一開始就指向列表索引爲n的元素處地ListIterator。
8.LinkedList
LinkedList也像ArrayList同樣實現了基本的List接口,可是它執行某些操做(在List的中間插入和移除)時比ArrayList更高效,但在隨機訪問操做方面卻要遜色一些。
LinkedList還添加了可使其用做棧、隊列或雙端隊列的方法。
Queue接口,它在LinkedList的基礎上添加了element()、offer()、peek()、poll()和remove()方法,以使其能夠成爲一個Queue的實現。這一點有1.5jdk api有出入,在api中,講的是Queue extends Collection,而LinkedList implements Queue。
9.Stack
「棧」一般是指「後進先出」(LIFO)的容器。有時棧也被稱爲疊加棧,由於最後「壓入」棧的元素,第一個「彈出」棧。常常用來類比棧的事物是裝有彈簧的儲放器中的自動餐托盤,最後裝入的托盤老是最早拿出使用的。
LinkedList具備可以直接實現棧的全部功能的方法,所以能夠直接將LinkedList做爲棧使用。可是能夠直接用Stack類更直接。java.util.Stack實現了List接口,若是你只須要棧的行爲,就不須要使用繼承了,這樣會產生List中的其餘方法,能夠採用11.8中的棧例子。
10.Set
Set不保存重複的元素。若是你試圖將相同對象的多個實例添加到Set中,那麼它就會阻止這種重複現象。Set中最常被使用的是測試歸屬性,你能夠很容易地詢問某個對象是否在某個Set中。正由於如此,查找就成爲了Set中最重要的操做,所以你一般都會選擇一個HashSet的實現,它專門對快速查找進行了優化。
Set具備與Collection徹底同樣的接口,所以沒有任何額外的功能,不像前面有兩個不一樣的List。實際上Set就是Collection,只是行爲不一樣。(這是繼承與多態思想的典型應用:表現不一樣的行爲。)Set是基於對象的值來肯定歸屬性的,而更加複雜的問題將在第17章介紹。
HashSet所維護的順序與TreeSet或LinkedHashSet都不一樣,由於它們的實現具備不一樣的元素存儲方式。TreeSet將元素存儲在紅-黑樹數據結構中,而HashSet使用的是散列函數。LinkedHashSet由於查詢速度的緣由也使用了散列,可是看起來它使用了鏈表來維護元素的插入順序。練習16稍後完成。
11.Map
將對象映射到其餘對象的能力是一種解決編程問題的殺手鐗。例如,能夠用來檢查java的Random類的隨機性,鍵是由Random產生的數字,而值是該數字出現的次數。詳見11.10例子。
Map與數組和其餘的Collection同樣,能夠很容易地擴展到多維,而咱們只需將其值設置爲Map(這些Map的值能夠是其餘容器,甚至是其餘Map)。所以,咱們可以很容易地將容器組合起來從而快速地生成強大的數據結構。例如,假設你正在跟蹤擁有多個寵物的人,你所需只是一個Map<Person,List<Pet>>。
12.Queue
隊列是一個典型的先進先出(FIFO)的容器。即從容器的一端放入事物,從另外一端取出,而且事物放入容器的順序與取出的順序是相同的。隊列常被看成一種可靠的將對象從程序的某個區域傳輸到另外一個區域的途徑。隊列在併發編程中特別重要,就像你將在第21章中所看到的,由於它們能夠安全的將對象從一個任務傳輸給另外一個任務。
LinkedList提供了方法以支持隊列的行爲,而且它實現了Queue接口,所以LinkedList能夠用做Queue的一種實現。經過將LinkedList向上轉型爲Queue。詳見11.11例子
offer()方法是與Queue相關的方法之一,它在容許的狀況下,將一個元素插入到隊尾,或者返回false。peek()和element()都將在不移除的狀況下返回隊頭,可是peek()方法在隊列爲空時返回null,而element()會拋出NoSuchElementException異常。poll()和remove()方法將移除並返回隊頭,可是poll()在隊列爲空時返回null,而remove()會拋出NoSuchElementException異常。
13.PriorityQueue
先進先出描述了最典型的隊列規則。隊列規則是指在給定一組隊列中的元素的狀況下,肯定下一個彈出隊列的元素的規則。先進先出聲明的是下一個元素應該是等待時間最長的元素。
優先級隊列聲明下一個彈出元素是最須要的元素(具備最高的優先級)。例如,在飛機場,當飛機臨近起飛時,這架飛機的乘客能夠在辦理登機手續時排到隊頭。若是構建了一個消息系統,某些消息比其餘消息更重要,所以應該更快地獲得處理,那麼它們什麼時候獲得處理就與它們什麼時候到達無關。PriorityQueue添加到java se5中,是爲了提供這種行爲的一種自動實現。
當你在PriorityQueue上調用offer()方法插入一個對象時,這個對象會在隊列中被排序(這實際上依賴於具體實現,優先級隊列算法一般會在插入時排序-維護一個堆,可是它們也可能在移除時選擇最重要的元素。若是對象的優先級在它所在隊列中等待時能夠進行修改,那麼算法的選擇就顯得很重要了)。默認的排序將使用對象在隊列中的天然順序,可是你能夠經過提供本身的Comparator來修改這個順序。PriorityQueue能夠確保當你調用peek()、poll()和remove()方法時,獲取的元素將是隊列中優先級最高的元素。
詳見11.11.1例子你能夠看到,重複是容許的,最小的值擁有最高的優先級(若是是String,空格也能夠算做值,而且比字母的優先級高)。PriorityQueue與Integer、String和Character一塊兒工做,所以這些類已經內建了天然排序。若是你想在PriorityQueue中使用本身的類,就必須包括額外的功能以產生天然排序,或者必須提供本身的Comparator。
14.Collection和Iterator
Collection是描述全部序列容器的共性的根接口,它可能會被認爲是一個「附屬接口」,即由於要表示其餘若干個接口的共性而出現的接口。另外,java.util.AbstractCollection類提供了Collection的默認實現,使得你能夠建立AbstractCollection的子類型,而其中沒有沒必要要的代碼重複。
使用接口描述的一個理由是它可使咱們可以建立更通用的代碼。經過針對接口而非具體實現來編寫代碼,咱們的代碼能夠應用於更多的對象類型。所以,若是我編寫的方法將接受一個Collection,那麼該方法就能夠應用於任何實現了Collection的類-這也就使得一個新類能夠選擇去實現Collection接口,以便個人方法可使用它。可是,有一點頗有趣,就是咱們注意到標準C++類庫中並無其容器的任何公共基類-容器之間的全部共性都是經過迭代器達成的。在java中,遵循C++的方式看起來彷佛很明智,即用迭代器而不是Collection來表示容器的共性。可是,這兩種方法綁定到了一塊兒,由於實現Collection就意味着須要提供iterator()方法。
15.Foreach與迭代器
可以與foreach一塊兒工做是全部Collection對象的特性。之因此可以工做,是由於java se5引入了新的被稱爲Iterable的接口,該接口包含一個可以產生Iterator的iterator()方法,而且Iterable接口被foreach用來在序列中移動。所以若是你建立了任何實現Iterable的類,均可以將它用於foreach語句中。
在java se5中,大量的類都是Iterable類型,主要包括全部的Collection類(可是不包括各類Map)。例如:
public class EnvironmentVariables{
public static void main(String[] args){
for(Map.Entry entry:System.getenv().entrySet()){
System.out.println(entry.getKey()+": "+entry.getValue());
}
}
}
System.getenv()(在java se5以前尚未它,由於該方法被認爲與操做系統的耦合度過緊,所以會違反「編寫一次,處處運行」的原則。如今提供它代表java的設計者們更加務實了)返回一個Map,entrySet()產生一個由Map.Entry的元素構成的Set,而且這個Set是一個Iterable,所以它能夠用於foreach循環。
foreach語句能夠用於數組或其餘任何Iterable,可是這並不意味着數組確定也是一個Iterable,而任何自動包裝也不會自動發生。詳見11.13例子,嘗試把數組看成一個Iterable參數傳遞會致使失敗。這說明不存在任何從數組到Iterable的自動轉換,你必須手工執行這種轉換。
Arrays.asList()產生的List對象會使用底層數組做爲其物理實現是很重要的。只要你執行的操做會修改這個List,而且你不想原來的數組被修改,那麼你就應該在另外一個容器中建立一個副本。代碼:
Integer[] ia = {1,2,3,4,5};
List<Integer> list1 = new ArrayList<Integer>(Arrays.asList(ia));
List<Integer> list2 = Arrays.asList(ia);
針對list1的順序進行操做,不會打亂ia的順序,打亂的只是list1中引用ia元素的元素,而針對list2的順序操做,會打亂ia的順序,由於list2是直接操做數組。
16.持有對象總結
java提供了大量持有對象的方式:
1)數組將數字與對象聯繫起來。它保存類型明確的對象,查詢對象時,不須要對結果作類型轉換。它能夠是多維的,能夠保存基本類型的數據。可是,數組一旦生成,其容量就不能改變。
2)Collection保存單一的元素,而Map保存相關聯的鍵值對。有了java的泛型,你就能夠指定容器中存放的對象類型,所以你就不會將錯誤類型的對象放置到容器中,而且在從容器中獲取元素時,沒必要進行類型轉換。各類Collection和各類Map均可以在你向其中添加更多的元素時,自動調整其尺寸。容器不能持有基本類型,可是自動包裝機制會仔細地執行基本類型到容器中所持有的包裝器類型之間的雙向轉換。
3)像數組同樣,List也創建數字索引與對象的關聯,所以,數組和List都是排序好的容器。List可以自動擴充容量。
4)若是要進行大量的隨機訪問,就是用ArrayList;若是要常常從表中間插入或刪除元素,則應該使用LinkedList。
5)各類Queue以及棧的行爲,有LinkedList提供支持。
6)Map是一種將對象(而非數字)與對象相關聯的設計。HashMap設計用來快速訪問;而TreeMap保持「鍵」始終處於排序狀態,因此沒有HashMap快。LinkedHashMap保持元素插入的順序,可是也經過散列提供了快速訪問能力。
7)Set不接受重複元素。HashSet提供最快的查詢速度,而TreeSet保持元素處於排序狀態。LinkedHashSet以插入順序保存元素。
8)新程序中不該該使用過期的Vector、Hashtable和Stack。java
-------------------------------------------------------------------end--------------------------------------------------------------------------------------------------------------------------------------------------------------
thinking:~?
1)當重寫對象的equals的時候,爲何要重寫hashCode?
直接從程序中看,是不必的,由於重寫equals方法的時候,根本沒用到hashCode,可是equals方法被重寫時,一般有必要重寫 hashCode 方法,以維護 hashCode 方法的常規協定,該協定聲明相等對象必須具備相等的哈希碼。
2)哈希碼值的協定?
hashCode 的常規協定是:
. 在 Java 應用程序執行期間,在同一對象上屢次調用 hashCode 方法時,必須一致地返回相同的整數,前提是對象上 equals 比較中所用的信息沒有被修改。從某一應用程序的 一次執行到同一應用程序的另外一次執行,該整數無需保持一致。
. 若是根據 equals(Object) 方法,兩個對象是相等的,那麼在兩個對象中的每一個對象上調用 hashCode 方法都必須生成相同的整數結果。
. 如下狀況不 是必需的:若是根據 equals(java.lang.Object) 方法,兩個對象不相等,那麼在兩個對象中的任一對象上調用 hashCode 方法一定會生成不一樣的整數結果。可是 ,程序員應該知道,爲不相等的對象生成不一樣整數結果能夠提升哈希表的性能。
實際上,由 Object 類定義的 hashCode 方法確實會針對不一樣的對象返回不一樣的整數。(這通常是經過將該對象的內部地址轉換成一個整數來實現的,可是 JavaTM 編程語言不須要這種實現技巧。)
3)當定義了一個對象,假設:
class A{
private String id;
private String name;
public A(){
}
public A(String name,String id){
this.name = name;
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
這時定義兩個對象:A a1 = new A("cjh","1");A a2 = new A("cjh","1");
此時打印System.out.println(a1+" , "+a2);
這時打印結果會帶上hashCode的十六進制,這時若是A沒有重寫hashCode方法的話,hashCode根據對象的內部地址碼轉換成一個整數,若是重寫了hashCode方法,會根據重寫後的hashCode方法生成新的hashCode。
4)針對equals方法和==的思考。
首先是==操做符。
針對對象而言,是兩個對象引用指向的對象的內部地址是否一致,若是一致,就是true,不然就是false,假設,有一個類A,用A定義了一個對象:A a = new A();A a1=a;其中的a和a1就是對象引用,a引用指向的地址是A對象的內部地址,此時a賦值給a1,a1也指向了新對象A的內部地址,因此兩個對象引用,是相等的。若是是這種狀況:A a = n ew A();A b = new A();實質是建立了兩個A對象,開闢了兩塊內存,具備兩個地址(假設d1,d2),a引用指向d1,b引用指向d2,因此a==b的執行結果是false。
針對基本數據類型而言,比較的就是變量表明的值是否相等,假設 int i = 1,j=1;此時i==j的執行結果是true,針對基本數據類型沒有引用的概念,假設int i=1;int j=i; 此時只是將i的值copy一份給j,因此j的值改變了,並不會影響i的值。
再解:實質上==,比較的是變量(包括引用變量)中所存儲的值是否一致,一致爲true,不然爲false;對於基本數據類型好理解,由於基本數據類型的賦值操做是copy一份 值賦給另外一個變量;針對對象而言,若是一個變量指向的數據是對象類型的,那麼,這時候涉及了兩塊內存,對象自己佔用一塊內存(堆內存),變量也佔用一塊內存,例如Objet obj = new Object();變量obj是一個內存,new Object()是另外一個內存,此時,變量obj所對應的內存中存儲的數值就是對象佔用的那塊內存的首地址。對於指向對象類型的變量,若是要比較兩個變量是否指向同一個對象,即要看這兩個變量所對應的內存中的數值是否相等,這時候就須要用==操做符進行比較。
其次是equals方法。
equals方法主要仍是從代碼來理解,首先來看Object類,這是全部對象的根類,如下是Object類中的equals方法的源碼:
public boolean equals(Object obj)
{
return this == obj;
}
從中能夠看見Object類中equals方法的本質是==,因此要比較對象的內容的時候,常常須要重寫equals方法(若是不想重寫equals方法的話,能夠引用apache的jar包-commons-lang-2.4.jar,該包的中的EqualsBulider類可供使用,例如:EqualsBuilder.reflectionEquals(a1, a2))。再者根據hashCode的常規協定,就必須再重寫hashCode方法。
對String類而言,因爲String中針對了equals方法已經進行了重寫,因此默認比較的就是String的內容,還有一些基本數據類型的包裝類Integer等類,也是對equals方法重 寫了,默認比較的就是其中的值。但針對這幾種類型,要注意equals和==的區別,假設以下代碼:
Integer i = new Integer(1);Integer j = new Integer(1);
i==j的執行結果是false,i.equals(j)的執行結果是true;
改變下以上代碼:Integer i = 1;Integer j = 1;
i==j的執行結果是true,i.equals(j)的執行結果是true;
爲何呢?(若是是String類型也是同樣的)
狀況一:經過new建立了2個對象,這時==操做符,確定結果就是false,而equals比較的是內容,都是1,就爲true;
狀況二:1是一個常量,在整個緩衝區中只有一份,因此,i和j實際指向的都是同一個1,因此==操做的結果就是true。程序員