以前學習了java中從語法到經常使用類的部分。在編程中有這樣一類需求,就是要保存批量的相同數據類型。針對這種需求通常都是使用容器來存儲。以前說過Java中的數組,可是數組不能改變長度。Java中提供了另外一種存儲方式,就是用容器類來處理這種須要動態添加或者刪除元素的狀況
java
Java中最多見的容器有一維和多維。單維容器主要是一個節點上存儲一個數據。好比列表和Set。而多維是一個節點有多個數據,例如Map,每一個節點上有鍵和值。
單維容器的上層接口是Collection,它根據存儲的元素是否爲線性又分爲兩大類 List與Set。它們根據實現不一樣,List又分爲ArrayList和LinkedList;Set下面主要的實現類有TreeSet、HashSet。
它們的結構大體以下圖:
算法
Collection 是單列容器的最上層的抽象接口,它裏面定義了全部單列容器都共有的一些方法:編程
boolean add(E e)
:向容器中添加元素void clear()
: 清空容器boolean contains(Object o)
: 判斷容器中是否存在對應元素boolean isEmpty()
: 容器是否爲空boolean remove(Object o)
: 移除指定元素<T> T[] toArray(T[] a)
: 轉化爲指定類型的數組list是Collection 中的一個有序容器,它裏面存儲的元素都是按照必定順序排序的,可使用索引進行遍歷。容許元素重複出現,它的實現中有 ArrayList和 LinkedList數組
Set集合是Collection下的另外一個抽象結構,Set相似於數學概念上的集合,不關心元素的順序,不能存儲重複元素。ide
從上面的描述看,想要在HashSet中添加元素,須要首先計算hash值,在判斷集合中是否存在元素。這樣在存儲自定義類型的元素的時候,須要保證類可以正確計算hash值以及進行類型的相等性判斷。所以要重寫類的hashCode
和equals
方法。
例以下面的例子函數
class Person{ private String name; private int age; Person(){ } Person(String name, int age){ this.name = name; this.age = age; } public int getAge(){ return this.age; } public String getName(){ return this.name; } public void setAge(int age){ this.age = age; } public void setName(String name){ this.name = name; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); } @Override public int hashCode() { return Objects.hash(this.name, this.age); } }
上面說到HashSet是無序的結構,若是咱們想要使用hashSet,可是又想它有序,該怎麼辦?在Set中提供了另外一個實現,LinkedHashMap。它的底層是一個Hash表和一個鏈表,Hash表用來存儲真正的數據,而鏈表用來存儲元素的順序,這樣就結合了兩者的優先。學習
Map是一個雙列的容器,一個節點存儲了兩個值,一個是元素的鍵,另外一個是值。其中Key 和 Value既能夠是相同類型的值,也能夠是不一樣類型的值。Key和Value是一一對應的關係。一個key只能對應一個值,可是多個key能夠指向同一個value,有點像數學中函數的自變量和值的關係。ui
Map經常使用的實現類有: HashMap和LinkedHashMap。this
經常使用的方法有:指針
void clear()
: 清空集合boolean containsKey(Object key)
: map中是否包含對應的鍵V get(Object key)
: 根據鍵返回對應的值V put(K key, V value)
: 添加鍵值對boolean isEmpty()
: 集合是否爲空int size()
: 包含鍵值對的個數針對列表類型的,元素順序固定,咱們可使用循環依據索引進行遍歷,好比
for(int i = 0; i < list.size(); i++){ String s = list.get(i); }
而對於Set這種不關心元素的順序的集合來講,不能再使用索引了。針對單列集合,有一個迭代器接口,使用迭代器能夠實現遍歷
迭代器能夠理解爲指向集合中某一個元素的指針。使用迭代器能夠操做元素自己,也能夠根據當前元素尋找到下一個元素,它的經常使用方法有:
boolean hasNext()
: 當前迭代器指向的位置是否有下一個元素E next()
: 獲取下一個元素並返回。調用這個方法後,迭代器指向的位置發生改變使用迭代器的通常步驟以下:
iterator()
返回一個迭代器hasNext
方法,判斷集合中是否還有元素須要遍歷next
方法,找到迭代器指向的下一個元素//假設set是一個 HashSet<String>集合 Iterator<String> it = set.iterator(); while(it.hasNext()){ Stirng s = it.next(); }
索引和迭代器的方式只能遍歷單列集合,像Map這樣的多列集合不能使用上述方式,它有額外的方法,主要有兩種方式
針對第一種方法,Map中有一個 keySet()
方法。這個方法會獲取到全部的key值並保存將這些值保存爲一個新的Set返回,咱們只要遍歷這個Set並調用 Map的get方法便可獲取到對應的Value, 例如:
// 假設map 是一個 HashMap<String, String> 集合 Set<String> kSet = map.keySet(); Iterator<String> key = kSet.iterator(); while(it.hasNext()){ String key = it.next(); String value = map.get(key); }
針對第二種方法,能夠先調用 Map的 entrySet()
獲取一個Entry
結構的Set集合。Entry
中保存了一個鍵和它對應的值。使用結構中的 getKey()
和 getValue()
分別獲取key和value。這個結構是定義在Map中的內部類,所以在使用的時候須要使用Map這個類名調用
// 假設map 是一個 HashMap<String, String> 集合 Set<Map.Entry<String,String>> entry = map.entrySet(); Iterator<Map.Entry<String, String>> it = entry.iterator(); while(it.hasNext()){ Map.Entry<String, String> me = it.next(); String key = me.getKey(); String value = me.getValue(); }
在上述遍歷的代碼中,不論是使用for或者while都顯得比較麻煩,咱們能像 Python
等腳本語言那樣,直接在 for
中使用迭代嗎?從JDK1.5 之後引入了for each寫法,使Java可以直接使用for迭代,而不用手工使用迭代器來進行迭代。
for (T t: set);
上述是它的簡單寫法。
例如咱們對遍歷Set的寫法進行簡化
//假設set是一個 HashSet<String>集合 for(String s: set){ //TODO:do some thing }
咱們說使用 for each寫法主要是爲了簡化迭代的寫法,它在底層仍然採用的是迭代器的方式來遍歷,針對向Map這樣沒法直接使用迭代的結構來講,天然沒法使用這種簡化的寫法,針對Map來講須要使用上述的兩種遍歷方式中的一種,先轉化爲可迭代的結構,而後使用for each循環
// 假設map 是一個 HashMap<String, String> 集合 Set<Map.Entry<String, String>> set = map.entrySet(); for(Map.Entry<String, String> entry: set){ String key = entry.getKey(); String value = entry.getValue(); System.out.println(key + "-->" + value); }
在上述的集合中,咱們已經使用了泛型。
泛型與C++ 中的模板基本相似,都是爲了重複使用代碼而產生的一種語法。因爲這些集合在建立,增刪改查上代碼基本相似,只是事先不知道要存儲的數據的類型。若是沒有泛型,咱們須要將全部類型對應的這些結構的代碼都重複寫一遍。有了泛型咱們就能更加專一於算法的實現,而不用考慮具體的數據類型。
在定義泛型的時候,只須要使用 <>中包含表示泛型的字母便可。常見的泛型有:
<>
中可使用任意標識符來表示泛型,只要符合Java的命名規則便可。使用 T
或者 E
只是爲了方便而已,好比下面的例子
public static <Element> void print(Element e){ System.out.println(e); }
固然也可使用Object 對象來實現泛型的重用代碼的功效,在對元素進行操做的時候主要使用java的多態來實現。可是使用多態的一個缺點是沒法使用元素對象的特有方法。
泛型能夠在類、接口、方法中使用
在定義類時定義的泛型能夠在類的任意位置使用
class DataCollection<T>{ private T data; public T getData(){ return this.data; } public void SetData(T data){ this.data = data; } }
在定義類的時候定義的泛型在建立對象的時候指定具體的類型.
也能夠在定義接口的時候定義泛型
public interface DataCollection<T>{ public abstract T getData(); public abstract void setData(T data); }
定義接口時定義的泛型能夠在定義實現類的時候指定泛型,或者在建立實現類的對象時指定泛型
public class StringDataCollectionImpl implements DataCollection<String>{ private String data; public String getData(){ return this.data; } public void SetData(String data){ this.data = data; } } public interface DataCollection<T> implements DataCollection<T>{ private T data; public T getData(){ return this.data; } public void SetData(T data){ this.data = data; } }
除了在定義類和接口時使用外,還能夠在定義方法的時候使用,針對這種狀況,不須要顯示的指定使用哪一種類型,因爲接收返回數據和傳入參數的時候已經知道了
public static <Element> Element print(Element e){ System.out.println(e); return e; } String s = print("hello world");
在使用通配符的時候可能有這樣的需求:我想要使用泛型,可是不但願它傳入任意類型的值,我只想要處理繼承自某一個類的類型,就好比說我只想保存那些實現了某個接口的類。咱們固然能夠將數據類型定義爲某個接口,可是因爲多態的這一個缺陷,實現起來總不是那麼完美。這個時候可使用泛型的通配符。
泛型中使用 ?
做爲統配符。在通配符中可使用 super
或者 extends
表示泛型必須是某個類型的父類或者是某個類型的實現類
class Fruit{ } class Apple extends Fruit{ } class Bananal extends Fruit{ } static void putFruit(<? extends Fruit> data){ }
上述代碼中 putFruit
函數中只容許 傳遞 Fruit
類的子類或者它自己做爲參數。
固然也可使用 <? super T>
表示只能取 T類型的父類或者T類型自己。