再說,數組

前面我跟大家簡單聊過Java的三大集合框架,今天我想再和大家說說數組。雖然我們目前已經很少直接用數組啦!

數組的概念

數組描述的是相同類型的若干個數據,按照一定的先後次序排列組合而成。其中,每一個數據稱作一個數組元素,每個數組元素可以通過一個下標來訪問它們。

數組的特點

1、其長度是確定的。數組一旦被創建,它的大小就是不可以改變的。

2、其元素必須是相同類型,不允許出現混合類型。

3、數組中的元素可以是任何數據類型,包括基本類型和引用類型。

好啦!以上都不是重點!(因為你們都知道)

再說,數組

接下來,我們談談數組與其他容器之間的區別(重點)

數組與其它容器的區別體現在三個方面

效率,類型識別以及可以持有primitives。

數組是Java提供的,能隨機存儲和訪問reference序列的諸多方法中的,最高效的一種。數組是一個簡單的線性序列,所以它可以快速的訪問其中的元素。但是速度是有代價的;當你創建了一個數組之後,它的容量就固定了,而且在其生命週期裡不能改變。也許你會提議先創建一個數組,等到快不夠用的時候,再創建一個新的,然後將舊的數組裡的reference全部導到新的裡面。其實ArrayList就是這麼做的。但是這種靈活性所帶來的開銷,使得ArrayList的效率比起數組有了明顯下降。
Java對數組和容器都做邊界檢查;如果過了界,它就會給一個RuntimeException。這種異常表明這個錯誤是由程序員造成的,這樣你就用不著再在程序裡面檢查了。

還有一些泛型容器類包括List,Set和Map。他們處理對象的時候就好像這些對象都沒有自己的具體類型一樣。也就是說,容器將它所含的元素都看成是(Java中所有類的根類)Object的。這樣你只需要建一種容器,就能把所有類型的對象全都放進去。從這個角度來看,這種做法很不錯(只是苦了 primitive。如果是常量,你還可以用Java的primitive的Wrapper類;如果是變量,那就只能放在你自己的類裡了)。與其他泛型容器相比,這裡體現數組的第二個優勢:創建數組的時候,你也同時指明瞭它所持有的對象的類型(這又引出了第三點--數組可以持有primitives,而容器卻不行)。

也就是說,它會在編譯的時候作類型檢查,從而防止你插入錯誤類型的對象,或者是在提取對象的時候把對象的類型給搞錯了。Java在編譯和運行時都能阻止你將一個不恰當的消息傳給對象。所有這並不是說使用容器就有什麼危險,只是如果編譯器能夠幫你指定,那麼程序運行會更快,最終用戶也會較少收到程序運行異常的騷擾。
從效率和類型檢查的角度來看,使用數組總是沒錯的。但是,如果你在解決一個更為一般的問題,那數組就會顯得功能太弱了點。

數組是第一流的對象
不管你用的是哪種類型的數組,數組的標識符實際上都是一個“創建在堆(heap)裡的實實在在的對象的”reference。實際上是那個對象持有其他對象的reference。你即可以用數組的初始化語句,隱含地創建這個對象,也可以用new表達式,明確地創建這個對象,只讀的length屬性能告訴你數組能存儲多少元素。它是數組對象的一部分(實際上也是你唯一能訪問的屬性或方法)。‘[]’語法是另一條訪問數組對象的途徑。
你沒法知道數組裡面究竟放了多少元素,因為length只是告訴你數組能放多少元素,也就是說是數組對象的容量,而不是它真正已經持有的元素的數量。但是,創建數組對象的時候,它所持有的reference都會被自動地初始化為null,所以你可以通過檢查數組的某個“槽位”是否為null,來判斷它是否持有對象。以此類推,primitive的數組,會自動來數字初始化為零,字符初始化為(char)0,boolean初始化為false。

primitive容器

容器類只能持有Object對象的reference。而數組除了能持有Objects的reference之外,還可以直接持有primitive。當然可以使用諸如Integer,Double之類的wrapper類。把primitive的值放到容器中,但這樣總有點怪怪的。此外, primitive數組的效率要比wrapper類容器的高出許多。
當然,如果你使用primitive的時候,還需要那種“能隨需要自動擴展的”容器類的靈活性,那就不能用數組了。你只能用容器來存儲primitive的wrapper類。

返回一個數組
假設你寫了一個方法,它返回的不是一個而是一組東西。那麼在Java中就可以返回的“就是一個數組”。與C++不同,你永遠也不必為Java的數組操心--只要你還需要它,它就還在;一旦你用完了,垃圾回收器會幫你把它打掃乾淨。

Arrays類

java.util裡面有一個Arrays類,它包括了一組可用於數組的static方法,這些方法都是一些實用工具。其中有四個基本方法:用來比較兩個數組是否相等的equals();用來填充的fill();用來對數組進行排序的sort();以及用於在一個已排序的數組中查找元素的 binarySearch()。所有這些方法都對primitive和Object進行了重載。此外還有一個asList()方法,它接受一個數組,然後把它轉成一個List容器。


雖然Arrays還是有用的,但它的功能並不完整。舉例來說,如果它能讓我們不用寫for循環就能直接打印數組,那就好了。此外,正如你所看到的fill()只能用一個值填數組。所以,如果你想把隨機生成的數字填進數組的話,fill()是無能為力的。

複製一個數組

Java標準類庫提供了一個System.arraycopy()的static方法。相比for循環,它能以更快的速度拷貝數組。System.arraycopy()對所有類型都作了重載。
對象數組和primitive數組都能拷貝。但是如果你拷貝的是對象數組,那麼你只拷貝了它們的reference--對象本身不會被拷貝。這被稱為淺拷貝(shallow copy)。

數組的比較

為了能比較數組是否完全相等,Arrays提供了經重載的equals()方法。當然,也是針對各種primitive以及Object的。兩個數組要想完全相等,他們必須有相同數量的元素,而且數組的每個元素必須與另一個數組的相對應的位置上的元素相等。元素的相等性,用equals()判斷。(對於 primitive,它會使用其wrapper類的equals();比如int使用Integer.equals()。)。

數組元素的比較

Java裡面有兩種能讓你實現比較功能的方法。一是實現java.lang.Comparable接口,並以此實現類“自有的”比較方法。這是一個很簡單的接口,它只有一個方法compareTo()。這個方法能接受另一個對象作為參數,如果現有對象比參數小,它就會返回一個負數,如果相同則返回零,如果現有的對象比參數大,它就返回一個正數。
static randInt()方法會生成一個介於0到100之間的正數。
現在假設,有人給你一個沒有實現Comparable接口的類,或者這個類實現了Comparable接口,但是你發現它的工作方式不是你所希望的,於是要重新定義一個新的比較方法。Java沒有強求你一定要把比較代碼塞進類裡,它的解決方案是使用“策略模式(strategy design pattern)”。有了策略之後,你就能把會變的代碼封裝到它自己的類裡(即所謂的策略對象strategy object)。你把策略對象交給不會變的代碼,然後用它運用策略完成整個算法。這樣,你就可以用不同的策略對象來表示不同的比較方法,然後把它們都交給同一個排序程序了。接下來就要“通過實現Comparator接口”來定義策略對象了。這個接口有兩個方法compare()和equals()。但是除非是有特殊的性能要求,否則你用不著去實現equals()。因為只要是類,它就都隱含地繼承自Object,而Object裡面已經有了一個 equals()了。所以你儘可以使用缺省的Object的equals(),這樣就已經滿足接口的要求了。


Collections類裡專門有一個會返回與對象自有的比較法相反的Comparator的方法。它能很輕易地被用到CompType上面。
Collections.reverseOrder()返回了一個Comparator的reference。
compare()方法會根據第一個參數是小於,等於還是大於第二個參數,分別返回負整數,零或是正整數。

數組的排序

有了內置的排序方法之後,你就能對任何數組排序了,不論是primitive的還是對象數組的,只要它實現了Comparable接口或有一個與之相關的Comparator對象就行了。
Java標準類庫所用的排序算法已經作了優化--對primitive,它用的是“快速排序(Quicksort)”,對對象,它用的是“穩定合併排序(stable merge sort)”。所以除非是prolier表明排序算法是瓶頸,否則你不用為性能擔心。

查詢有序數組

一旦數組排完序,你就能用Arrays.binarySearch()進行快速查詢了。但是切忌對一個尚未排序的數組使用binarySearch();因為這麼做的結果是沒意義的。
如果Arrays.binarySearch()找到了,它就返回一個大於或等於0的值。否則它就返回一個負值,而這個負值要表達的意思是,如果你手動維護這個數組的話,這個值應該插在哪個為止。這個值就是:


-(插入點)-1
“插入點”就是,在所有“比要找的那個值”更大值中,最小的那個值的下標,或者,如果數組中所有的值都比要找的值小,它就是a.size()。
如果數組裡面有重複元素,那它不能保證會返回哪一個。這個算法不支持重複元素,不過它也不報錯。所以,如果你需要的是一個無重複元素的有序序列的話,那麼可以考慮使用本章後面所介紹的TreeSet(支持【排序順序“sorted order”】)和LinkedHashSet(支持【插入順序“sorted order”】)。這兩個類會幫你照看所有細節。只有在遇到性能瓶頸的時候,你才應該用手動維護的數組來代替這兩個類。
如果排序的時候用到了Comparator(針對對象數組,primitive數組不允許使用Comparator),那麼binarySearch()的時候,也必須使用同一個Comparator(用這個方法的重載版)。

數組總結

總而言之,如果你要持有一組對象,首選,同時也是效率最高的選擇,應該是數組。而且,如果這是一組primitive的話,你也只能用數組。還有一些更為一般的情況,也就是寫程序的時候還不知道要用多少對象,或者要用一種更復雜方式來存儲對象情況。為此,Java提供了“容器類(container class)”。其基本類型有List,Set和Map。


它們還有一些別的特性。比方說Set所持有的對象,個個都不同,Map則是一個“關聯性數組(associative array)”,它能在兩個對象之間建立聯繫。此外,與數組不同,它們還能自動調整大小,所以你可以往裡面放任意數量的對象。

好啦!今天數組的知識點,理論上比較多,文字更加是密密麻麻的。主要講的也是底層的一些東西,同學認真看完的話相信多少還是有收穫的!謝謝大家保持耐心看到這裡,有不同見解的可以留言咋們一起討論!


分享到:


相關文章: