9.6.3 集合框架簡述
在JDK API中專門設計了一組類,這組類的功能就是實現各種各樣方式的數據存儲,這樣一組專門用來存儲其它對象的類,一般被稱為對象容器類,簡稱容器類,這組類和接口的設計結構也被統稱為集合框架(Collection Framework)。
這組類和接口都包含在java.util包中。
為了使整個集合框架中的類便於使用,在設計集合框架時大量的使用接口,實際實現的功能類實現對應的接口,這樣可以保證各個集合類的使用方式保持統一。
在集合框架中,提供的存儲方式共有兩種:
1、按照索引值操作數據
在這種存儲方式中,為每個存儲的數據設定一個索引值,存儲在容器中的第一個元素索引值是0,第二個索引值是1,依次類推。在操作數據時按照索引值操作對應的數據,實現這種方式的集合類都實現java.util.Collection接口。
2、按照名稱操作數據
在這種存儲方式中,為每個存儲的數據設定一個名稱(任意非null的對象都可以作為名稱),以後按照該名稱操作該數據,要求名稱不能重復,每個名稱對應唯一的一個值。這種存儲數據的方式也稱作名稱-數值對,也就是名值對存儲。實現這種方式的幾個類都實現java.util.Map接口。
這裡“按照索引值操作數據”的存儲方式,又按照容器內部是否能夠存儲重復的元素,劃分成兩類:
1、允許存儲重復元素。
這種存儲方式中,所有的類都實現了java.util.List接口。
2、不允許存儲重復元素。
這種存儲方式中,所有的類都實現了java.util.Set接口。
這樣,集合框架中的類就分成了三大類:
1、List系列
該系列中的類按照索引值來操作數據,允許存放重復的元素。
2、Set系列
該系列中的類按照索引值來操作數據,不允許存放重復的元素。
3、Map系列
該系列中的類按照名稱來操作數據,名稱不允許重復,值可以重復,一個名稱對應一個唯一的值。
而在數據結構中,實現數據的存儲又可以使用不同的數據結構類型進行存儲,例如數組、鏈表、棧、隊列和樹等,則以上三類集合框架可以使用不同的數據結構類進行實現,使用每種數據結構則具備該中數據結構的特點。例如使用數組則訪問速度快,使用鏈表則便於動態插入和刪除等,這樣就造成了集合框架的復雜性。
另外,在將對象存儲到集合類中,為了加快存儲的速度,要求被存儲對象的類中必須覆蓋equals方法和hashCode方法。
對於這些集合類,下面按照以上三個系列的順序一一進行說明。
9.6.3.1 List系列
List系列的類均實現List接口,大部分的類都以List作為類名的後綴,也有部分該體系中的類命名比較特殊。
該系列中的類,比較常見的有ArrayList和LinkedList兩個。其中ArrayList是以數組為基礎實現的List,而LinkedList則是以鏈表為基礎實現的List,ArrayList擁有數組的優點,而LinkedList擁有鏈表的優點。
由於該體系中的類均實現List接口,所以在這些類的內部,相同的功能方法聲明是保持一致的,下面進行一一介紹:
a、add方法
boolean add(Object o)
該方法的作用是追加對象o到已有容器的末尾。
另外一個add方法:
void add(int index, Object element)
該方法的作用是將對象element插入到容器中索引值為index的位置,原來位於該位置的對象以及後續的內容將依次向後移動。
b、addAll方法
boolean addAll(Collection c)
該方法的作用是將容器對象c中的每個元素依次添加到當前容器的末尾。
另外一個addAll方法:
boolean addAll(int index, Collection c)
該方法的作用是將容器對象c中的第一個元素插入到當前容器中索引值為index的位置,第二個元素插入到當前容器中索引值為index+1的位置,依次類推。而當前容器中原來位於index以及index索引值以後的元素則依次向後移動。
c、get方法
Object get(int index)
該方法的作用是返回當前容器對象中索引值為index的元素的內容。
d、indexOf方法
int indexOf(Object o)
該方法的作用是查找當前容器中是否存在對象o,如果存在則返回該對象第一次出現位置的索引值,如果不存在則返回-1。
另外一個方法lastIndexOf則是從末尾向前查找,返回從末尾向前第一次出現位置的索引值,如果不存在則返回-1。
e、remove方法
Object remove(int index)
該方法的作用是刪除索引值為index的對象的內容,如果刪除成功則返回被刪除對象的內容。
另外一個remove方法:
boolean remove(Object o)
該方法的作用是刪除對象內容為o的元素,如果相同的對象有多個,則只刪除索引值小的對象。如果刪除成功則返回true,否則返回false。
無論使用哪一個remove方法,類內部都自動移動將被刪除位置後續的所有元素向前移動,保證索引值的連續性。
f、set方法
Object set(int index, Object element)
該方法的作用是修改索引值為index的內容,將原來的內容修改成對象element的內容。
g、size方法
int size()
該方法的作用是返回當前容器中已經存儲的有效元素的個數。
h、toArray方法
Object[] toArray()
該方法的作用是將當前容器中的元素按照順序轉換成一個Object數組。
下面是一個簡單的以ArrayList類為基礎實現的List系列中類基本使用的示例,代碼如下:
import java.util.*;
/**
* 以ArrayList類為基礎演示List系列類的基本使用
*/
public class ArrayListUse {
public static void main(String[] args) {
//容器對象的初始化
List list = new ArrayList();
//添加數據
list.add("1");
list.add("2");
list.add("3");
list.add("1");
list.add("1");
//插入數據
list.add(1,"12");
//修改數據
list.set(2, "a");
//刪除數據
list.remove("1");
//遍歷
int size = list.size(); //獲得有效個數
//循環有效索引值
for(int i = 0;i < size;i++){
System.out.println((String)list.get(i));
}
}
}
該程序的運行結果為:
12
a
3
1
1
在List系列中,還包含了Stack(棧)類和Vector(向量)類,Stack類除了實現List系列的功能以外,還實現了棧的結構,主要實現了出棧的pop方法和入棧的push方法。
而Vector類由於需要兼容老版本JDK中緣故,所以在實現的方法中需要提供老版本Vector類中對應的方法,這樣導致Vector類中相同或類似的功能方法一般是成對出現的。
9.6.3.2 Set系列
Set系列中的類都實現了Set接口,該系列中的類均以Set作為類名的後綴。該系列中的容器類,不允許存儲重復的元素。也就是當容器中已經存儲一個相同的元素時,無法實現添加一個完全相同的元素,也無法將已有的元素修改成和其它元素相同。
Set系列中類的這些特點,使得在某些特殊場合的使用比較適合。
該系列中常見的類有:
1、CopyOnWriteArraySet
以數組為基礎實現的Set類。
2、HashSet
以哈希表為基礎實現的Set類。
3、LinkedHashSet
以鏈表為基礎實現的Set類。
4、TreeSet
以樹為基礎實現的Set類。
以不同的數據結構類型實現的Set類,擁有不同數據結構帶來的特性,在實際使用時,根據邏輯的需要選擇合適的Set類進行使用。
Set系列中的類的方法和List系列中的類的方法要比List系列中少很多,例如不支持插入和修改,而且對於Set系列中元素的遍歷也需要轉換為專門的Iterator(迭代器)對象才可以進行遍歷,遍歷時順序和Set中存儲的順序會有所不同。
下面是以HashSet類為基礎實現的示例代碼,代碼如下:
import java.util.*;
/**
* 以HashSet為基礎演示Set系列類的基本使用
*/
public class HashSetUse {
public static void main(String[] args) {
//容器對象的初始化
Set set = new HashSet();
//添加元素
set.add("1");
set.add("2");
set.add("3");
set.add("1");
set.add("1");
//刪除數據
//set.remove("1");
//遍歷
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println((String)iterator.next());
}
}
}
該程序的運行結果為:
3
2
1
9.6.3.3 Map系列
Map系列中的類都實現了Map接口,該系列中的部分類以Map作為類名的後綴。該系列容器類存儲元素的方式和以上兩種完全不同。
Map提供了一種使用“名稱:值”這樣的名稱和數值對存儲數據的方法,在該存儲方式中,名稱不可以重復,而不同的名稱中可以存儲相同的數值。具體這種存儲的格式將在示例代碼中進行實現。
在這種存儲結構中,任何不為null的對象都可以作為一個名稱(key)來作為存儲的值(value)的標識,使用這種形式更利於存儲比較零散的數據,也方便數據的查找和獲得。Map類中存儲的數據沒有索引值,系統會以一定的形式索引存儲的名稱,從而提高讀取數據時的速度。
該系列中常見的類有:
1、HashMap
以Hash(哈希表)為基礎實現的Map類。
2、LinkedHashMap
以鏈表和Hash(哈希表)為基礎實現的Map類。
3、TreeMap
以樹為基礎實現的Map類。
和上面的結構類似,以不同的數據結構實現的Map類,擁有不同數據結構的特點,在實際的項目中使用時,根據需要選擇合適的即可。
該系列的類中常見的方法如下:
a、get方法
Object get(Object key)
該方法的作用是獲得當前容器中名稱為key的結構對應的值。
b、keySet方法
Set keySet()
該方法的作用是返回當前容器中所有的名稱,將所有的名稱以Set的形式返回。使用這個方法可以實現對於Map中所有元素的遍歷。
c、put方法
Object put(Object key, Object value)
該方法的作用是將值value以名稱key的形式存儲到容器中。
d、putAll方法
void putAll(Map t)
該方法的作用是將Map對象t中的所有數據按照原來的格式存儲到當前容器類中,相當於合並兩個Map容器對象。
e、remove方法
Object remove(Object key)
該方法的作用是刪除容器中名稱為key的值。
f、size方法
int size()
該方法的作用是返回當前日期中存儲的名稱:值數據的組數。
g、values方法
Collection values()
該方法的作用是返回當前容器所有的值組成的集合,以Collection對象的形式返回。
下面是一個簡單的示例,在該示例中演示Map系列類的基本使用,代碼如下:
import java.util.*;
/**
* 以HashMap為基礎演示Map系列中類的使用
*/
public class HashMapUse {
public static void main(String[] args) {
//容器對象的初始化
Map map = new HashMap();
//存儲數據
map.put("蘋果", "2.5");
map.put("桔子", "2.5");
map.put("香蕉", "3");
map.put("菠蘿", "2");
//刪除元素
map.remove("桔子");
//修改元素的值
map.put("菠蘿", "5");
//獲得元素個數
int size = map.size();
System.out.println("個數是:" + size);
//遍歷Map
Set set = map.keySet();
Iterator iterator = set.iterator();
while(iterator.hasNext()){
//獲得名稱
String name = (String)iterator.next();
//獲得數值
String value = (String)map.get(name);
//顯示到控制台
System.out.println(name + ":" + value);
}
}
}
該程序的運行結果為:
個數是:3
香蕉:3
菠蘿:5
蘋果:2.5
9.6.3.4 使用示例
如前所述,集合框架中的類只是提供了一種數據存儲的方式,在實際使用時,可以根據邏輯的需要選擇合適的集合類進行使用。
下面以一個字符串計算的示例演示集合類的實際使用。
該程序的功能為計算一個數字字符串,例如”1+2*31-5”、”12*30/34-450”等,的計算結果,在該示例中支持四則運算,但是不支持括號。本示例中計算的字符串要求合法。
該程序實現的原理是:首先按照運算符作為間隔,將字符串差分為數字字符串和運算符字符串的序列,由於分拆出的字符串數量不固定,所以存儲到List系列的Vector容器中,然後按照運算符的優先級進行計算。
該程序的代碼如下:
import java.util.*;
/**
* 計算字符串的值
*/
public class CalcStr {
public static void main(String[] args) {
String s = "1+20*3/5";
double d = calc(s);
System.out.println(d);
}
/**
* 計算字符串的值
* @param s 需要計算的字符串
* @return 計算結果
*/
public static double calc(String s){
//拆分字符串
Vector v = split(s);
//print(v); //測試代碼
//計算字符串
double d = calcVector(v);
return d;
}
/**
* 將字符串拆分為數字和運算符。
* 例如:"1+23*4"則拆分為:"1"、"+"、"23"、"*"和"4"
* @param s 需要拆分的字符串
* @return 拆分以後的結果
*/
private static Vector split(String s){
Vector v = new Vector();
String content = "";
int len = s.length(); //字符串長度
char c;
for(int i = 0;i < len;i++){
c = s.charAt(i);
//判斷是否為運算符
if(c == '+' ||
c == '-' ||
c == '*' ||
c == '/'){
//存儲數字
v.add(content);
//存儲運算符
v.add("" + c);
//清除已有字符串
content = "";
}else{
content += c; //連接字符串
}
}
v.add(content); //添加最後一個數字
return v;
}
/**
* 測試代碼,輸出拆分以後的結果
* @param v 需要打印的Vector對象
*/
private static void print(Vector v){
int size = v.size();
for(int i = 0;i < size;i++){
System.out.println((String)v.get(i));
}
}
/**
* 計算Vector中的數據
* @param v 存儲拆分後字符串的Vector
* @return 計算結果
*/
private static double calcVector(Vector v){
int index1;
int index2;
//計算乘除
while(true){
index1 = v.indexOf("*"); //乘號索引值
index2 = v.indexOf("/"); //除號索引值
//無乘除符號
if(index1 == - 1 && index2 == -1){
break; //結束循環
}
//如果有乘號
if(index1 != -1){
//沒有除號或乘號在前
if(index2 == -1 || index1 < index2){
String s1 = (String)v.get(index1 - 1); //第一個數字
String opr = (String)v.get(index1); //運算符
String s2 = (String)v.get(index1 + 1); //第二個數字
//計算
String answer = calc(s1,s2,opr);
//計算以後的處理
handle(answer,index1 - 1,v);
}
}
//有除號
if(index2 != -1){
//沒有乘號或除號在前
if(index1 == -1 || index2 < index1){
String s1 = (String)v.get(index2 - 1); //第一個數字
String opr = (String)v.get(index2); //運算符
String s2 = (String)v.get(index2 + 1); //第二個數字
//計算
String answer = calc(s1,s2,opr);
//計算以後的處理
handle(answer,index2 - 1,v);
}
}
}
//計算加
int index3 = v.indexOf("+");
while(index3 != -1){ //有加號
String s1 = (String)v.get(index3 - 1); //第一個數字
String opr = (String)v.get(index3); //運算符
String s2 = (String)v.get(index3 + 1); //第二個數字
//計算
String answer = calc(s1,s2,opr);
//計算以後的處理
handle(answer,index3 - 1,v);
//獲得下一個加號的位置
index3 = v.indexOf("+");
}
//計算減
index3 = v.indexOf("-");
while(index3 != -1){ //有加號
String s1 = (String)v.get(index3 - 1); //第一個數字
String opr = (String)v.get(index3); //運算符
String s2 = (String)v.get(index3 + 1); //第二個數字
//計算
String answer = calc(s1,s2,opr);
//計算以後的處理
handle(answer,index3 - 1,v);
//獲得下一個減號的位置
index3 = v.indexOf("-");
}
//反饋結果
String data = (String)v.get(0);
return Double.parseDouble(data);
}
/**
* 計算兩個字符串類型的值運算結果
* @param number1 數字1
* @param number2 數字2
* @param opr 運算符
* @return 運算結果
*/
private static String calc(String number1,String number2,String opr){
//將字符串轉換為數字
double d1 = Double.parseDouble(number1);
double d2 = Double.parseDouble(number2);
//判斷運算符
if(opr.equals("+")){
return "" + (d1 + d2);
}
if(opr.equals("-")){
return "" + (d1 - d2);
}
if(opr.equals("*")){
return "" + (d1 * d2);
}
if(opr.equals("/")){
return "" + (d1 / d2);
}
return "0"; //運算符錯誤時返回0
}
/**
* 計算以後的處理
* @param answer 計算結果
* @param index 參與計算的三個字符串中第一個字符串的起始位置
* @param v 存儲字符串的容器
*/
private static void handle(String answer,int index,Vector v){
//刪除計算過的字符串
for(int i = 0;i < 3;i++){
v.remove(index);
}
//將計算結果插入到index位置
v.insertElementAt(answer, index);
}
}
該程序的運行結果為:
13.0
9.7 總結
在本章中,主要介紹了java.lang包和java.util包中比較常見的類的使用,熟悉了JDK API的基本使用,掌握了文檔的查閱以及方法調用等一些基本的技能,為後續章節的學習打下堅實的基礎。