Java的基本使用之集合
1、集合
在Java中,如果一個Java對象可以在內部持有若干其他 Java 對象,并對外提供訪問接口,我們把這種Java對象稱為集合。很顯然,Java 的數(shù)組可以看作是一種集合。
在Java中數(shù)組有如下限制:
- 數(shù)組初始化后大小不可變;
- 數(shù)組只能按索引順序存取。
因此,我們需要各種不同類型的集合類來處理不同的數(shù)據(jù),例如:
- 可變大小的順序鏈表;
- 保證無重復元素的集合.....
最常用的集合有:ArrayList,HashSet,HashMap,Array(數(shù)組)。
Java標準庫自帶的java.util包提供了集合類:Collection ,它是除 Map 外所有其他集合類的根接口。Java的java.util包主要提供了以下三種類型的集合:
List:一種有序列表的集合,例如,按索引排列的Student的List;Set:一種保證沒有重復元素的集合,例如,所有無重復名稱的Student的Set;Map:一種通過鍵值(key-value)查找的映射表集合,例如,根據(jù)Student的name查找對應Student的Map。
1.1、集合的特點
java 中的集合可以存放不同類型、不限數(shù)量的數(shù)據(jù)類型(可以通過泛型來限制只可以存入某種類型的數(shù)據(jù))。Java 中的集合只能存放對象,比如你把一個 int 類型的數(shù)據(jù) 1 放入集合,其實它是自動轉換成 Integer 類后放入的。
Java集合的其他特點:
- 實現(xiàn)了接口和實現(xiàn)類相分離,例如,有序表的接口是
List,具體的實現(xiàn)類有ArrayList,LinkedList等, - 支持泛型,我們可以限制在一個集合中只能放入同一種數(shù)據(jù)類型的元素,例如:List<String> list = new ArrayList<>(); // 只能放入String類型
- Java訪問集合總是通過統(tǒng)一的方式——迭代器(Iterator)來實現(xiàn),通過迭代器訪問無需知道集合內部的元素的存儲方式
2、List 集合(有序可重復,可變長度數(shù)組)
List 集合是一種有序列表,它的行為和數(shù)組幾乎完全相同:List 內部按照放入元素的先后順序存放,每個元素都可以通過索引確定自己的位置,List的索引和數(shù)組一樣,從0開始。List 集合的大小是可變的。
List 代表一個元素有序且可重復的集合,集合中的每個元素都有其對應的順序索引。
List 允許使用重復元素,可以通過索引來訪問指定位置的集合元素。List 默認按元素的添加順序設置元素的索引
2.1、ArrayList 有序列表
在實際應用中,需要增刪元素的有序列表,我們使用最多的是ArrayList。實際上,ArrayList在內部是使用數(shù)組來存儲所有元素的。
2.2、LinkedList 有序列表
ArrayList 通過數(shù)組的方式實現(xiàn)了 List 接口,LinkedList通過“鏈表”的方式實現(xiàn)了List接口,在LinkedList中,它的內部每個元素都指向下一個元素:

2.3、ArryList 和 LinkedList 的對比
ArrayList 集合的底層數(shù)據(jù)結構是數(shù)組,它查詢快,增刪慢!線程不安全,但是效率高!
LinkedList 集合的底層數(shù)據(jù)結構是鏈表,它查詢慢,但是增刪快!線程不安全,但是效率高!

在各種Lists中,最好的做法是以 ArrayList 作為缺省選擇。當插入、刪除頻繁時,可以使用 LinkedList()
2.4、List 集合的幾個主要接口方法
List<E>接口的幾個主要的接口方法:
- 在末尾添加一個元素:
void add(E e) - 在指定索引添加一個元素:
void add(int index, E e) - 刪除指定索引的元素:
int remove(int index) - 刪除某個元素:
int remove(Object e) - 獲取指定索引的元素:
E get(int index) - 獲取鏈表大小(包含元素的個數(shù)):
int size() - 獲取元素第一次出現(xiàn)的位置:int indexOf(E e)
- 修改指定位置的元素:E set(int index, E e )
示例:
List<String> list = new ArrayList<>(); list.add("apple"); list.add("null"); // 允許添加 null list.add("apple"); // 允許重復添加元素 System.out.println(list.size()); //size: 3 String str = list.get(0); // "apple"
創(chuàng)建 List:
除了使用ArrayList和LinkedList,我們還可以通過List接口提供的of()方法,根據(jù)給定元素快速創(chuàng)建List:
List<Integer> list = List.of(1, 2, 5);
但是List.of()方法不接受null值,如果傳入null,會拋出NullPointerException異常。
2.5、遍歷 List 集合
2.5.1、for 循環(huán)遍歷(不推薦使用)
和數(shù)組類型一樣,我們要遍歷一個List,完全可以用for循環(huán)根據(jù)索引配合get(int)方法遍歷:
List<String> list = List.of("apple", "pear", "banana");
for (int i=0; i<list.size(); i++) {
String s = list.get(i);
System.out.println(s);
}
但這種方式并不推薦,一是代碼復雜,二是因為get(int)方法只有ArrayList的實現(xiàn)是高效的,換成LinkedList后,索引越大,訪問速度越慢。
2.5.2、迭代器 Iterator 遍歷(推薦使用)
for 循環(huán)使用迭代器
我們應該始終堅持使用迭代器本身也是一個對象,但它是由Iterator來訪問List,IteratorList的實例調用iterator()方法的時候創(chuàng)建的。Iterator對象知道如何遍歷一個List,并且不同的List類型,返回的Iterator對象實現(xiàn)也是不同的,但總是具有最高的訪問效率。
List<String> list = List.of("apple", "pear", "banana");
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
String s = it.next();
System.out.println(s);
}
for each 循環(huán)使用迭代器
通過Iterator遍歷List永遠是最高效的方式。由于Iterator遍歷非常常用,為了簡潔代碼,在 Java 中也支持通過 for each 循環(huán)來使用Iterator遍歷
public class Main { public static void main(String[] args) { List<String> list = List.of("apple", "pear", "banana"); for (String s : list) { System.out.println(s); } } }
實際上,只要實現(xiàn)了Iterable接口的集合類都可以直接用for each循環(huán)來遍歷,Java編譯器本身并不知道如何遍歷集合對象,但它會自動把for each循環(huán)變成Iterator的調用,原因就在于Iterable接口定義了一個Iterator<E> iterator()方法,強迫集合類必須返回一個Iterator實例。
2.6、編寫equals()方法以滿足contains()和indexOf()方法的使用
要正確使用List的contains()、indexOf()方法,放入的實例必須正確覆寫equals()方法。
List提供了boolean contains(Object o)方法來判斷List是否包含某個指定元素。此外,int indexOf(Object o)方法可以返回某個元素的索引,如果元素不存在,就返回-1。
List<String> list = List.of("A", "B", "C");
System.out.println(list.contains("C")); // true
System.out.println(list.contains("X")); // false
System.out.println(list.indexOf("C")); // 2
System.out.println(list.indexOf("X")); // -1
List內部并不是通過==判斷兩個元素是否相等,而是使用equals()方法判斷兩個元素是否相等。因此,要正確使用List的contains()、indexOf()這些方法,放入的實例必須正確覆寫equals()方法,否則,放進去的實例,查找不到。我們之所以能正常放入String、Integer這些對象,是因為Java標準庫定義的這些類已經正確實現(xiàn)了equals()方法。
代碼示例:
//下面的list集合中雖然放入了new Person("Bob"),但是用另一個new Person("Bob")查詢不到,原因就是Person類沒有覆寫equals()方法。 public class Main { public static void main(String[] args) { List<Person> list = List.of( new Person("Bob") ); System.out.println(list.contains(new Person("Bob"))); // false } } class Person { String name; public Person(String name) { this.name = name; } }
2.6.1、如何覆寫equals方法
equals()方法的正確編寫方法:
- 先確定實例“相等”的邏輯,即哪些字段相等,就認為實例相等
- 用
instanceof判斷傳入的待比較的Object是不是當前類型,如果是,繼續(xù)比較,否則,返回false; - 對引用類型用
Objects.equals()比較,對基本類型直接用==比較。使用Objects.equals()比較兩個引用類型是否相等可以省去判斷null的麻煩。兩個引用類型都是null時它們也是相等的。
首先,我們要定義“相等”的邏輯含義。比如對于Person類,如果name相等,并且age相等,我們就認為兩個Person實例相等。
public boolean equals(Object o) { if (o instanceof Person) { Person p = (Person) o; return Objects.equals(this.name, p.name) && this.age == p.age; } return false; }
如果不調用List的contains()、indexOf()這些方法,那么放入的元素就不需要實現(xiàn)equals()方法
3、Map 集合(key--value,類似于對象)
Map 集合是一種通過鍵值(key-value)查找的映射表集合,map 集合的作用就是能高效地通過 key 來查找對應的 value 值,提高查找效率。
Map 中不允許存在重復的 key,如果放入相同的key,只會把原有的 key-value 對應的 value 給替換掉。
和 List 類似,Map 也是一個接口,最常用的實現(xiàn)類是 HashMap。
3.1、關于Map集合的方法
put(k, v) 和 get(k) 方法存取元素
通過 put(key, value) 方法來放入值,如果 key 已經存在,該方法返回已經存在的 key 對應的 value 值,并且后放入的 value 將會替代掉舊的 value。如果 key 不存在,該方法返回 null。
通過 get(key) 方法來取值,如果 key 在 map 集合中不存在,將返回 null 。
public class Main { public static void main(String[] args) { Student s = new Student("Xiao Ming", 99); Map<String, Student> map = new HashMap<>(); map.put("Xiao Ming", s); // 插入值,將"Xiao Ming"和Student實例映射并關聯(lián) Student target = map.get("Xiao Ming"); // 取值,通過key查找并返回映射的Student實例 System.out.println(target == s); // true,同一個實例 Student another = map.get("Bob"); // 未找到返回 null } } class Student { public String name; public int score; public Student(String name, int score) { this.name = name; this.score = score; } }
Map 集合可以重復放入key-value,但是一個key只能關聯(lián)一個value,后面放入的value會替代掉前面的value。如果放入的key已經存在,put()方法會返回被刪除的舊的value,否則,返回null。
在一個Map中,雖然key不能重復,但value是可以重復的
查詢key是否存在(containsKey(k)):如果只是想查詢某個key是否存在,可以調用boolean containsKey(K key)方法。
查詢value是否存在(containsValue(v))
根據(jù)key值移除元素(mapObj.remove(k))
3.2、遍歷 Map
Map和List不同,Map存儲的是key-value的映射關系,并且,它不保證順序。在遍歷的時候,遍歷的順序既不一定是put()時放入的key的順序,也不一定是key的排序順序。遍歷的時候,每個key會保證被遍歷一次且僅遍歷一次,但順序完全沒有保證,甚至對于不同的JDK版本,相同的代碼遍歷的輸出順序都是不同的!
HashMap 內部的 key 是無序的,TreeMap 的 key 是有序的。
3.2.1、遍歷所有的key(keySet())
要遍歷 Map 集合的 key 可以使用 for each 循環(huán)來遍歷 Map 實例的 keySet() 方法返回的 Set 集合,該 Set 集合包含不重復的key的集合:
Map<String, Integer> map = new HashMap<>(); map.put("apple", 123); map.put("pear", 456); map.put("banana", 789); for (String key : map.keySet()) { //keySet()方法返回一個不包含重復key的set集合 Integer value = map.get(key); System.out.println(key + " = " + value); }
3.2.2、直接遍歷key--value(entrySet())
同時遍歷key和value可以使用for each循環(huán)遍歷Map對象的entrySet()集合,它包含每一個key-value映射:
Map<String, Integer> map = new HashMap<>(); map.put("apple", 123); map.put("pear", 456); map.put("banana", 789); for (Map.Entry<String, Integer> entry : map.entrySet()) { String key = entry.getKey(); Integer value = entry.getValue(); System.out.println(key + " = " + value); }
3.3、覆寫equals()和hashcode()方法
正確使用 Map 必須保證:
-
作為
key的對象必須正確覆寫equals()方法,相等的兩個key實例調用 eauals() 必須返回 true,不同的key返回false -
作為
key的對象還必須正確覆寫hashCode()方法,且hashCode()方法要嚴格遵循以下規(guī)范:如果兩個對象相等,則兩個對象的hashCode()必須相等;如果兩個對象不相等,則兩個對象的hashCode()不要相等。
我們經常使用String作為key,因為String已經正確覆寫了equals()和hashCode()方法。
3.3.1、覆寫equals()方法
要想保證通過內容相同但不一定是同一個對象的 key 獲取到同一個 value 值,必須得保證作為 key 的對象已經正確地覆寫了 equals() 方法。
通過不是同一個對象,但是內容相同的 key 實例可以取到同一個 value 值,這是因為 Map 內部對 key 做比較是通過 equals 方法實現(xiàn)的,這一點和List查找元素需要正確覆寫equals()是一樣的,即正確使用Map必須保證:作為key的對象必須正確覆寫equals()方法。
我們經常使用String作為key,因為String已經正確覆寫了equals()方法。但如果我們放入的key是一個自己寫的類,就必須保證正確覆寫了equals()方法。
3.3.2、覆寫hashcode()方法
Map 集合是通過 key 來計算出 value 存儲的索引的,計算方式是調用了 key 對象的 hashcode() 方法,它返回一個 int 整數(shù),HashMap 正是通過這個方法直接定位key對應的value的索引,繼而直接返回value。
在正確實現(xiàn)equals()的基礎上,我們還需要正確實現(xiàn)hashCode(),代碼示例:
public class Person { String firstName; String lastName; int age; @Override int hashCode() { int h = 0; h = 31 * h + firstName.hashCode(); h = 31 * h + lastName.hashCode(); h = 31 * h + age; return h; } //為了避免firstName或lastName為null,代碼拋NullPointerException異常,hashCode方法可以這么寫 @Override int hashCode() { return Objects.hash(firstName, lastName, age); } }
equals()方法中用到的用于比較的每一個字段,都必須在hashCode()中用于計算;equals()中沒有使用到的字段,絕不可放在hashCode()中計算。
3.4、TreeMap(元素有序)
HashMap 內部的 key 是無序的,還有一種Map,它在內部會對Key進行排序,這種Map就是SortedMap。注意到SortedMap是接口,它的實現(xiàn)類是TreeMap。TreeMap不使用equals()和hashCode(),所以不需要覆寫equals和hashCode方法。

SortedMap保證遍歷時以Key的順序來進行排序。例如,放入的Key是"apple"、"pear"、"orange",遍歷的順序一定是"apple"、"orange"、"pear",因為String默認按字母排序:
使用TreeMap時,放入的Key必須實現(xiàn)Comparable接口。String、Integer這些類已經實現(xiàn)了Comparable接口,因此可以直接作為Key使用。
如果作為Key的class沒有實現(xiàn)Comparable接口,那么,必須在創(chuàng)建TreeMap時傳入Comparator即指定一個自定義排序算法:
import java.util.*; public class Main { public static void main(String[] args) { Map<Person, Integer> map = new TreeMap<>(new Comparator<Person>() { //在創(chuàng)建 TreeMap 時傳入一個排序算法 public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); } }); map.put(new Person("Tom"), 1); map.put(new Person("Bob"), 2); map.put(new Person("Lily"), 3); for (Person key : map.keySet()) { System.out.println(key); // {Person: Bob}, {Person: Lily}, {Person: Tom} } System.out.println(map.get(new Person("Bob"))); // 2 } } class Person { public String name; Person(String name) { this.name = name; } public String toString() { return "{Person: " + name + "}"; } } //比較的值是數(shù)字 Map<Student, Integer> map = new TreeMap<>(new Comparator<Student>() { public int compare(Student p1, Student p2) { if (p1.score == p2.score) { return 0; } return p1.score > p2.score ? -1 : 1; //或者可以直接借助Integer.compare(int, int)也可以返回正確的比較結果,就不用寫上面這么多判斷了 } });
4、Set 集合(元素不重復)
Set用于存儲不重復的元素集合,它主要提供以下幾個方法:
添加元素:boolean add(E e)- 刪除元素:
boolean remove(Object e) - 判斷是否包含元素:
boolean contains(Object e)
代碼示例:
Set<String> set = new HashSet<>(); System.out.println(set.add("abc")); // true System.out.println(set.add("abc")); // false,添加失敗,因為元素已存在,不可添加重復元素 System.out.println(set.contains("abc")); // true,元素存在 System.out.println(set.contains("xyz")); // false,元素不存在 System.out.println(set.remove("hello")); // false,刪除失敗,因為元素不存在 System.out.println(set.size()); // 1,一共1個元素
放入Set的元素和Map的key類似,都要正確實現(xiàn) equals() 和 hashCode()方法,否則該元素無法正確地放入 Set。
最常用的Set實現(xiàn)類是HashSet,實際上,HashSet僅僅是對HashMap的一個簡單封裝。
繼承關系:

4.1、HashSet
Set接口并不保證有序,而SortedSet接口則保證元素是有序的。HashSet 是無序的,它實現(xiàn)了Set接口,但沒有實現(xiàn)SortedSet接口;
當向一個 HashSet 集合存入一個元素時,HashSet 會調用該對象的 hashCode() 方法來得到該對象的 hashCode 值,然后根據(jù) hashCode值決定該元素在 HashSet 中的存儲位置。如果兩個元素的 equals 方法返回 true,但它們的 hashCode() 返回值不相等,那這兩個元素仍然可以添加成功,hashSet 會將它們存儲在不同的位置。
HashSet 集合判斷兩個元素相等的標準:兩個對象通過 equals() 方法比較返回 true,并且兩個對象的 hashCode() 方法返回值也相等。
如果兩個對象通過 equals() 方法返回 true,這兩個對象的 hashCode 值也應該相同。
HashSet 是無序的,可以存放 null。
public class Main { public static void main(String[] args) { Set<String> set = new HashSet<>(); set.add("apple"); set.add("banana"); set.add("pear"); set.add("orange"); for (String s : set) { System.out.println(s); //亂序輸出 banana apple pear ... } } }
4.2、TreeSet
TreeSet 是有序的,因為它實現(xiàn)了SortedSet接口。在遍歷TreeSet時,輸出就是有序的,順序按插入的元素ASCll碼排序。
public class Main { public static void main(String[] args) { Set<String> set = new TreeSet<>(); set.add("apple"); set.add("banana"); set.add("pear"); set.add("orange"); for (String s : set) { System.out.println(s); //輸出apple banana orange pear } } }
使用TreeSet和使用TreeMap的要求一樣,添加的元素必須正確實現(xiàn)Comparable接口,如果沒有實現(xiàn)Comparable接口,那么創(chuàng)建TreeSet時必須傳入一個Comparator對象。
5、迭代器的使用
迭代器是一種設計模式,它是一個對象,它可以遍歷并選擇序列中的對象,而開發(fā)人員不需要了解該序列的底層結構。迭代器通常被稱為“輕量級”對象,因為創(chuàng)建它的代價小。
Java中的Iterator功能比較簡單,并且只能單向移動:
- 使用 iterator() 方法會返回一個 Iterator 對象。第一次調用 Iterator 對象的 next() 方法時,它返回序列的第一個元素。(注意,iterator() 方法是 java.lang.Iterable接口,被 Collection 繼承)
- 使用 next() 獲得序列中的下一個元素
- 使用 hasNext() 檢查序列中是否還有元素
- 使用 remove() 將迭代器新返回的元素刪除
Iterator 是Java迭代器最簡單的實現(xiàn),為 List 設計的 ListIterator 具有更多的功能,它可以從兩個方向遍歷List,也可以從 List 中插入和刪除元素。
list l = new ArrayList(); l.add("aa"); l.add("bb"); for (Iterator iter = l.iterator(); iter.hasNext();) { String str = (String)iter.next(); System.out.println(str); } /*迭代器用于 while 循環(huán)遍歷 Iterator iter = l.iterator(); while(iter.hasNext()){ String str = (String) iter.next(); System.out.println(str); } */ /*迭代器用于 for each 循環(huán)遍歷 for(Object obj : iter) { System.out.println(obj); } */

浙公網安備 33010602011771號