以下是《瘋狂Java講義》中的一些知識,如有錯誤,煩請指正。
集合概述
Java集合可以分為Set、List、Map、Queue四種體系。Set是無序集合、List是有序集合、Queue是隊列實現,Map保存映射關系。Java的集合類主要由兩個接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口。
集合類:為了保存數量不確定的數據以及保存具有映射關系的數據。集合里只能保存對象,不能保存基本類型。集合類主要負責保存、盛裝其他數據,因此集合類也被稱為容器類。所有集合類都位于java.util包下。
派生接口:Collection和Map
Collection和Iterator接口
Collection是List、Set、Queue接口的父接口。
import java.util.*;
public class CollectionTest
{
public static void main(String[] args)
{
Collection c = new ArrayList();
// 添加元素
c.add("孫悟空");
// 雖然集合里不能放基本類型的值,但Java支持自動裝箱
c.add(6);
System.out.println("c集合的元素個數為:" + c.size()); // 輸出2
// 刪除指定元素
c.remove(6);
System.out.println("c集合的元素個數為:" + c.size()); // 輸出1
// 判斷是否包含指定字符串
System.out.println("c集合的是否包含\"孫悟空\"字符串:"
+ c.contains("孫悟空")); // 輸出true
c.add("輕量級Java EE企業應用實戰");
System.out.println("c集合的元素:" + c);
Collection books = new HashSet();
books.add("輕量級Java EE企業應用實戰");
books.add("瘋狂Java講義");
System.out.println("c集合是否完全包含books集合?"
+ c.containsAll(books)); // 輸出false
// 用c集合減去books集合里的元素
c.removeAll(books);
System.out.println("c集合的元素:" + c);
// 刪除c集合里所有元素
c.clear();
System.out.println("c集合的元素:" + c);
// 控制books集合里只剩下c集合里也包含的元素
books.retainAll(c);
System.out.println("books集合的元素:" + books);
}
}
import java.util.*;
public class ForeachTest
{
public static void main(String[] args)
{
// 創建集合、添加元素的代碼與前一個程序相同
Collection books = new HashSet();
books.add(new String("輕量級Java EE企業應用實戰"));
books.add(new String("瘋狂Java講義"));
books.add(new String("瘋狂Android講義"));
for (Object obj : books)
{
// 此處的book變量也不是集合元素本身
String book = (String)obj;
System.out.println(book);
if (book.equals("瘋狂Android講義"))
{
// 下面代碼會引發ConcurrentModificationException異常
books.remove(book); //①
}
}
System.out.println(books);
}
}
Iterator也是Java集合框架的成員,主要用于遍歷,Iterable接口是Collection接口的父接口。
import java.util.*;
public class CollectionEach
{
public static void main(String[] args)
{
// 創建一個集合
Collection books = new HashSet();
books.add("輕量級Java EE企業應用實戰");
books.add("瘋狂Java講義");
books.add("瘋狂Android講義");
// 調用forEach()方法遍歷集合,Collection集合直接調用父接口的默認方法
// foreach 方法所需參數類型是一個函數型接口
books.forEach(obj -> System.out.println("迭代集合元素:" + obj));
}
}
import java.util.*;
public class IteratorTest
{
public static void main(String[] args)
{
// 創建集合、添加元素的代碼與前一個程序相同
Collection books = new HashSet();
books.add("輕量級Java EE企業應用實戰");
books.add("瘋狂Java講義");
books.add("瘋狂Android講義");
// 獲取books集合對應的迭代器
Iterator it = books.iterator();
while(it.hasNext())
{
// it.next()方法返回的數據類型是Object類型,因此需要強制類型轉換
String book = (String)it.next();
System.out.println(book);
if (book.equals("瘋狂Java講義"))
{
// 從集合中刪除上一次next方法返回的元素
it.remove();
}
// 對book變量賦值,不會改變集合元素本身
book = "測試字符串";
}
System.out.println(books);
}
}
當使用Iterator迭代訪問Collection集合元素時,Collection集合里的元素不能被改變,只有通過Iterator的remove方法刪除上一次返回的集合元素才可以,否則引發java.util.ConcurrentModificationException異常
import java.util.*;
public class IteratorErrorTest
{
public static void main(String[] args)
{
// 創建集合、添加元素的代碼與前一個程序相同
Collection books = new HashSet();
books.add("輕量級Java EE企業應用實戰");
books.add("瘋狂Java講義");
books.add("瘋狂Android講義");
// 獲取books集合對應的迭代器
Iterator it = books.iterator();
while(it.hasNext())
{
String book = (String)it.next();
System.out.println(book);
if (book.equals("瘋狂Android講義"))
{
// 使用Iterator迭代過程中,不可修改集合元素,下面代碼引發異常
books.remove(book);
}
}
}
}
迭代時檢測到集合被修改將會引發異常,只有刪除集合的某個特定元素才不會引發異常。
lambda表達式遍歷Iterator
java8提供的forEachRemaining參數同樣也是函數式接口。
import java.util.*;
public class IteratorEach
{
public static void main(String[] args)
{
// 創建集合、添加元素的代碼與前一個程序相同
Collection books = new HashSet();
books.add("輕量級Java EE企業應用實戰");
books.add("瘋狂Java講義");
books.add("瘋狂Android講義");
// 獲取books集合對應的迭代器
Iterator it = books.iterator();
// 使用Lambda表達式(目標類型是Comsumer)來遍歷集合元素
it.forEachRemaining(obj -> System.out.println("迭代集合元素:" + obj));
}
}
使用foreach循環遍歷集合元素
同樣當使用foreach迭代訪問集合元素時,該集合不能被改變。
import java.util.*;
public class ForeachTest
{
public static void main(String[] args)
{
// 創建集合、添加元素的代碼與前一個程序相同
Collection books = new HashSet();
books.add(new String("輕量級Java EE企業應用實戰"));
books.add(new String("瘋狂Java講義"));
books.add(new String("瘋狂Android講義"));
for (Object obj : books)
{
// 此處的book變量也不是集合元素本身
String book = (String)obj;
System.out.println(book);
if (book.equals("瘋狂Android講義"))
{
// 下面代碼會引發ConcurrentModificationException異常
books.remove(book);
}
}
System.out.println(books);
}
}
使用Predicate操作集合
Java8新增了一個removeIf(Predicate filter)方法,該方法將刪除符合filter條件的所有元素。Predicate也是函數式接口,因此可以使用Lambda表達式作為參數。
import java.util.*;
import java.util.function.*;
public class PredicateTest
{
public static void main(String[] args)
{
// 創建一個集合
Collection books = new HashSet();
books.add(new String("輕量級Java EE企業應用實戰"));
books.add(new String("瘋狂Java講義"));
books.add(new String("瘋狂iOS講義"));
books.add(new String("瘋狂Ajax講義"));
books.add(new String("瘋狂Android講義"));
// 使用Lambda表達式(目標類型是Predicate)過濾集合
books.removeIf(ele -> ((String)ele).length() < 10);
System.out.println(books);
}
}
使用Predicate可以簡化集合的運算。callAll方法只統計滿足Predicate條件的圖書。
import java.util.*;
import java.util.function.*;
public class PredicateTest2
{
public static void main(String[] args)
{
// 創建books集合、為books集合添加元素的代碼與前一個程序相同。
Collection books = new HashSet();
books.add(new String("輕量級Java EE企業應用實戰"));
books.add(new String("瘋狂Java講義"));
books.add(new String("瘋狂iOS講義"));
books.add(new String("瘋狂Ajax講義"));
books.add(new String("瘋狂Android講義"));
// 統計書名包含“瘋狂”子串的圖書數量
System.out.println(calAll(books , ele->((String)ele).contains("瘋狂")));
// 統計書名包含“Java”子串的圖書數量
System.out.println(calAll(books , ele->((String)ele).contains("Java")));
// 統計書名字符串長度大于10的圖書數量
System.out.println(calAll(books , ele->((String)ele).length() > 10));
}
public static int calAll(Collection books , Predicate p)
{
int total = 0;
for (Object obj : books)
{
// 使用Predicate的test()方法判斷該對象是否滿足Predicate指定的條件
if (p.test(obj))
{
total ++;
}
}
return total;
}
}
Java 8新增的Stream操作
Java 8還新增了Stream、IntStream、LongStream、DoubleStream等流式API。
獨立使用Stream的步驟如下:
- 使用Stream或XxxStream的builder()類方法創建該Stream對應Builder。
- 重復調用Builder的add()方法向該流中添加多個元素。
- 調用Builder的build()方法獲取對應的Stream。
- 調用Stream的聚集方法。
import java.util.stream.*;
public class IntStreamTest
{
public static void main(String[] args)
{
IntStream is = IntStream.builder()
.add(20)
.add(13)
.add(-2)
.add(18)
.build();
// 下面調用聚集方法的代碼每次只能執行一個
System.out.println("is所有元素的最大值:" + is.max().getAsInt());
System.out.println("is所有元素的最小值:" + is.min().getAsInt());
System.out.println("is所有元素的總和:" + is.sum());
System.out.println("is所有元素的總數:" + is.count());
System.out.println("is所有元素的平均值:" + is.average());
System.out.println("is所有元素的平方是否都大于20:"
+ is.allMatch(ele -> ele * ele > 20));
System.out.println("is是否包含任何元素的平方大于20:"
+ is.anyMatch(ele -> ele * ele > 20));
// 將is映射成一個新Stream,新Stream的每個元素是原Stream元素的2倍+1
IntStream newIs = is.map(ele -> ele * 2 + 1);
// 使用方法引用的方式來遍歷集合元素
newIs.forEach(System.out::println); // 輸出41 27 -3 37
}
}
Collection接口提供了一個stream()默認方法,該方法可返回該集合對應的流,接下來即可通過流API來操作集合元素。由于Stream可以對集合元素進行整體的聚集操作,因此Stream極大了豐富了集合的功能。
import java.util.*;
import java.util.function.*;
public class CollectionStream
{
public static void main(String[] args)
{
// 創建books集合、為books集合添加元素的代碼與8.2.5小節的程序相同。
Collection books = new HashSet();
books.add(new String("輕量級Java EE企業應用實戰"));
books.add(new String("瘋狂Java講義"));
books.add(new String("瘋狂iOS講義"));
books.add(new String("瘋狂Ajax講義"));
books.add(new String("瘋狂Android講義"));
// 統計書名包含“瘋狂”子串的圖書數量
System.out.println(books.stream()
.filter(ele->((String)ele).contains("瘋狂"))
.count()); // 輸出4
// 統計書名包含“Java”子串的圖書數量
System.out.println(books.stream()
.filter(ele->((String)ele).contains("Java") )
.count()); // 輸出2
// 統計書名字符串長度大于10的圖書數量
System.out.println(books.stream()
.filter(ele->((String)ele).length() > 10)
.count()); // 輸出2
// 先調用Collection對象的stream()方法將集合轉換為Stream,
// 再調用Stream的mapToInt()方法獲取原有的Stream對應的IntStream
books.stream().mapToInt(ele -> ((String)ele).length())
// 調用forEach()方法遍歷IntStream中每個元素
.forEach(System.out::println);// 輸出8 11 16 7 8
}
}
Set集合
Set集合不允許包含相同的元素,如果試圖把兩個相同的元素加入同一個Set集合中,添加操作失敗,add方法返回false,且新元素不會被加入。
HashSet類是Set接口的典型實現。HashSet按Hash算法來存儲集合中的元素,因此具有很好的存取和查找性能。當向HashSet集合中存入一個元素時,HashSet會調用該對象的hashCode()方法來得到該對象的hashCode值,然后根據該HashCode值來決定該對象在HashSet中存儲位置。如果有兩個元素通過equals方法比較返回true,但它們的hashCode()方法返回值不相等,HashSet將會把它們存儲在不同位置,也就可以添加成功。
特點:無序、非同步、可以包含null。
HashSet判斷元素相等:1.equals方法比較相等;2.hashCode()方法返回值相等。所以如果重寫equals方法,需要重寫hashCode方法。
import java.util.*;
// 類A的equals方法總是返回true,但沒有重寫其hashCode()方法
class A
{
public boolean equals(Object obj)
{
return true;
}
}
// 類B的hashCode()方法總是返回1,但沒有重寫其equals()方法
class B
{
public int hashCode()
{
return 1;
}
}
// 類C的hashCode()方法總是返回2,且重寫其equals()方法總是返回true
class C
{
public int hashCode()
{
return 2;
}
public boolean equals(Object obj)
{
return true;
}
}
public class HashSetTest
{
public static void main(String[] args)
{
HashSet books = new HashSet();
// 分別向books集合中添加兩個A對象,兩個B對象,兩個C對象
books.add(new A());
books.add(new A());
books.add(new B());
books.add(new B());
books.add(new C());
books.add(new C());
System.out.println(books);//輸出2A,2B,1C
}
}
重寫hashCode的基本規則:
- 同一個對象多次調用hashCode方法應該返回相同的值
- 兩個對象equal方法返回true。則hashCode應返回相等的值
- 用作equal方法比較標準的實例變量,都應該用于計算hashCode值。
程序把可變對象添加到HashSet中之后,盡量不要修改參與運算的hashCode()、equals()的實例變量,否則會導致HashSet無法正確操作這些集合元素。因為對象的Hashcode改變了,實際存儲的位置并沒有變化,無法對其進行準確的索引。
import java.util.*;
class R
{
int count;
public R(int count)
{
this.count = count;
}
public String toString()
{
return "R[count:" + count + "]";
}
public boolean equals(Object obj)
{
if(this == obj)
return true;
if (obj != null && obj.getClass() == R.class)
{
R r = (R)obj;
return this.count == r.count;
}
return false;
}
public int hashCode()
{
return this.count;
}
}
public class HashSetTest2
{
public static void main(String[] args)
{
HashSet hs = new HashSet();
hs.add(new R(5));
hs.add(new R(-3));
hs.add(new R(9));
hs.add(new R(-2));
// 打印HashSet集合,集合元素沒有重復
System.out.println(hs);
// 取出第一個元素
Iterator it = hs.iterator();
R first = (R)it.next();
// 為第一個元素的count實例變量賦值
first.count = -3;
// 再次輸出HashSet集合,集合元素有重復元素
System.out.println(hs);
// 刪除count為-3的R對象
hs.remove(new R(-3));
// 可以看到被刪除了一個R元素
System.out.println(hs);
System.out.println("hs是否包含count為-3的R對象?"
+ hs.contains(new R(-3))); // 輸出false
System.out.println("hs是否包含count為-2的R對象?"
+ hs.contains(new R(-2))); // 輸出false
}
}
LinkedHashSet
LinkedHashSet根據元素的hashCode來決定元素的存儲位置,但同時使用鏈表維護元素的次序。當遍歷LinkedHashSet集合里的元素時,將會按照元素的添加順序來訪問集合里的元素。因為需要維護元素的插入順序,性能略低于HashSet的性能,但在迭代訪問Set里的全部元素時有很好的性能。
import java.util.*;
public class LinkedHashSetTest
{
public static void main(String[] args)
{
LinkedHashSet books = new LinkedHashSet();
books.add("瘋狂Java講義");
books.add("輕量級Java EE企業應用實戰");
System.out.println(books);//與添加順序一致
// 刪除 瘋狂Java講義
books.remove("瘋狂Java講義");
// 重新添加 瘋狂Java講義
books.add("瘋狂Java講義");
System.out.println(books);
}
}
TreeSet
TreeSet是SortedSet接口的實現類,可以確保集合元素處于排序狀態。
import java.util.*;
public class TreeSetTest
{
public static void main(String[] args)
{
TreeSet nums = new TreeSet();
// 向TreeSet中添加四個Integer對象
nums.add(5);
nums.add(2);
nums.add(10);
nums.add(-9);
// 輸出集合元素,看到集合元素已經處于排序狀態
System.out.println(nums);
// 輸出集合里的第一個元素
System.out.println(nums.first()); // 輸出-9
// 輸出集合里的最后一個元素
System.out.println(nums.last()); // 輸出10
// 返回小于4的子集,不包含4
System.out.println(nums.headSet(4)); // 輸出[-9, 2]
// 返回大于5的子集,如果Set中包含5,子集中還包含5
System.out.println(nums.tailSet(5)); // 輸出 [5, 10]
// 返回大于等于-3,小于4的子集。
System.out.println(nums.subSet(-3 , 4)); // 輸出[2]
}
}
TreeSet采用紅黑樹的數據結構對元素進行排序。TreeSet支持兩種排序方法:自然排序和定制排序。
1. 自然排序
TreeSet會調用集合元素的compareTo(Object obj)方法來比較元素之間的大小關系,然后將集合元素按照升序排列。如果試圖把一個對象添加到TreeSet時,則該對象必須實現Compareable接口的compareTo(Object obj)方法,否則拋出異常。
import java.util.*;
class Err{}
public class TreeSetErrorTest
{
public static void main(String[] args)
{
TreeSet ts = new TreeSet();
// 向TreeSet集合中添加兩個Err對象
ts.add(new Err());
ts.add(new Err()); //Err沒有實現compareTo方法,引發ClassCastException
}
}
大部分類在實現CompareTo方法時,需要將被比較對象強制轉換成相同類型。因此向TreeSet中添加的應該是同一個類的對象,否則引發ClassCastException。
import java.util.*;
public class TreeSetErrorTest2
{
public static void main(String[] args)
{
TreeSet ts = new TreeSet();
// 向TreeSet集合中添加兩個對象
ts.add(new String("瘋狂Java講義"));
ts.add(new Date()); // 會引發異常
}
}
如果希望TreeSet能正常運作,TreeSet只能添加同一種類型的對象。
TreeSet判斷對象相等:通過compareTo方法比較是否返回0
import java.util.*;
class Z implements Comparable
{
int age;
public Z(int age)
{
this.age = age;
}
// 重寫equals()方法,總是返回true
public boolean equals(Object obj)
{
return true;
}
// 重寫了compareTo(Object obj)方法,總是返回1,總是認為不相等
public int compareTo(Object obj)
{
return 1;
}
}
public class TreeSetTest2
{
public static void main(String[] args)
{
TreeSet set = new TreeSet();
Z z1 = new Z(6);
set.add(z1);
// 第二次添加同一個對象,輸出true,表明添加成功
System.out.println(set.add(z1)); //true
// 下面輸出set集合,將看到有兩個元素
System.out.println(set);
// 修改set集合的第一個元素的age變量
((Z)(set.first())).age = 9;
// 輸出set集合的最后一個元素的age變量,將看到也變成了9
System.out.println(((Z)(set.last())).age);
}
}
注意equals方法應該和compareTo方法有一致的結果。
一旦改變了TreeSet集合里可變元素的實例變量,導致它的與其他對象的大小順序發生改變,但TreeSet不會調整他們的 順序,可能導致compareTo返回0。當試圖刪除該對象時,刪除會失敗,可以刪除沒有被修改的實例變量、切不與被修改的實例變量重復的變量。執行一次成功刪除之后TreeSet會對集合中的元素重新索引,接下來就可以刪除TreeSet中的所有元素了。
import java.util.*;
class R implements Comparable
{
int count;
public R(int count)
{
this.count = count;
}
public String toString()
{
return "R[count:" + count + "]";
}
// 重寫equals方法,根據count來判斷是否相等
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if(obj != null && obj.getClass() == R.class)
{
R r = (R)obj;
return r.count == this.count;
}
return false;
}
// 重寫compareTo方法,根據count來比較大小
public int compareTo(Object obj)
{
R r = (R)obj;
return count > r.count ? 1 :
count < r.count ? -1 : 0;
}
}
public class TreeSetTest3
{
public static void main(String[] args)
{
TreeSet ts = new TreeSet();
ts.add(new R(5));
ts.add(new R(-3));
ts.add(new R(9));
ts.add(new R(-2));
// 打印TreeSet集合,集合元素是有序排列的
System.out.println(ts); // -3 -2 5 9
// 取出第一個元素
R first = (R)ts.first();
// 對第一個元素的count賦值
first.count = 20;
// 取出最后一個元素
R last = (R)ts.last();
// 對最后一個元素的count賦值,與第二個元素的count相同
last.count = -2;
// 再次輸出將看到TreeSet里的元素處于無序狀態,且有重復元素
System.out.println(ts); // 20 -2 5 -2
// 刪除實例變量被改變的元素,刪除失敗
System.out.println(ts.remove(new R(-2))); // false
System.out.println(ts);//20 -2 5 -2
// 刪除實例變量沒有被改變的元素,刪除成功
System.out.println(ts.remove(new R(5))); // true,之后會重新索引,可以刪除所有元素了
System.out.println(ts);// 20 -2 -2
}
}
2. 定制排序
如果需要實現定制排序,例如降序,需要在創建TreeSet集合對象時,提供一個Comparator對象與該TreeSet集合關聯,由該Comparator對象負責集合元素的排序邏輯。可以用Lambda表達式代替Comparator對象,這時候無需實現Comparable接口。
import java.util.*;
class M
{
int age;
public M(int age)
{
this.age = age;
}
public String toString()
{
return "M[age:" + age + "]";
}
}
public class TreeSetTest4
{
public static void main(String[] args)
{
// 此處Lambda表達式的目標類型是Comparator
TreeSet ts = new TreeSet((o1 , o2) ->
{
M m1 = (M)o1;
M m2 = (M)o2;
// 根據M對象的age屬性來決定大小,age越大,M對象反而越小
return m1.age > m2.age ? -1
: m1.age < m2.age ? 1 : 0;
});
ts.add(new M(5));
ts.add(new M(-3));
ts.add(new M(9));
System.out.println(ts);
}
}
List集合
List集合代表一個元素有序可重復的集合,集合中每個元素都有其對應的順序索引。List作為Collection接口的子接口,與Set集合相比多了根據索引插入、替換、刪除集合元素的方法。
import java.util.*;
public class ListTest
{
public static void main(String[] args)
{
List books = new ArrayList();
// 向books集合中添加三個元素
books.add(new String("輕量級Java EE企業應用實戰"));
books.add(new String("瘋狂Java講義"));
books.add(new String("瘋狂Android講義"));
System.out.println(books);
// 將新字符串對象插入在第二個位置
books.add(1 , new String("瘋狂Ajax講義"));
for (int i = 0 ; i < books.size() ; i++ )
{
System.out.println(books.get(i));
}
// 刪除第三個元素
books.remove(2);
System.out.println(books);
// 判斷指定元素在List集合中位置:輸出1,表明位于第二位
System.out.println(books.indexOf(new String("瘋狂Ajax講義"))); //①
//將第二個元素替換成新的字符串對象
books.set(1, new String("瘋狂Java講義"));
System.out.println(books);
//將books集合的第二個元素(包括)
//到第三個元素(不包括)截取成子集合
System.out.println(books.subList(1 , 2));
}
}
List判斷對象相等:equals方法返回true即可。
import java.util.*;
class A
{
public boolean equals(Object obj)
{
return true;
}
}
public class ListTest2
{
public static void main(String[] args)
{
List books = new ArrayList();
books.add(new String("輕量級Java EE企業應用實戰"));
books.add(new String("瘋狂Java講義"));
books.add(new String("瘋狂Android講義"));
System.out.println(books);
// 刪除集合中A對象,將導致第一個元素被刪除
books.remove(new A()); // ①
System.out.println(books);
// 刪除集合中A對象,再次刪除集合中第一個元素
books.remove(new A()); // ②
System.out.println(books);
}
}
List常用的默認方法sort()需要一個Comparator對象控制秩序,可使用Lambda表達式作為參數;默認方法replaceAll()需要一個函數式接口的UnaryOperator來替換所有集合元素,也可用Lambda表達式作為參數。
import java.util.*;
public class ListTest3
{
public static void main(String[] args)
{
List books = new ArrayList();
// 向books集合中添加4個元素
books.add(new String("輕量級Java EE企業應用實戰"));
books.add(new String("瘋狂Java講義"));
books.add(new String("瘋狂Android講義"));
books.add(new String("瘋狂iOS講義"));
// 使用目標類型為Comparator的Lambda表達式對List集合排序
books.sort((o1, o2)->((String)o1).length() - ((String)o2).length());
System.out.println(books);
// 使用目標類型為UnaryOperator的Lambda表達式來替換集合中所有元素
// 該Lambda表達式控制使用每個字符串的長度作為新的集合元素
books.replaceAll(ele->((String)ele).length());
System.out.println(books); // 輸出[7, 8, 11, 16]
}
}
Set只提供一個iterator()方法,List提供了listIterator()方法,該方法返回一個ListIterator對象,ListIterator接口繼承了Iterator接口。
import java.util.*;
public class ListIteratorTest
{
public static void main(String[] args)
{
String[] books = {
"瘋狂Java講義", "瘋狂iOS講義",
"輕量級Java EE企業應用實戰"
};
List bookList = new ArrayList();
for (int i = 0; i < books.length ; i++ )
{
bookList.add(books[i]);
}
ListIterator lit = bookList.listIterator();
while (lit.hasNext())
{
System.out.println(lit.next());
lit.add("-------分隔符-------");
}
System.out.println("=======下面開始反向迭代=======");
while(lit.hasPrevious())
{
System.out.println(lit.previous());
}
}
}
ArrayList和Vector
兩個類都是基于數組實現的List類,所以都封裝了動態的可以再分配的Object[]數組。ArrayList對象和Vector對象使用initialCapacity參數來設置數組的長度。添加元素超出數組長度時,initialCapacity會自動增加。
Vector提供了一個子類Stack,這也是一個古老的集合,線程安全,性能較差,盡量少用。
區別在于:ArrayList時線程不安全的,Vector是線程安全的。所以Vector性能比ArrayList性能要低。
Vector提供了Stack子類,用于模擬后進先出(LIFO)的“棧”這種數據結構。同樣線程安全、性能較差。如果需要實現“棧”,可以使用ArrayDeque。
固定長度的List
介紹數組時的Arrays類提供了asList(Object a)方法,該方法可以把一個數組或者指定個數對象轉換成一個List集合,這個List集合既不是ArrayList實現類的實例,也不是Vector實現類的實例,而是Arrays的內部類ArrayList的實例。Arrays.ArrayList是一個固定長度的List集合,程序只能遍歷訪問該集合里的元素,不可增加、刪除該集合里的元素。
import java.util.*;
public class FixedSizeList
{
public static void main(String[] args)
{
List fixedList = Arrays.asList("瘋狂Java講義"
, "輕量級Java EE企業應用實戰");
// 獲取fixedList的實現類,將輸出Arrays$ArrayList
System.out.println(fixedList.getClass());
// 使用方法引用遍歷集合元素
fixedList.forEach(System.out::println);
// 試圖增加、刪除元素都會引發UnsupportedOperationException異常
fixedList.add("瘋狂Android講義");
fixedList.remove("瘋狂Java講義");
}
}
Queue集合
Queue用于模擬“先進先出”(FIFO)隊列這種數據結構,通常隊列不允許隨機訪問隊列中的元素。Queue接口有一個PriorityQueue實現類和一個Deque接口,后者既可以當成隊列使用,也可以當棧使用。
PriorityQueue實現類
比較標準的隊列實現類--保存隊列元素的順序按照隊列元素的大小重新排列。這是違反隊列的先進先出的原則的
import java.util.*;
public class PriorityQueueTest
{
public static void main(String[] args)
{
PriorityQueue pq = new PriorityQueue();
// 下面代碼依次向pq中加入四個元素
pq.offer(6);
pq.offer(-3);
pq.offer(20);
pq.offer(18);
// 輸出pq隊列,并不是按元素的加入順序排列
System.out.println(pq); // 輸出[-3, 6, 20, 18]
// 訪問隊列第一個元素,其實就是隊列中最小的元素:-3
System.out.println(pq.poll());
}
}
沒有從小到大排序是因為受了toString方法返回值的影響。如果是使用poll()方法,則嚴格從小到大。
PriorityQueue兩種排序方式:自然排序、定制排序,詳細參考TreeSet。
Deque接口和ArrayDeque實現類
Deque接口代表一個雙端隊列,還可以當成棧使用。Deque接口提供了典型的實現類ArrayDeque,這是基于數組實現的雙端隊列。
ArrayDeque可作為“棧”,也可作為“隊列”
注意:ArrayList和ArrayDeque兩個集合類的實現機制基本相似,底層都采用一個動態的、可重分配的Object[]數組來存儲集合元素,當集合元素超出了數組的容量時,系統會在底層重新分配一個Object[]數組來存儲元素。
ArrayDeque作為棧:
import java.util.*;
public class ArrayDequeStack
{
public static void main(String[] args)
{
ArrayDeque stack = new ArrayDeque();
// 依次將三個元素push入"棧"
stack.push("瘋狂Java講義");
stack.push("輕量級Java EE企業應用實戰");
stack.push("瘋狂Android講義");
// 輸出:[瘋狂Android講義, 輕量級Java EE企業應用實戰, 瘋狂Java講義]
System.out.println(stack);
// 訪問第一個元素,但并不將其pop出"棧",輸出:瘋狂Android講義
System.out.println(stack.peek());
// 依然輸出:[瘋狂Android講義, 瘋狂Java講義, 輕量級Java EE企業應用實戰]
System.out.println(stack);
// pop出第一個元素,輸出:瘋狂Android講義
System.out.println(stack.pop());
// 輸出:[輕量級Java EE企業應用實戰, 瘋狂Java講義]
System.out.println(stack);
}
}
ArrayDeque作為隊列
import java.util.*;
public class ArrayDequeQueue
{
public static void main(String[] args)
{
ArrayDeque queue = new ArrayDeque();
// 依次將三個元素加入隊列
queue.offer("瘋狂Java講義");
queue.offer("輕量級Java EE企業應用實戰");
queue.offer("瘋狂Android講義");
// 輸出:[瘋狂Java講義, 輕量級Java EE企業應用實戰, 瘋狂Android講義]
System.out.println(queue);
// 訪問隊列頭部的元素,但并不將其poll出隊列"棧",輸出:瘋狂Java講義
System.out.println(queue.peek());
// 依然輸出:[瘋狂Java講義, 輕量級Java EE企業應用實戰, 瘋狂Android講義]
System.out.println(queue);
// poll出第一個元素,輸出:瘋狂Java講義
System.out.println(queue.poll());
// 輸出:[輕量級Java EE企業應用實戰, 瘋狂Android講義]
System.out.println(queue);
}
}
LinkedList實現類
這是一個List接口的實現類,所以可以根據索引來隨機訪問集合中的元素。除此之外還實現了Deque接口,可作為雙端隊列。雙端隊列棧的頭部和尾部分別對應隊列的頭部和尾部。
import java.util.*;
public class LinkedListTest
{
public static void main(String[] args)
{
LinkedList books = new LinkedList();
// 將字符串元素加入隊列的尾部
books.offer("book1");
books.offer("boook2");
// 將一個字符串元素加入棧的頂部
books.push("book3");
books.push("book4");
// 將字符串元素添加到隊列的頭部(相當于棧的頂部)
books.offerFirst("book5");
books.offerFirst("book6");
// 以List的方式(按索引訪問的方式)來遍歷集合元素
for (int i = 0; i < books.size() ; i++ )
{
System.out.println("遍歷中:" + books.get(i));
}
// 訪問、并不刪除棧頂的元素 book6
System.out.println(books.peekFirst());
// 訪問、并不刪除隊列的最后一個元素 book2
System.out.println(books.peekLast());
// 將棧頂的元素彈出“棧” book6
System.out.println(books.pop());
// 下面輸出將看到隊列中第一個元素被刪除
System.out.println(books);
// 訪問、并刪除隊列的最后一個元素book2
System.out.println(books.pollLast());
System.out.println(books);
}
}
LinkedList與ArrayList、ArrayDeque的實現機制完全不同。后兩者內部以數組形式來保存集合中的元素,因此隨機訪問集合元素時性能較好;LinkedList內部以鏈表的形式來保存集合中的元素,因此隨機訪問集合中的元素時性能較差,但在插入、刪除元素時性能較好。Vector和后兩者都是以數組形式存儲元素,,而Vector實現了線程安全,所以性能較差。
各種線性表的性能分析
數組一塊連續內存區來保存所有的數組元素,所以在隨機訪問時性能最好,所有的內部以數組作為底層實現的集合隨機訪問時性能都比較好。而內部以鏈表作為底層實現的集合在執行插入、刪除操作時性能較好。
List集合使用的幾點建議
- 需要遍歷List集合元素時,對于ArrayList、Vector集合,應該使用隨機訪問方法來遍歷集合元素;對于LinkedList應該采用迭代器來遍歷。
- 需要經常執行插入、刪除來改變包含大量數據的List集合的大小,可考慮使用LinkedList集合。使用ArrayList需要重新分配內部數組的大小,效果比較差。
- 如果有多個線程需要同時訪問List集合中的元素,開發者可考慮使用Collection將集合包裝成線性安全的集合。
Map集合
Map用于保存具有映射關系的數據,包含兩組值,一組是key,一組是value。key不允許重復。Java源碼中先實現了Map,然后通過包裝一個所有value都為null的Map實現了Set集合。Map中包括一個內部類Entry,該類封裝了一個key-value對。
import java.util.*;
public class MapTest
{
public static void main(String[] args)
{
Map map = new HashMap();
// 成對放入多個key-value對
map.put("瘋狂Java講義" , 109);
map.put("瘋狂iOS講義" , 10);
map.put("瘋狂Ajax講義" , 79);
// 多次放入的key-value對中value可以重復
map.put("輕量級Java EE企業應用實戰" , 99);
// 放入重復的key時,新的value會覆蓋原有的value
// 如果新的value覆蓋了原有的value,該方法返回被覆蓋的value
System.out.println(map.put("瘋狂iOS講義" , 99)); // 輸出10
System.out.println(map); // 輸出的Map集合包含4個key-value對
// 判斷是否包含指定key
System.out.println("是否包含值為 瘋狂iOS講義 key:"
+ map.containsKey("瘋狂iOS講義")); // 輸出true
// 判斷是否包含指定value
System.out.println("是否包含值為 99 value:"
+ map.containsValue(99)); // 輸出true
// 獲取Map集合的所有key組成的集合,通過遍歷key來實現遍歷所有key-value對
for (Object key : map.keySet() )
{
// map.get(key)方法獲取指定key對應的value
System.out.println(key + "-->" + map.get(key));
}
map.remove("瘋狂Ajax講義"); // 根據key來刪除key-value對。
System.out.println(map); // 輸出結果不再包含 瘋狂Ajax講義=79 的key-value對
}
}
import java.util.*;
public class MapTest2
{
public static void main(String[] args)
{
Map map = new HashMap();
// 成對放入多個key-value對
map.put("瘋狂Java講義" , 109);
map.put("瘋狂iOS講義" , 99);
map.put("瘋狂Ajax講義" , 79);
// 嘗試替換key為"瘋狂XML講義"的value,由于原Map中沒有對應的key,
// 因此對Map沒有改變,不會添加新的key-value對
map.replace("瘋狂XML講義" , 66);
System.out.println(map);
// 使用原value與參數計算出來的結果覆蓋原有的value
map.merge("瘋狂iOS講義" , 10 ,
(oldVal , param) -> (Integer)oldVal + (Integer)param);
System.out.println(map); // "瘋狂iOS講義"的value增大了10
// 當key為"Java"對應的value為null(或不存在時),使用計算的結果作為新value
map.computeIfAbsent("Java" , (key)->((String)key).length());
System.out.println(map); // map中添加了 Java=4 這組key-value對
// 當key為"Java"對應的value存在時,使用計算的結果作為新value
map.computeIfPresent("Java",
(key , value) -> (Integer)value * (Integer)value);
System.out.println(map); // map中 Java=4 變成 Java=16
}
}
HashMap和Hashtable實現類
HashMap和Hashtable類似于ArrayList和Vector的關系,Hashtable是一個古老的Map類。類似Vector盡量少用Hashtable,可以通過Collections工具類把HashMap變成線程安全的。
區別:
- Hashtable線程安全,HashMap線程不安全。HashMap性能更高。多個線程訪問同一個Map對象時使用Hashtable
- Hashtable不允許使用null作為key或者value,HashMap可以使用null作為key或者value
import java.util.*;
public class NullInHashMap
{
public static void main(String[] args)
{
HashMap hm = new HashMap();
// 試圖將兩個key為null的key-value對放入HashMap中
hm.put(null , null);
hm.put(null , null); // 無法放入
// 將一個value為null的key-value對放入HashMap中
hm.put("a" , null); // ②
// 輸出Map對象
System.out.println(hm);
}
}
HashMap、Hashtable判斷key相等:兩個key通過equal方法返回true;兩個key的hashCode值相等。
HashMap、Hashtable判斷value相等:兩個對象通過equal方法返回true。
import java.util.*;
class A
{
int count;
public A(int count)
{
this.count = count;
}
// 根據count的值來判斷兩個對象是否相等。
public boolean equals(Object obj)
{
if (obj == this)
return true;
if (obj != null && obj.getClass() == A.class)
{
A a = (A)obj;
return this.count == a.count;
}
return false;
}
// 根據count來計算hashCode值。
public int hashCode()
{
return this.count;
}
}
class B
{
// 重寫equals()方法,B對象與任何對象通過equals()方法比較都返回true
public boolean equals(Object obj)
{
return true;
}
}
public class HashtableTest
{
public static void main(String[] args)
{
Hashtable ht = new Hashtable();
ht.put(new A(60000) , "瘋狂Java講義");
ht.put(new A(87563) , "輕量級Java EE企業應用實戰");
ht.put(new A(1232) , new B());
System.out.println(ht);
// 只要兩個對象通過equals比較返回true,
// Hashtable就認為它們是相等的value。
// 由于Hashtable中有一個B對象,
// 它與任何對象通過equals比較都相等,所以下面輸出true。
System.out.println(ht.containsValue("測試字符串")); // 輸出true
// 只要兩個A對象的count相等,它們通過equals比較返回true,且hashCode相等
// Hashtable即認為它們是相同的key,所以下面輸出true。
System.out.println(ht.containsKey(new A(87563))); // 輸出true
// 下面語句可以刪除最后一個key-value對
ht.remove(new A(1232));
System.out.println(ht);
}
}
盡量不要使用可變對象作為HashMap、Hashtable的key,如果確實需要,盡量不要在程序中修改作為key的可變對象。
LinkedHashMap實現類
LinkedHashMap也是用雙向鏈表來維護key-value對的次序,該鏈表負責維護Map的迭代順序,其余key-value對的插入順序保持一致。因為需要維護元素的插入順序,性能不如HashMap,但在迭代訪問Map里的全部元素時將有較好的性能。
import java.util.*;
public class HashMapErrorTest
{
public static void main(String[] args)
{
HashMap ht = new HashMap();
// 此處的A類與前一個程序的A類是同一個類
ht.put(new A(60000) , "瘋狂Java講義");
ht.put(new A(87563) , "輕量級Java EE企業應用實戰");
// 獲得Hashtable的key Set集合對應的Iterator迭代器
Iterator it = ht.keySet().iterator();
// 取出Map中第一個key,并修改它的count值
A first = (A)it.next();
first.count = 87563;
// 輸出{A@1560b=瘋狂Java講義, A@1560b=輕量級Java EE企業應用實戰}
System.out.println(ht);
// 只能刪除沒有被修改過的key所對應的key-value對
ht.remove(new A(87563));
System.out.println(ht);
// 無法獲取剩下的value,下面兩行代碼都將輸出null。
System.out.println(ht.get(new A(87563))); //輸出null
System.out.println(ht.get(new A(60000))); //輸出null
}
}
LinkedHashMap實現類
使用雙向鏈表維護key-value對。因為需要維護元素的插入順序,性能低于HaspMap。
import java.util.*;
public class LinkedHashMapTest
{
public static void main(String[] args)
{
LinkedHashMap scores = new LinkedHashMap();
scores.put("語文" , 80);
scores.put("英文" , 82);
scores.put("數學" , 76);
// 調用forEach方法遍歷scores里的所有key-value對
scores.forEach((key, value) -> System.out.println(key + "-->" + value));
}
}
使用Properties讀寫屬性文件
Properties類是Hashtable類的子類,該對象在處理屬性文件時特別方便。Properties類可以把Map對象和屬性文件關聯起來,從而可以把Map對象中的key-value對寫入屬性文件,也可以把屬性文件中的屬性名=屬性值加載到Map對象中。由于屬性文件里的屬性名、屬性值只能是字符串類型,所以Properties里的key、value都是字符串類型
import java.util.*;
import java.io.*;
public class PropertiesTest
{
public static void main(String[] args)
throws Exception
{
Properties props = new Properties();
// 向Properties中增加屬性
props.setProperty("username" , "yeeku");
props.setProperty("password" , "123456");
// 將Properties中的key-value對保存到a.ini文件中
props.store(new FileOutputStream("a.ini")
, "comment line"); //寫入文件
// 新建一個Properties對象
Properties props2 = new Properties();
// 向Properties中增加屬性
props2.setProperty("gender" , "male");
// 將a.ini文件中的key-value對追加到props2中
props2.load(new FileInputStream("a.ini") ); //讀取
System.out.println(props2);
}
}
SortedMap和TreeMap實現類
Set接口派生出SortedSet子接口,TreeSet接口也有一個TreeSet實現類。
Map接口派生出SortedMap子接口,SortedMap接口也有一個TreeMap實現類。
兩種排序方式:自然排序、定制排序,同TreeSet。
TreeMap判斷key相等:兩個key通過compareTo方法返回0。但是自定義類時需要equals方法和compareTo方法返回的結果一致。
import java.util.*;
class R implements Comparable
{
int count;
public R(int count)
{
this.count = count;
}
public String toString()
{
return "R[count:" + count + "]";
}
// 根據count來判斷兩個對象是否相等。
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj != null && obj.getClass() == R.class)
{
R r = (R)obj;
return r.count == this.count;
}
return false;
}
// 根據count屬性值來判斷兩個對象的大小。
public int compareTo(Object obj)
{
R r = (R)obj;
return count > r.count ? 1 :
count < r.count ? -1 : 0;
}
}
public class TreeMapTest
{
public static void main(String[] args)
{
TreeMap tm = new TreeMap();
tm.put(new R(3) , "輕量級Java EE企業應用實戰");
tm.put(new R(-5) , "瘋狂Java講義");
tm.put(new R(9) , "瘋狂Android講義");
System.out.println(tm);
// 返回該TreeMap的第一個Entry對象
System.out.println(tm.firstEntry());
// 返回該TreeMap的最后一個key值
System.out.println(tm.lastKey());
// 返回該TreeMap的比new R(2)大的最小key值。
System.out.println(tm.higherKey(new R(2)));
// 返回該TreeMap的比new R(2)小的最大的key-value對。
System.out.println(tm.lowerEntry(new R(2)));
// 返回該TreeMap的子TreeMap 介于-1--4之間的key
System.out.println(tm.subMap(new R(-1) , new R(4)));
}
}
Map實現類的性能分析
HashMap和Hashtable實現機制幾乎一樣,但是后者是線程安全的,HashMap通常比Hashtable快。
TreeMap比HashMap和Hashtable慢,因為其底層采用紅黑樹管理key-value對。優勢在于:key-value對總是處于有序狀態,無須專門進行排序操作。
以上應該多考慮使用HashMap。
LinkedHashMap比HashMap慢一點,因為需要維護鏈表維持添加順序 。
操作集合的工具類
Collections提供了大量方法對Set、List、Map進行排序、查詢、修改操作。
排序
public class SortTest
{
public static void main(String[] args)
{
ArrayList nums = new ArrayList();
nums.add(2);
nums.add(-5);
nums.add(3);
nums.add(0);
System.out.println(nums); // 輸出:[2, -5, 3, 0]
Collections.reverse(nums); // 將List集合元素的次序反轉
System.out.println(nums); // 輸出:[0, 3, -5, 2]
Collections.sort(nums); // 將List集合元素的按自然順序排序
System.out.println(nums); // 輸出:[-5, 0, 2, 3]
Collections.shuffle(nums); // 將List集合元素的按隨機順序排序
System.out.println(nums); // 每次輸出的次序不固定
}
}
查找替換
import java.util.*;
public class SearchTest
{
public static void main(String[] args)
{
ArrayList nums = new ArrayList();
nums.add(2);
nums.add(-5);
nums.add(3);
nums.add(0);
System.out.println(nums); // 輸出:[2, -5, 3, 0]
System.out.println(Collections.max(nums)); // 輸出最大元素,將輸出3
System.out.println(Collections.min(nums)); // 輸出最小元素,將輸出-5
Collections.replaceAll(nums , 0 , 1); // 將nums中的0使用1來代替
System.out.println(nums); // 輸出:[2, -5, 3, 1]
// 判斷-5在List集合中出現的次數,返回1
System.out.println(Collections.frequency(nums , -5));
Collections.sort(nums); // 對nums集合排序
System.out.println(nums); // 輸出:[-5, 1, 2, 3]
//只有排序后的List集合才可用二分法查詢,輸出3
System.out.println(Collections.binarySearch(nums , 3));
}
}
同步控制
HashSet、TreeSet、ArrayList、ArrayDeque、linkedlist、HashMap、TreeMap都是線程不安全的。Collections提供了許多類方法把他們包裝成線程同步集合。
import java.util.*;
public class SynchronizedTest
{
public static void main(String[] args)
{
// 下面程序創建了四個線程安全的集合對象
Collection c = Collections
.synchronizedCollection(new ArrayList());
List list = Collections.synchronizedList(new ArrayList());
Set s = Collections.synchronizedSet(new HashSet());
Map m = Collections.synchronizedMap(new HashMap());
}
}
設置不可變集合
import java.util.*;
public class UnmodifiableTest
{
public static void main(String[] args)
{
// 創建一個空的、不可改變的List對象
List unmodifiableList = Collections.emptyList();
// 創建一個只有一個元素,且不可改變的Set對象
Set unmodifiableSet = Collections.singleton("瘋狂Java講義");
// 創建一個普通Map對象
Map scores = new HashMap();
scores.put("語文" , 80);
scores.put("Java" , 82);
// 返回普通Map對象對應的不可變版本
Map unmodifiableMap = Collections.unmodifiableMap(scores);
// 下面任意一行代碼都將引發UnsupportedOperationException異常
unmodifiableList.add("測試元素"); //異常
unmodifiableSet.add("測試元素"); //異常
unmodifiableMap.put("語文" , 90); //異常
}
}