數組和集合的哲學思考

1       數組和集合的哲學認知

在漢語大詞典中集合的名詞解釋是(1)在現實生活中,許多分散的人和物聚在一起;(2)在數學上指若干具有共同屬性的事物的總體。如全部整數就成一個整數的集合。而數組在漢語大詞典中卻沒有解釋,我們借用以下的解釋作爲數組的名詞解釋,數組是一組具有相同類型和名稱的變量的集合。這些變量稱爲數組的元素,每個數組元素都有一個編號,這個編號叫做下標,我們可以通過下標來區別這些元素。數組元素的個數有時也稱之爲數組的長度。數組是屬於集合的,是集合中的一個分類,數組是集合中的一種特殊形式。

數組和集合總體的形的抽象也。在現實生活中集體是由個體組成、線是由點組成、平臺是由線組成的,爲了研究這些個體聚集而成的物質,我們就需要一種對這種物質的抽象形式,即數組或集合。研究數組和集合,可以幫助我們解決求平均值、最大最小、排序、查找等統計類的問題。

數組和集合是統計的基礎。統計總體簡稱總體是我們要調查或統計某一現象全部數據的集合。總體是一個簡化的概念,它可以分爲自然總體和測量總體。所謂自然總體就是由客觀存在的具有相同性質的許多個別事物構成的整體;自然總體中的個體通常都具有多種屬性,我們把個體所具有某種共同那個屬性的數值的整體稱爲一個測量總體。總體必須具備的三個特性,即大量性、同質性、變異性。

分類是集合的前提,集合是分類的結果,分類標準是按事物的屬性。集合就是按事物某個屬性分類的結果體。“分”即鑑定、描述和命名,“類”即歸類,按一定秩序排列類羣,也是系統演化。把無規律的事物分爲有規律的,按照不同的特點分類事物,使事物按照分類對應規則來處理。

數組和集合的理論基礎是集合論。集合論或集論是研究集合(由一堆抽象物件構成的整體)的數學理論,包含集合、元素和成員關係等最基本數學概念。在大多數現代數學的公式化中,集合論提供了要如何描述數學物件的語言。集合論和邏輯與一階邏輯共同構成了數學的公理化基礎,以未定義的“集合”與“集合成員”等術語來形式化地建構數學物件。在樸素集合論中,集合是被當做一堆物件構成的整體之類的自證概念。 在公理化集合論中,集合和集合成員並不直接被定義,而是先規範可以描述其性質的一些公理。在此一想法之下,集合和集合成員是有如在歐式幾何中的點和線,而不被直接定義。所以可以把集合論(包括集合的定義和運算)看作是邏輯的形式化表示。

1.1  集合的定義

(1)     外延式定義

A={a,b,c} 。外延中的對象與概念的關係: a ∈ A

(2)     內涵式定義

A={x|x 滿足所有內涵的條件 }

1.2  集合的關係

(1)     包含關係

集合 A 被集合 B 包含(A is included by B),或者說爲 A 包含於 B指的是邏輯表達式 (∀x)( x ∈ A imp x ∈ B ) 成立的時候。也就是說A 的任何元素 x 都屬於B ,寫作 A ⊆ B

(2)     並集關係

當 A={x: P(x)} 和 B = {y: Q(y)} 爲集合的時候,因 R(z) = P(z) or Q(z) 成爲一個新的性質,於是就可以考慮成一個新的集合 C = {z: R(z)}。稱其爲,集合 A 和 B 的 並 或 並集(Union) ,寫作 C = A U B。因爲性質 P(x) 和 x ∈ A , Q(x) 和 x ∈ B 等價,所以A U B = {x: R(x)} = {x: P(x) or Q(x)} = {x: x ∈ A or x ∈ B} 成立。也就是說 A 和 B 的並集就是,屬於A或B的所有元素的集合。

(3)     交集關係

跟定義並集一樣對交集進行定義。 當A={x: P(x)} 和 B = {y: Q(y)}爲集合的時候,因R(z) = P(z) and Q(z) 成爲一個新的性質,於是就可以考慮成一個新的集合C = {z: R(z)}。稱其爲,集合 A 和B的 交 或 交集(Intersection),寫作C = A ∩ B 。因爲性質P(x) 和 x ∈ A , Q(x) 和x ∈ B 等價,所以 A ∩ B = {x: R(x)} = {x: P(x) and Q(x)} = {x: x ∈ A and x ∈ B} 成立。也就是說A 和 B 的交集就是 ,A 和 B 共有元素的集合。

(4)     補集

A = {x: P(x)}爲集合的時候 ,{x: not P(x)} 叫做 A 的補集(Complement),寫作 A'。這個集合是,不屬於A的元素的集合。

 

2       JAVA語言中的數組

數組是一組具有相同類型和名稱的變量的集合。這些變量稱爲數組的元素,每個數組元素都有一個編號,這個編號叫做下標,我們可以通過下標來區別這些元素。數組元素的個數有時也稱之爲數組的長度。一般情況下,數組的元素類型必須相同,可以是前面講過的各種基本數據類型。但當數組類型被指定爲變體型時,它的各個元素就可以是不同的類型。數組和變量一樣,也是有作用域的,按作用域的不同可以把數組分爲:過程級數組(或稱爲局部數組)、模塊級數組以及全局數組。

2.1  數組的基本特性

數組是一種高效的存儲和隨機訪問對象引用序列的方式,使用數組可以快速的訪問數組中的元素。但 是當創建一個數組對象 ( 注意和對象數組的區別 ) 後,數組的大小也就固定了,當數組空間不足的時候就再創建一個新的數組,把舊的數組中所有的引用複製到新的數組中。

Java 中的數組和容器都需要進行邊界檢查,如果越界就會得到一個 RuntimeException 異常。這點和 C++ 中有所不同, C++ 中 vector 的操作符 [] 不會做邊界檢查,這在速度上會有一定的提高, Java 的數組和容器會因爲時刻存在的邊界檢查帶來一些性能上的開銷。

Java 中通用的容器類不會以具體的類型來處理對象,容器中的對象都是以 Object 類型處理的,這是 Java 中所有類的基類。另外,數組可以保存基本類型,而容器不能,它只能保存任意的 Java 對象。

一般情況下,考慮到效率與類型檢查,應該儘可能考慮使用數組。如果要解決一般化的問題,數組可能會受到一些限制,這時可以使用 Java 提供的容器類。

2.2  數組的相關操作

在 java .util.Arrays 類中,有許多 static 靜態方法,提供了操作數組的一些基本功能:

 

    equals() 方法 ---- 用於比較兩個數組是否相等,相等的條件是兩個數組的元素個數必須相等,並且對應位置的元素也相等。

    fill() 方法 ---- 用以某個值填充整個數組,這個方法有點笨。

    asList() 方法 ---- 接受任意的數組爲參數,將其轉變爲 List 容器。

    binarySearch() 方法 ---- 用於在已經排序的數組中查找元素,需要注意的是必須是已經排序過的數組。當 Arrays.binarySearch() 找到了查找目標時,該方法將返回一個等於或大於 0 的值,否則將返回一個負值,表示在該數組目前的排序狀態下此目標元素所應該插入的位置。負值的計算公式是 “-x-1” 。 x 指的是第一個大於查找對象的元素在數組中的位置,如果數組中所有的元素都小於要查找的對象,則 x = a.size() 。如果數組中包含重複的元素,則無法保證找到的是哪一個元素,如果需要對沒有重複元素的數組排序,可以使用 TreeSet 或者 LinkedHashSet 。另外,如果使用 Comparator 排序了某個對象數組,在使用該方法時必須提供同樣的 Comparator 類型的參數。需要注意的是,基本類型數組無法使用 Comparator 進行排序。

   sort() 方法 ---- 對數組進行升序排序。

在 Java 標準類庫中,另有 static 方法 System.arraycopy() 用來複制數組,它針對所有類型做了重載。

3       JAVA語言中的數組類

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

    還有一些泛型容器類包括List,Set 和Map。他們處理對象的時候就好像這些對象都沒有自己的具體類型一樣。也就是說,容器將它所含的元素都看成是(Java 中所有類的根類)Object的。這樣你只需要建一種容器,就能把所有類型的對象全都放進去。從這個角度來看,這種做法很不錯(只是苦了 primitive。如果是常量,你還可以用Java 的 primitive的Wrapper類;如果是變量,那就只能放在你自己的類裏了)。與其他泛型容器相比,這裏體現數組的第二革優勢:創建數組的時候,你 也同時指明瞭它所持有的對象的類型(這又引出了第三點--數組可以持有primitives,而容器卻不行)。也就是說,它會在編譯的時候作類型檢查,從 而防止你插入錯誤類型的對象,或者是在提取對象的時候把對象的類型給搞錯了。Java 在編譯和運行時都能阻止你將一個不恰當的消息傳給對象。所有這並不是說使用容器就有什麼危險,只是如果編譯器能夠幫你指定,那麼程序運行會更快,最終用戶 也會較少收到程序運行異常的騷擾。

    從效率和類型檢查的角度來看,使用數組總是沒錯的。但是,如果你在解決一個更爲一般的問題,那數組就會顯得功能太弱了點。

3.1  數組是第一流的對象

    不管你用的是那種類型的數組,數組的標識符實際上都是一個“創建在堆(heap)裏的實實在在的對象的”reference。實際上是那個對象持 有其他對象的reference。你即可以用數組的初始化語句,隱含地創建這個對象,也可以用new表達式,明確地創建這個對象,只讀的length屬性 能告訴你數組能存儲多少元素。它是數組對象的一部分(實際上也是你唯一能訪問的屬性或方法)。‘[]’語法是另一條訪問數組對象的途徑。

    你沒法知道數組裏面究竟放了多少元素,因爲length只是告訴你數組能放多少元素,也就是說是數組對象的容量,而不是它真正已經持有的元素的數 量。但是,創建數組對象的時候,它所持有的reference都會被自動地初始化爲null,所以你可以通過檢查數組的某個 “槽位”是否爲null,來判斷它是否持有對象。以此類推,primitive的數組,會自動來數字初始化爲零,字符初始化爲 (char)0,boolean初始化爲false。

3.2  Arrays類的應用

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

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

3.2.1       複製一個數組

  Java 標準類庫提供了一個System.arraycopy()的static方法。相比for循環,它能以更快的速度拷貝數組。 System.arraycopy()對所有類型都作了重載。

  對象數組和primitive數組都能拷貝。但是如果你拷貝的是對象數組,那麼你只拷貝了它們的reference--對象本身不會被拷貝。這被 成爲淺拷貝(shallow copy)。

3.2.2       數組的比較

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

3.2.3       數組元素的比較

    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(),這樣就已經滿足接口的要求了。

3.2.4       數組的排序

   有了內置的排序方法之後,你就能對任何數組排序了,不論是primitive的還是對象數組的,只要它實現了Comparable接口或有一個與 之相關的Comparator對象就行了。

   Java 標準類庫所用的排序算法已經作了優化--對primitive,它用的是“快速排序(Quicksort)”,對對象,它用的是“穩定合併排序 (stable merge sort)”。所以除非是prolier表明排序算法是瓶頸,否則你不用爲性能擔心。

3.2.5       查詢有序數組

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

 

4       JAVA語言中的集合

Java容器類類庫的用途是“保存對象”,並將其劃分爲兩個不同的概念:

1)  Collection 。 一組對立的元素,通常這些元素都服從某種規則。List必須保持元素特定的順序,而Set 不能有重複元素。

2)  Map 。 一組 成對的“鍵值對”對象。初看起來這似乎應該是一個Collection ,其元素是成對的對象,但是這樣的設計實現起來太笨拙了,於是我們將Map明確的提取出來形成一個獨立的概念。另一方面,如果使用Collection 表示Map的部分內容,會便於查看此部分內容。因此Map一樣容易擴展成多維Map ,無需增加新的概念,只要讓Map中的鍵值對的每個“值”也是一個Map即可。

Collection和Map的區別在於容器中每個位置保存的元素個數。Collection 每個位置只能保存一個元素(對象)。此類容器包括:List ,它以特定的順序保存一組元素;Set 則是元素不能重複。Map保存的是“鍵值對”,就像一個小型數據庫。我們可以通過“鍵”找到該鍵對應的“值”。

u     Collection – 對象之間沒有指定的順序,允許重複元素。

u     Set –  對象之間沒有指定的順序,不允許重複元素

u     List–  對象之間有指定的順序,允許重複元素,並引入位置下標。

u     Map – 接口用於保存關鍵字(Key)和數值(Value)的集合,集合中的每個對象加入時都提供數值和關鍵字。Map 接口既不繼承 Set 也不繼承 Collection。

4.1  Collection

Collection 接口用於表示任何對象或元素組。想要儘可能以常規方式處理一組元素時,就使用這一接口。Collection 在前面的大圖也可以看出,它是List和Set 的父類。並且它本身也是一個接口。它定義了作爲集合所應該擁有的一些方法。如下:(注意:集合必須只有對象,集合中的元素不能是基本數據類型。)

Collection接口支持如添加和除去等基本操作。設法除去一個元素時,如果這個元素存在,除去的僅僅是集合中此元素的一個實例。

u     boolean add(Object element)

u     boolean remove(Object element)

Collection 接口還支持查詢操作:

u     int size()

u     boolean isEmpty()

u     boolean contains(Object element)

u     Iterator iterator()

組操作 :Collection 接口支持的其它操作,要麼是作用於元素組的任務,要麼是同時作用於整個集合的任務。

u     boolean containsAll(Collection collection)

u     boolean addAll(Collection collection)

u     void clear()

u     void removeAll(Collection collection)

u     void retainAll(Collection collection)

containsAll() 方法允許您查找當前集合是否包含了另一個集合的所有元素,即另一個集合是否是當前集合的子集。其餘方法是可選的,因爲特定的集合可能不支持集合更改。 addAll() 方法確保另一個集合中的所有元素都被添加到當前的集合中,通常稱爲 clear() 方法從當前集合中除去所有元素。 removeAll() 方法類似於 clear() ,但只除去了元素的一個子集。 retainAll() 方法類似於 removeAll() 方法,不過可能感到它所做的與前面正好相反:它從當前集合中除去不屬於另一個集合的元素,即

4.2 List

List 就是列表的意思,它是Collection 的一種,繼承了 Collection 接口,以定義一個允許重複項的有序集合。該接口不但能夠對列表的一部分進行處理,還添加了面向位置的操作。List 是按對象的進入順序進行保存對象,而不做排序或編輯操作。它除了擁有Collection接口的所有的方法外還擁有一些其他的方法。

面向位置的操作包括插入某個元素或 Collection 的功能,還包括獲取、除去或更改元素的功能。在 List 中搜索元素可以從列表的頭部或尾部開始,如果找到元素,還將報告元素所在的位置。

u       void add(int index, Object element) :添加對象element到位置index上

u       boolean addAll(int index, Collection collection) :在index位置後添加容器collection中所有的元素

u       Object get(int index) :取出下標爲index的位置的元素

u       int indexOf(Object element) :查找對象element 在List中第一次出現的位置

u       int lastIndexOf(Object element) :查找對象element 在List中最後出現的位置

u       Object remove(int index) :刪除index位置上的元素

u       Object set(int index, Object element) :將index位置上的對象替換爲element 並返回老的元素。

4.3  Map

Map 接口不是 Collection 接口的繼承。而是從自己的用於維護鍵-值關聯的接口層次結構入手。按定義,該接口描述了從不重複的鍵到值的映射。我們可以把這個接口方法分成三組操作:改變、查詢和提供可選視圖。改變操作允許您從映射中添加和除去鍵-值對。鍵和值都可以爲 null。但是,您不能把 Map 作爲一個鍵或值添加給自身。

u       Object put(Object key,Object value):用來存放一個鍵-值對Map

u       Object remove(Object key):根據key(),移除一個鍵-值對,並將值返回

u       void putAll(Map mapping) :將另外一個Map中的元素存入當前的Map

u       void clear() :清空當前Map中的元素

查詢操作允許您檢查映射內容:

u       Object get(Object key) :根據key()取得對應的值

u       boolean containsKey(Object key) :判斷Map中是否存在某鍵(key

u       boolean containsValue(Object value):判斷Map中是否存在某值(value)

u       int size():返回Map鍵-值對的個數

u       boolean isEmpty() :判斷當前Map是否爲空

最後一組方法允許您把鍵或值的組作爲集合來處理。

u       public Set keySet() :返回所有的鍵(key),並使用Set容器存放

u       public Collection values() :返回所有的值(Value),並使用Collection存放

u       public Set entrySet() 返回一個實現 Map.Entry 接口的元素 Set

因爲映射中鍵的集合必須是唯一的,就使用 Set 來支持。因爲映射中值的集合可能不唯一,就使用 Collection 來支持。最後一個方法返回一個實現 Map.Entry 接口的元素 Set。

4.4  Set

按照定義,Set 接口繼承 Collection 接口,而且它不允許集合中存在重複項。所有原始方法都是現成的,沒有引入新方法。具體的 Set 實現類依賴添加的對象的 equals() 方法來檢查等同性。我們簡單的描述一下各個方法的作用:

u     public int size() :返回set中元素的數目,如果set包含的元素數大於Integer.MAX_VALUE,返回Integer.MAX_VALUE

u     public boolean isEmpty() :如果set中不含元素,返回true

u     public boolean contains(Object o) :如果set包含指定元素,返回true

u     public Iterator iterator()

l         返回set中元素的迭代器

l         元素返回沒有特定的順序,除非set是提高了該保證的某些類的實例

u     public Object[] toArray() :返回包含set中所有元素的數組

u     public Object[] toArray(Object[] a) :返回包含set中所有元素的數組,返回數組的運行時類型是指定數組的運行時類型

u     public boolean add(Object o) :如果set中不存在指定元素,則向set加入

u     public boolean remove(Object o) :如果set中存在指定元素,則從set中刪除

u     public boolean removeAll(Collection c) :如果set包含指定集合,則從set中刪除指定集合的所有元素

u     public boolean containsAll(Collection c) :如果set包含指定集合的所有元素,返回true。如果指定集合也是一個set,只有是當前set的子集時,方法返回true

u     public boolean addAll(Collection c) :如果set中中不存在指定集合的元素,則向set中加入所有元素

u     public boolean retainAll(Collection c) :只保留set中所含的指定集合的元素(可選操作)。換言之,從set中刪除所有指定集合不包含的元素。 如果指定集合也是一個set,那麼該操作修改set的效果是使它的值爲兩個set的交集

u     public boolean removeAll(Collection c) :如果set包含指定集合,則從set中刪除指定集合的所有元素

u     public void clear() :從set中刪除所有元素

 

5       JAVA語言的數組和集合應用實例

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