對Java程序開發而言,ArrayList 的使用頻率是非常高的,尤其在進行 JavaWeb 開發的時候,ArrayList 和 HashMap 這兩個類,相信你一定不會陌生,因為天天都在用嘛。
本系列對 ArrayList 做一個解析,同時把Java基礎知識個串連進去。一開始我會對如何使用 ArrayList 做一個簡要的說明,然后,我們來仿照 ArrayList 封裝一個自己的集合框架MyList,通過練習,來一步一步猜想ArrayList 可能的實現方式。
最后,深入到ArrayList 的源碼進行解讀。
為什么要學習源碼?
很簡單,一個知道源碼的人和一個不知道源碼的人,雖然都能使用 ArrayList ,但是,他們在使用的時候,心態是完全不一樣的。
只有當你深入了源碼,然后你才會對它的一些細節有更充分的認識。這是一本萬利的事情。
當然,對于初學者,還是盡量以使用為主,因為源碼的話,畢竟有一定的難度。如果一味地追求這些東西,可能會大大降低自己的學習興趣和熱情。
1、 ArrayList 概述
副本難度:一顆星
經驗值:500
首先來看一下文檔,
<h5>
All Implemented Interfaces:
Serializable, Cloneable, Iterable<E>, Collection<E>, List<E>, RandomAccess
</h5>
<p>
從圖中可以看到,ArrayList 實現了Iterable接口,這個接口表示一種迭代的能力。
</p>
既然是ArrayList,那么肯定和 List 有關,所以它果然繼承了List接口。
接口的知識點在這里就用上了。
其他接口我們暫且不談,先繼續。
然后,我們看一下官方對ArrayList 做出的說明。
文檔中有這么一句:
Resizable-array implementation of the List interface.
這句話是說,ArrayList 是對List接口的一個實現,實現方式是利用一個可改變尺寸的數組,也就是說,它的底層就是一個數組。而且是可改變尺寸的數組,說明這個數組是動態的。
哦,難怪它叫ArrayList ,Array就是數組的意思,那么它肯定和數組有關。
**Implements all optional list operations, and permits all elements, including null. **
ArrayList 實現了list接口的所有方法,并且允許空元素。
前半句是肯定的,因為在Java中,如果一個類實現了一個接口,那么就必須要重寫該接口里所有的抽象方法。
我們知道,接口里只有方法的聲明,沒有方法的實現。
我對接口的理解,總結以后就只有一句話:
Java類實現接口,就是給這個類本身添加了一個新的身份。
ArrayList是一個類,那么它的身份就是ArrayList,你問他“你叫什么名字呀?”,他肯定會毫不猶豫地告訴你,“我叫ArrayList!”。
白天,我們大家都以為它是ArrayList,結果到了夜晚。
ArrayList竟然搖身一變,成為了Iterable!
原來,ArrayList實現了Iterable,所以它也就擁有了Iterable的身份。
接口其實就是這個意思。網上對接口的解釋眾說紛紜,反正我總結下來就是這么一句話,也不想搞那么復雜了。
ArrayList不僅有Iterable的身份,還擁有其他好幾種身份,比如List,Collection等。
這個情況也叫作多態。
新建一個測試類。
public class TestArrayList1 {
public static void main(String[] args) {
}
}
現在我新建一個ArrayList類的實例
ArrayList list = new ArrayList();
這肯定是沒問題的。
文檔上說,ArrayList 實現了Iterable接口,那么也就是說,它的另一個身份是Iterable。
所以,我這樣寫是不是也沒有問題啊。
Iterable iterable = new ArrayList();
就好像白天是普通的上班族,一旦到了夜晚,就。。。
總之,
Iterable iterable = new ArrayList();
這句話的含義就是說,它原來是普通的ArrayList,一旦情況需要,就變身成為Iterable。
好了,既然變成了 Iterable ,那么它是不是也就擁有了 Iterable 的能力?
我們來看一下 Iterable 有哪些方法?
去除繼承自Object類的方法不談,Iterable 只有一個方法,就是 iterator()
它返回的是一個 Iterator ,這是一個迭代器。
通過這個迭代器,我們是不是就可以遍歷 ArrayList 中所有的數據了呀?
首先,我們需要一個迭代對象:
Iterable iterable = new ArrayList();
Iterator it = iterable.iterator();
當然,這個例子中,ArrayList里面沒有數據。
現在思考一個問題,是不是我非得把 ArrayList 對象改變成 Iterable 身份,才可以調用 iterator() 方法呢?
當然不是了,ArrayList 有一個身份是 Iterable ,所以它具有 Iterable 的所有能力(方法),這沒問題。那么難道 ArrayList 不變身,就沒有 Iterable 的能力了嗎?
答案自然是否定的。
一個小說家如果有一天轉行去寫代碼了,那么你覺得他還會不會寫小說呢?
肯定會嘛,這沒有什么好懷疑的。
所以,代碼這么寫是不是也沒關系?
ArrayList list = new ArrayList();
for (int i = 0; i < 100; i++) {
list.add(i);
}
Iterator it = list.iterator();
循環輸出
while(it.hasNext()){
System.out.println(it.next());
}
總結一下,ArrayList 它只要實現 Iterable 接口,那么它就必須要擁有 Iterable 規定的所有能力,也就是方法。而接口中,我們知道它里面只有方法的聲明,沒有方法的實現,所以 ArrayList 需要實現這些方法,實現接口就是這么個意思。
2、 ArrayList常用方法
副本難度:三顆星
經驗值:800
2.1、 屬性和方法的調用問題
在調用ArrayList的方法之前,我們需要先獲得一下ArrayList的實例對象,除了靜態方法,其他所有的方法,都只能由對象來調用。
ArrayList是一個類,我更愿意把它稱為一個** 數據模板**。它只是一個模板而已,不是一個實實在在的對象,這一點首先要確定。
就好像工廠生產一個產品,首先是不是要有一個模板和設計圖紙,這個模板決定了你這個產品是一種怎樣的形狀,以及可能會具備哪些功能?圖紙則決定了功能的具體實現。
比如生成一部手機,模板開出來就是一個扁平的長方體的樣子,可是光有模板還不行,你還得規定它的一些具體細節。
這些細節就好比是Java類的構造方法,以及其他的一些方法實現。
但是,你光給客戶模板和圖紙行嗎?
一般來說是不行的。
至于靜態方法,我們知道,我們調用靜態方法的時候,不需要先生成一個實例,可以通過類名直接調用。
這就相當于,在弄模版的時候,這些功能就已經定制在里面了。
你買手機的時候,里面不是經常有一些內置的應用嗎?有些刪都刪不掉,這不就相當于靜態方法嗎?
(我只是舉一個例子啊,你不要非得較真說我可以ROOT一下啊)
如果模板里面已經有了一些做好的功能,今后任何根據這個模具生成出來的產品也自帶了這些功能。
如果模板里面已經做好了一些功能,那么我的確可以使用這個模板,而不需要真正拿到一個產品。
比如生產一部手機,它的模板里面已經做好了一個手電筒的功能,那么,你即便不給我一個真正的產品,僅僅給我一個模板,我是不是也可以用它的手電筒功能呢?
這就是靜態方法。
所以我們常說,靜態方法和靜態屬性為所有實例共用,不就是這個道理嗎?
所以,正常情況下,我們調用一個類的非靜態方法,是不是必須要先new一個對象?
好的,我們現在來 根據 ArrayList 模板生產一個 ArrayList 產品。
這樣,我們才能調用它里面所有的非私有方法。
怎么生產呢,是不是new一下就可以啦?
ArrayList arrayList = new ArrayList();
2.2、 add方法
ArrayList是一個集合,既然是一個集合,那么它肯定是可以往里頭添東西的。
怎么往里面添,用add,用add方法往里面加。
arrayList.add("HelloWorld"); //添加一個字符串
arrayList.add(new Integer(100));//添加一個Integer類型的數字
add的參數就是一個object,這就是多態,多態就是多種形態,多種身份的意思。object可以有多種形態。它可以是String,也可以是Integer,還可以是用戶自定義的類型。
這里是不是又多態了。。。
這就是多態的一個用法。
2.3、 get 方法
既然能夠往里面添加東西,是不是肯定還要拿出來啊。
怎么拿出來,用get方法拿出來,而且一次只能拿一個。不要多拿哦。
get 方法需要傳入一個 int 類型的數字,這個數字就是元素對應的下標。
我們剛才第一個放進去的是 "HelloWorld" ,一個字符串。那么對應的下標就是0 。
第二個放進去的是new Integer(100),這是一個Integer對象,是一個實實在在的東西了。那么對應的下標就是1 。
現在,我取出第1個元素,應該是100。
試試看
Integer i = arrayList.get(1);
報錯了,因為 get 方法返回的是一個Object對象,而我們拿Integer 去接,就出問題了。
這是咋回事呢?
很簡單,比如張三是一個醫生,同時他還擁有一個人類的身份,可并不是所有的人類都是醫生啊?
注意我下面分析的用詞,能幫助你理解。
同理,Integer 是一個整數類型,同時它還擁有一個Object的身份,可并不是所有的 Object 都是Integer 啊?
同接口一樣,A繼承B,可以看成A同時擁有了B的身份。
如果A繼續保持A的身份,那么它不僅擁有自己本身的能力,也擁有B的能力。只要A愿意,他完全可以展現B的能力。
如果A變身成為了B,那么它肯定不希望別人知道他是A。
就好比雖然他知道他可以變身成為奧特曼,但是一般情況下,他都不愿意曝露自己的身份。
所以如果A變身成為了B,那么A就只會使用B的能力,同時隱藏他本身的能力。
如果你是初學者,請仔細體會一下這其中的韻味。慢慢地,你就會對多態有一個更深入的理解。一段時間后,你再重新去看以前寫的代碼,會有不一樣的感覺。
再舉一個例子,幫助你理解。
我 new 一個 ArrayList :(在eclipse中)
ArrayList arrayList = new ArrayList();
換行,寫上arrayList 。
我直接在arrayList 右邊加一個點,然后會有提示:
這些都是它可以調用的方法和屬性,哇,這么多。
如果我這樣寫呢?
Object arrayList = new ArrayList();
我也在arrayList 右邊加一個點
不好意思,你現在只能調用 Object 類的屬性和方法了。
嗯,再體會一下。
初學者在面向對象方面的理解總是會走彎路,如果你能把這些東西理清,對今后的學習會有巨大的好處。
繼續。
我們這里就強轉一下吧,因為我們知道 index 為1的元素是一個Integer類型的。
Integer i = (Integer) arrayList.get(1);
System.out.println(i);
結果
好的,是正確的。
2.4、 remove 方法
remove 方法可以刪除集合中的元素,ArrayList給我們提供了很多刪除元素的方法。
我們這里先看一下第一個方法。
這是通過數組下標來刪除某一個特定的元素,我們剛才給ArrayList添加了兩個元素,下標分別為 0,1 ,那么,如果我刪除第0個元素,會怎么樣呢?
首先,ArrayList的列表長度會不會改變?
我們可以通過size()方法來獲取ArrayList當前的列表長度。
測試:
arrayList.remove(0);
System.out.println(arrayList.size());
打印出來是1,看來的確是影響長度了。
2.5、 toArray 方法
這個方法可以將ArrayList轉換成一個Object數組。
例:
Object[] objs = arrayList.toArray();
for (int i = 0; i < objs.length; i++) {
System.out.println(objs[i].toString());
}
需要注意的是,哪怕你給ArrayList全部添加Integer類型的元素,也不能采用這樣的代碼:
Integer[] objs = (Integer[]) arrayList.toArray();
這是錯誤的,雖然編譯的時候不會報錯,但是運行無法通過。
因為ArrayList的add方法可以添加任意類型的參數,Java運行機制無法獲知ArrayList中的元素是否可以都強制轉換為你指定的類型。所以這種寫法是不被允許的。