本文總結java單列集合,單列集合頂層接口是Collection。剛接觸java集合時知道java集合可以裝載任意類型的對象,是一種功能強大的容器,逐漸學習後發現java集合框架層次明確,每個容器都有自己的特殊用途,學習時要掌握並理清集合的共性方法和每種容器的特殊方法。
集合
集合用於存儲對象,長度可變,存儲的對象元素類型可不一致。集合只能存儲對象,不能存儲基本數據類型。
java集合框架有2個頂層接口,Collection是單列集合的頂層接口,Map是雙列集合的頂層接口。
圖解:
1. 虛線框內的都是接口,實線框內的是類。
2. Map指向Collection的虛線代表Map雙列集合可以通過keySet()和entrySet()方法獲得鍵值和映射關係的單列集合。
3. Collection指向Iterator接口的虛線表示,Collection子類對象都可以通過iterator()方法獲取迭代器對象。
4. 集合框架有2個常用工具類,Collections和Arrays,裏面封裝了若干實用的靜態方法。
5. 集合體系是存儲對象的容器,之所以會出現圖中這麼多的容器,是因爲每一個容器對數據的存儲方式(也就是數據結構)都有不同。
Collection類中集合共性方法
1. 添加元素
void add(Object obj), 參數類型是Object, 以便於接收任意類型。
boolean addAll(Collection <? extends E> c), 將集合對象c整個添加到調用此方法的Collection對象中,如果調用此方法的Collection對象存儲的元素類型是E,那c可以是存儲E的任意子類對象的集合;如果調用此方法後,集合內容發生變更,返回true.
集合中存儲的是對象的引用地址。
2. 獲取集合長度
int size()
3. 打印集合
System.out.println(c).
4. 刪除元素
boolean remove(Object obj), 如果刪除了一個元素,返回true.
boolean removeAll(Collection<?> c), 集合中刪除指定集合c中的所有元素,如果調用此方法後集合內容變更,返回true.
boolean retain(Collection<?> c), 保留此集合與指定集合c的交集,無交集時,此集合變爲空,如果調用方法後此集合內容發生改變,返回true.
5. 清空集合
void clear()
6. 判斷元素
boolean contains(Object obj), 判斷集合中是否包含obj對象
boolean isEmpty(), 判斷集合是否爲空.
7. 遍歷集合
java將集合元素的共性取出方式(都是先判斷再取出),抽象爲Iterator接口。
每一個容器的數據結構不同,取出動作的細節也不一樣,所以將取出方式定義在集合內部,這樣可以直接訪問集合內部的元素。
每個集合子類中都有一個實現Iterator接口的內部類(非匿名),集合對外提供iterator()方法,iterator()方法返回一個此內部類的對象,也就是此集合的迭代器。
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class CollectionDemo1 {
private static ArrayList al2;
public static void main(String[] args) {
baseMethod();
}
public static void baseMethod(){
Collection al=new ArrayList();
//添加元素
al.add("day01");
al.add("day02");
al.add("day03");
al.add("day04");
//打印集合
sop("原集合:"+al);
//打印集合長度
sop("原集合長度:"+al.size());
//刪除元素
sop("是否刪除了day01元素:"+al.remove("day01"));//刪除了"day01",返回true
sop("是否刪除了day111元素:"+al.remove("day111"));//集合中沒有此元素,沒法刪除,原集合不變,返回false
//清空集合
//al.clear();
//判斷元素
sop("是否存在day02元素"+al.contains("day01"));
sop("集合是否爲空:"+al.isEmpty());
//獲取2個集合的交集元素
al2 = new ArrayList();
al2.add("day02");
al2.add("day03");
al2.add("day05");
al2.add("day06");
sop("原集合與a12是否有交集:"+al.retainAll(al2));
sop("原集合與a12的交集元素:"+al);
//集合元素迭代
for(Iterator it=al.iterator();it.hasNext();){
sop(it.next());
}
}
public static void sop(Object obj){
System.out.println(obj);
}
}
運行結果:
原集合:[day01, day02, day03, day04]
原集合長度:4
是否刪除了day01元素:true
是否刪除了day111元素:false
是否存在day02元素false
集合是否爲空:false
原集合與a12是否有交集:true
取交集後,al集合變爲:[day02, day03]
day02
day03
注:jdk1.5版本起,引入了泛型機制,編譯上述程序時會報“ 注意:CollectionDemo1.java使用了未經檢查或不安全的操作。注意:要了解詳細信息,請使得-xlint:unchecked重新編譯。”,是因爲上面程序中使用ArrayList集合時未指定泛型,存在安全隱患
Collection接口有2個子接口:List, Set.
List: 元素是有序的,元素可以重複,該集合體繫有索引。
Set; 元素無序,元素不可重複。
List集合
與Collection接口相比,凡是可以操作角標的方法都是該體系的特有的方法,元素角標從0開始。
1. 增加元素
boolean add(index,element), boolean addAll(index,collection)
2. 刪除元素
boolean remove(index)
3. 修改
E set(int index, E e), 用指定元素替換列表中指定位置的元素,返回替換前的元素。
4. 查詢
E get(int index)
List<E> subList(int from, int to)
5. 迭代器
Iterator iterator()和ListIterator listIterator():
ListIteratror對象是List集合特有的迭代器,只能通過listIterator()方法獲取,List集合中也還有iterator()方法,但調用此方法返回的是Iterator接口的直接內部子類對象。
Iterator迭代器的侷限性:在迭代時,不可以通過集合對象的方法操作集合中的元素,因爲迭代器與對象本身同時操作集合時會發生異常CurrentModificatonException, 所以在迭代時,只能用迭代器的方法操作元素,可是Iterator方法是有限的,只能對元素進行判斷、取出和刪除。
ListIterator解決了這個問題,ListIterator是Iterator的子接口,該迭代器中可以對元素進行添加、修改。
ListIterator迭代器中還可以逆向遍歷集合元素,使用方法hasPrevious()和previous()方法。
List有3個子類:ArrayList, LinkedList, Vector
ArrayList: 從jdk 1.2版本開始的,底層數據結構是數組,非線程同步的,特點是查詢速度快,但是增刪稍慢(元素多時影響大)。默認長度是10,超過時,先copy原數組,再創建新數組,50%擴展空間。
LinkedList: 從jdk1.2版本開始的,底層使用的是鏈表數據結構,非線程同步,特點是增刪速度快,查詢稍慢。
LinkedList的特有方法:
addFirst(), addLast(), 在鏈表頭或尾添加元素;
getFirst(), getLast(), 獲取元素,但不刪除元素;
removeFirst(), removeLast(), 獲取元素,並刪除元素,可用來遍歷刪鏈表;
---上面的get和remove方法,如果集合中沒有元素會拋出NoSuchElementException異常
offerFirst(), offerLast(), 在表頭或表尾插入元素;
peekFirst(), peekLast(), 獲取表頭或表尾元素,但不刪除元素;
pollFirst(), pollLast(), 獲取表頭或表尾元素,並刪除元素。
---上面幾個是jdk1.6版本纔有的方法,如果集合中沒有元素不會拋出異常,返回null.
Vector: 從jdk1.0版本開始的,先與集合框架存在, 底層是數組結構,線程同步,與ArrayList功能重複,被ArrayList替代了,需要判斷鎖,效率慢。默認長度是10,超過時100%擴展空間。
Set集合
元素是無序的,存入和取出的順序不一定一致,元素不可以重複。
Set集合常見子類-HashSet
HashSet底層數據結構是Hash表,非線程同步。
往HashSet集合中存儲元素時,是先調用元素的hashCode()判斷哈希值大小,哈希值一樣時,再調用元素的equals()方法判斷元素內容。如果2個對象哈希值一樣,內容不一樣,會在一連續地址值中順沿存放2個元素。如果哈希值不一樣,不會再調用equals()方法。
hashCode()是Object類的方法,返回對象的哈希值,可被複寫;Object類對象的哈希值就是地址值,子類複寫後就不一定是地址值了。
HashSet具體存儲原理-哈希表
想查找一個集合中是否包含有某個對象,通常想到的是逐一取出集合中每個元素與要查找的元素使用equals()方法進行比較, 如果集合中有一萬個元素,這種方法效率很低。
哈希算法可以大大提高在集合中查找元素的效率。這種算法採用對某個數字n進行取餘的方式將元素哈希值進行劃分,將集合分成若干存儲區域,根據一個對象的哈希值就可以確定該對象應該存儲在哪個區域,確定存儲區域後,只需取出該區域的各個元素與要查找的對象使用equals方法進行比較即可,不用遍歷集合中所有元素,從而提高了效率。
對象要存儲在HashSet集合中,最好是複寫hashCode()方法和equals()方法。HashSet集合的方法contains()和remove(),執行原理也是依賴這2個方法(List集合只依賴equals()方法)。
注:當一個對象被存儲進HashSet集合以後,就不能修改這個對象中那些參與計算哈希值的字段了,否則,對象修改後的哈希值與最初存儲進HashSet集合中的哈希值就不同了,這樣,使用contains方法在HashSet中檢索該對象時,就會找不到這個對象,也就無法單獨刪除此對象,造成內存泄露(java雖然的內存回收機制,但仍有內存泄露,這就是一個例子)。
Set集合常見子類-TreeSet
TreeSet可以對集合中元素按照元素自然順序(默認順序)進行排序,或者根據創建 TreeSet 時提供的Comparator比較器進行排序,底層數據結構是二叉樹。TreeSet集合中的add(),contains()和remove()方法依據元素的自然順序或TreeSet集合自身的Comparator比較器。
TreeSet排序的第1種方式:讓元素自身具備比較性,元素需要實現Compareable接口,並覆蓋compareTo()方法。
TreeSet排序的第2種方式:讓集合自身具備比較性,當元素不具備比較性,或具備的比較性不是所需要的,可以使用此方式,集合的比較性會覆蓋元素的比較性。
具體是定義比較器類,將該比較器類對象作爲參數傳遞給TreeSet()的構造函數,比較器類需實現Comparator()接口,並覆蓋compare(obj1,obj2)方法。
下面是一個用第一種方式解決IP地址排序的例子:
/*
需求:用TreeSet實現IP地址排序,用第1種方式實現,IP地址封裝成IPAdress類,讓IPAdress本身具有比較性
*/
import java.util.Iterator;
import java.util.TreeSet;
public class IpTest {
public static void main(String[] args) {
TreeSet ts=new TreeSet();
ts.add(new IpAdress("61.54.231.245"));
ts.add(new IpAdress("61.54.231.9"));
ts.add(new IpAdress("61.54.231.246"));
ts.add(new IpAdress("61.54.231.48"));
ts.add(new IpAdress("61.53.231.249"));
Iterator it=ts.iterator();
while(it.hasNext()){
IpAdress ip=(IpAdress)it.next();
ip.Show();
}
}
}
class IpAdress implements Comparable{
private String adress;
public IpAdress(String adress){
this.adress=adress;
}
public int compareTo(Object obj) {
if(!(obj instanceof IpAdress)){
throw new RuntimeException("不是IP地址");
}
IpAdress p=(IpAdress)obj;
String[] sp1=this.adress.split("\\.");
String[] sp2=p.adress.split("\\.");
for(int i=0;i<sp1.length;i++){
int x=Integer.parseInt(sp1[i])-Integer.parseInt(sp2[i]);
if(x!=0){
return x;
}
}
return 0;
}
public void Show(){
System.out.println(adress);
}
}
運行結果就是:
61.53.231.249
61.54.231.9
61.54.231.48
61.54.231.245
61.54.231.246