一叨橱、集合入門總結
集合框架:
Java中的集合框架大類可分為Collection和Map;兩者的區(qū)別:
1断盛、Collection是單列集合罗洗;Map是雙列集合
2、Collection中只有Set系列要求元素唯一钢猛;Map中鍵需要唯一伙菜,值可以重復
3、Collection的數據結構是針對元素的命迈;Map的數據結構是針對鍵的仇让。
泛型:
在說兩大集合體系之前先說說泛型,因為在后面的集合中都會用到躺翻;
所謂的泛型就是:類型的參數化
泛型是類型的一部分丧叽,類名+泛型是一個整體
如果有泛型,不使用時公你,參數的類型會自動提升成Object類型踊淳,如果再取出來的話就需要向下強轉,就可能發(fā)生類型轉化異常(ClassCaseException)陕靠;不加泛型就不能在編譯期限定向集合中添加元素的類型迂尝,導致后期的處理麻煩。
下面就來對比加了泛型和不加泛型的區(qū)別:
package 好好學java;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Test {
public static void main(String[] args) {
// 不加泛型剪芥,添加和遍歷
List list = new ArrayList<>();
list.add(1);
list.add("123");
list.add("hello");
Iterator it = list.iterator();
while(it.hasNext()){
// 沒有添加泛型垄开,這里只能使用Object接收
Object obj = it.next();
System.out.println(obj);
}
}
}
package 好好學java;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Test {
public static void main(String[] args) {
// 加泛型,添加和遍歷
List<String> list = new ArrayList<String>();
list.add("123");
list.add("hello");
Iterator<String> it = list.iterator();
while(it.hasNext()){
// 因為添加了泛型税肪,就說明集合中裝的全部都是String類型的數據
// 所以這里用String類型接收溉躲,就不會發(fā)生異常,并且可以使用String的方法
String str = it.next();
System.out.println(str.length());
}
}
}
自定義帶泛型的類:
package 好好學java;
public class Test {
// 自定義一個帶有一個參數的泛型類,可以向傳入什么類型就傳入什么類型
public static void main(String[] args) {
// 進行測試, 傳入一個String對象
Person<String> perStr = new Person<String>();
perStr.setT("我是字符串");
String str = perStr.getT();
System.out.println(str);
// 進行測試益兄,傳入一個Integer對象
Person<Integer> perInt = new Person<Integer>();
perInt.setT(100);
Integer intVal = perInt.getT();
System.out.println(intVal);
}
}
//自定義一個帶有一個參數的泛型類
class Person<T>{
private T t;
void setT(T t){
this.t = t;
}
T getT(){
return t;
}
}
實現帶有泛型的接口類型:
實現接口的同時, 指定了接口中的泛型類型. (定義類時確定)锻梳;
public class GenericImpl1 implements GenericInter<String> {}
實現接口時, 沒有指定接口中的泛型類型.此時, 需要將該接口的實現類定義為泛型類.接口的類型需要在創(chuàng)建實現類對象時才能真正確定其類型. (始終不確定類型, 直到創(chuàng)建對象時確定類型);
public class GenericImpl2<T> implements GenericInter<T> {}
泛型的通配符(?):
上限限定:比如定義方法的時候出現,public void getFunc(List<? extends Animal> an)净捅,
那么表示這里的參數可以傳入Animal疑枯,或者 Animal的子類
下限限定: 比如定義方法的時候出現,public void getFunc(Set<? super Animal> an ),
那么表示這里的參數可以傳入Animal蛔六,或者Animal的父類
使用泛型的注意點:
1荆永、泛型不支持基本數據類型
2废亭、泛型不支持繼承,必須保持前后一致(比如這樣是錯誤的:List<Object> list = new ArrayList<String>();
Collection體系:
ollection包括兩大體系具钥,List和Set
List的特點:
存取有序豆村,有索引,可以根據索引來進行取值氓拼,元素可以重復
Set的特點:
存取無序你画,元素不可以重復
List:
下面有ArrayList,LinkedList桃漾,Vector
(已過時)
集合的的最大目的就是為了存然捣恕;List集合的特點就是存取有序撬统,可以存儲重復的元素适滓,可以用下標進行元素的操作
ArrayList: 底層是使用數組實現,所以查詢速度快恋追,增刪速度慢
package 好好學java;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Test {
// 使用ArrayList進行添加和遍歷
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("接口1");
list.add("接口2");
list.add("接口3");
// 第一種遍歷方式,使用迭代器
Iterator<String> it = list.iterator();
while(it.hasNext()){
String next = it.next();
System.out.println(next);
}
System.out.println("-------------------");
// 第二種遍歷方式凭迹,使用foreach
for (String str : list){
System.err.println(str);
}
}
}
LinkedList:是基于鏈表結構實現的,所以查詢速度慢苦囱,增刪速度快嗅绸,提供了特殊的方法,對頭尾的元素操作(進行增刪查)撕彤。
使用LinkedList來實現棧和隊列鱼鸠;棧是先進后出,而隊列是先進先出
package com.xiaoshitou.classtest;
import java.util.LinkedList;
/**
* 利用LinkedList來模擬棧
* 棧的特點:先進后出
* @author Beck
*
*/
public class MyStack {
private LinkedList<String> linkList = new LinkedList<String>();
// 壓棧
public void push(String str){
linkList.addFirst(str);
}
// 出棧
public String pop(){
return linkList.removeFirst();
}
// 查看
public String peek(){
return linkList.peek();
}
// 判斷是否為空
public boolean isEmpty(){
return linkList.isEmpty();
}
}
package 好好學java;
public class Test {
public static void main(String[] args) {
// 測試棧
StackTest stack = new StackTest();
stack.push("我是第1個進去的");
stack.push("我是第2個進去的");
stack.push("我是第3個進去的");
stack.push("我是第4個進去的");
stack.push("我是第5個進去的");
// 取出
while (!stack.isEmpty()){
String pop = stack.pop();
System.out.println(pop);
}
// 打印結果
/*我是第5個進去的
我是第4個進去的
我是第3個進去的
我是第2個進去的
我是第1個進去的*/
}
}
LinkedList實現Queue:
package 好好學java;
import java.util.LinkedList;
/**
* 利用linkedList來實現隊列
* 隊列: 先進先出
* @author Beck
*
*/
public class QueueTest {
private LinkedList<String> link = new LinkedList<String>();
// 放入
public void put(String str){
link.addFirst(str);
}
// 獲取
public String get(){
return link.removeLast();
}
// 判斷是否為空
public boolean isEmpty(){
return link.isEmpty();
}
}
package 好好學java;
public class Test {
public static void main(String[] args) {
// 測試隊列
QueueTest queue = new QueueTest();
queue.put("我是第1個進入隊列的");
queue.put("我是第2個進入隊列的");
queue.put("我是第3個進入隊列的");
queue.put("我是第4個進入隊列的");
// 遍歷隊列
while (!queue.isEmpty()){
String str = queue.get();
System.out.println(str);
}
// 打印結果
/*我是第1個進入隊列的
我是第2個進入隊列的
我是第3個進入隊列的
我是第4個進入隊列的*/
}
}
Vector:因為已經過時羹铅,被ArrayList取代了蚀狰;它還有一種迭代器通過vector.elements()獲取,判斷是否有元素和取元素的方法為:hasMoreElements()职员,nextElement()
麻蹋。
package 好好學java;
import java.util.Enumeration;
import java.util.Vector;
public class Test {
public static void main(String[] args) {
Vector<String> vector = new Vector<String>();
vector.add("搜索");
vector.add("vector");
vector.add("list");
Enumeration<String> elements = vector.elements();
while (elements.hasMoreElements()){
String nextElement = elements.nextElement();
System.out.println(nextElement);
}
}
}
Set:
Set集合的特點:元素不重復贪染,存取無序陪每,無下標
Set集合下面有:HashSet,LinkedHashSet板丽,TreeSet
HashSet存儲字符串:
package 好好學java;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Test {
public static void main(String[] args) {
// 利用HashSet來存取
Set<String> set = new HashSet<String>();
set.add("我的天");
set.add("我是重復的");
set.add("我是重復的");
set.add("welcome");
// 遍歷 第一種方式 迭代器
Iterator<String> it = set.iterator();
while(it.hasNext()){
String str = it.next();
System.out.println(str);
}
System.out.println("--------------");
for (String str : set){
System.out.println(str);
}
// 打印結果蛛蒙,重復的已經去掉了
/*我的天
welcome
我是重復的
--------------
我的天
welcome
我是重復的*/
}
那哈希表是怎么來保證元素的唯一性的呢糙箍,哈希表是通過hashCode和equals方法來共同保證的。
哈希表的存儲數據過程(哈希表底層也維護了一個數組):
根據存儲的元素計算出hashCode值牵祟,然后根據計算得出的hashCode值和數組的長度進行計算出存儲的下標;如果下標的位置無元素抖格,那么直接存儲诺苹;如果有元素咕晋,那么使用要存入的元素和該元素進行equals方法,如果結果為真收奔,則已經有相同的元素了掌呜,所以直接不存;如果結果假坪哄,那么進行存儲质蕉,以鏈表的形式存儲。
演示HashSet來存儲自定義對象:
package 好好學java;
public class Person {
// 屬性
private String name;
private int age;
// 構造方法
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
// 要讓哈希表存儲不重復的元素翩肌,就必須重寫hasCode和equals方法
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
// getter & setter
...
}
package 好好學java;
import java.util.HashSet;
import java.util.Set;
public class Test {
public static void main(String[] args) {
// 利用HashSet來存取自定義對象 Person
Set<Person> set = new HashSet<Person>();
set.add(new Person("張三", 12));
set.add(new Person("李四", 13));
set.add(new Person("王五", 22));
set.add(new Person("張三", 12));
// 遍歷
for (Person p : set){
System.out.println(p);
}
// 結果:向集合中存儲兩個張三對象模暗,但是集合中就成功存儲了一個
/*Person [name=王五, age=22]
Person [name=李四, age=13]
Person [name=張三, age=12]*/
}
}
所以在向HashSet集合中存儲自定義對象時,為了保證set集合的唯一性念祭,那么必須重寫hashCode和equals方法兑宇。
LinkedHashSet:
是基于鏈表和哈希表共同實現的,所以具有存取有序粱坤,元素唯一
package 好好學java;
import java.util.LinkedHashSet;
public class Test {
public static void main(String[] args) {
// 利用LinkedHashSet來存取自定義對象 Person
LinkedHashSet<Person> set = new LinkedHashSet<Person>();
set.add(new Person("張三", 12));
set.add(new Person("李四", 13));
set.add(new Person("王五", 22));
set.add(new Person("張三", 12));
// 遍歷
for (Person p : set){
System.out.println(p);
}
// 結果:向集合中存儲兩個張三對象隶糕,但是集合中就成功存儲了一個,
// 并且存進的順序,和取出來的順序是一致的
/*Person [name=張三, age=12]
Person [name=李四, age=13]
Person [name=王五, age=22]*/
}
}
TreeSet:
特點:存取無序站玄,元素唯一枚驻,可以進行排序(排序是在添加的時候進行排序)。
TreeSet是基于二叉樹的數據結構株旷,二叉樹的:一個節(jié)點下不能多余兩個節(jié)點再登。
二叉樹的存儲過程:
如果是第一個元素,那么直接存入灾常,作為根節(jié)點霎冯,下一個元素進來是會跟節(jié)點比較,如果大于節(jié)點放右邊的钞瀑,小于節(jié)點放左邊沈撞;等于節(jié)點就不存儲。后面的元素進來會依次比較雕什,直到有位置存儲為止
TreeSet集合存儲String對象
package 好好學java;
import java.util.TreeSet;
public class Test {
public static void main(String[] args) {
TreeSet<String> treeSet = new TreeSet<String>();
treeSet.add("abc");
treeSet.add("zbc");
treeSet.add("cbc");
treeSet.add("xbc");
for (String str : treeSet){
System.out.println(str);
}
// 結果:取出來的結果是經過排序的
/*
abc
cbc
xbc
zbc*/
}
}
TreeSet保證元素的唯一性是有兩種方式:
1缠俺、自定義對象實現Comparable接口,重寫comparaTo方法贷岸,該方法返回0表示相等壹士,小于0表示準備存入的元素比被比較的元素小,否則大于0偿警;
2躏救、在創(chuàng)建TreeSet的時候向構造器中傳入比較器Comparator接口實現類對象,實現Comparator接口重寫compara方法。
如果向TreeSet存入自定義對象時盒使,自定義類沒有實現Comparable接口崩掘,或者沒有傳入Comparator比較器時,會出現ClassCastException異常
下面就是演示用兩種方式來存儲自定義對象
package 好好學java;
public class Person implements Comparable<Person>{
// 屬性
private String name;
private int age;
// 構造方法
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
// 要讓哈希表存儲不重復的元素少办,就必須重寫hasCode和equals方法
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
// getter & setter
...
@Override
public int compareTo(Person o) {
int result = this.age - o.age;
if (result == 0){
return this.name.compareTo(o.name);
}
return result;
}
}
package 好好學java;
import java.util.TreeSet;
public class Test {
public static void main(String[] args) {
// 利用TreeSet來存儲自定義類Person對象
TreeSet<Person> treeSet = new TreeSet<Person>();
// Person類實現了Comparable接口苞慢,并且重寫comparaTo方法
// 比較規(guī)則是先按照 年齡排序,年齡相等的情況按照年齡排序
treeSet.add(new Person("張山1", 20));
treeSet.add(new Person("張山2", 16));
treeSet.add(new Person("張山3", 13));
treeSet.add(new Person("張山4", 17));
treeSet.add(new Person("張山5", 20));
for (Person p : treeSet){
System.out.println(p);
}
// 結果:按照comparaTo方法內的邏輯來排序的
/*
Person [name=張山3, age=13]
Person [name=張山2, age=16]
Person [name=張山4, age=17]
Person [name=張山1, age=20]
Person [name=張山5, age=20]
*/
}
}
另一種方式:使用比較器Comparator
package 好好學java;
public class Person{
// 屬性
private String name;
private int age;
// 構造方法
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
// 要讓哈希表存儲不重復的元素英妓,就必須重寫hasCode和equals方法
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
// getter & setter
...
}
package 好好學java;
import java.util.Comparator;
import java.util.TreeSet;
public class Test {
public static void main(String[] args) {
// 利用TreeSet來存儲自定義類Person對象
// 創(chuàng)建TreeSet對象的時候傳入Comparator比較器挽放,使用匿名內部類的方式
// 比較規(guī)則是先按照 年齡排序,年齡相等的情況按照年齡排序
TreeSet<Person> treeSet = new TreeSet<Person>(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
if (o1 == o2){
return 0;
}
int result = o1.getAge() - o2.getAge();
if (result == 0){
return o1.getName().compareTo(o2.getName());
}
return result;
}
});
treeSet.add(new Person("張山1", 20));
treeSet.add(new Person("張山2", 16));
treeSet.add(new Person("張山3", 13));
treeSet.add(new Person("張山4", 17));
treeSet.add(new Person("張山5", 20));
for (Person p : treeSet){
System.out.println(p);
}
// 結果:按照compara方法內的邏輯來排序的
/*
Person [name=張山3, age=13]
Person [name=張山2, age=16]
Person [name=張山4, age=17]
Person [name=張山1, age=20]
Person [name=張山5, age=20]
*/
}
}
比較器總結:
Collection體系總結:
- List : "特點 :" 存取有序,元素有索引,元素可以重復.
- ArrayList : 數組結構,查詢快,增刪慢,線程不安全,因此效率高.
- Vector : 數組結構,查詢快,增刪慢,線程安全,因此效率低.
- LinkedList : 鏈表結構,查詢慢,增刪快,線程不安全,因此效率高.
addFirst() removeFirst() getFirst()
- Set :"特點 :" 存取無序,元素無索引,元素不可以重復.
- HashSet : 存儲無序,元素無索引,元素不可以重復.底層是哈希表.
請問 : 哈希表如何保證元素唯一呢 ? 底層是依賴 hashCode 和 equals 方法.
當存儲元素的時候,先根據 hashCode + 數組長度 計算出一個索引,判斷索引位置是否有元素.
如果沒有元素,直接存儲,如果有元素,先判斷 equals 方法,比較兩個元素是否相同,不同則存儲,相同則舍棄.
我們自定義對象存儲的元素一定要實現 hashCode 和 equals.
- LinkedHashSet : 存儲有序,元素不可以重復.
- TreeSet : 存取無序, 但是可以排序 (自然排序), 元素不可以重復.
有兩種排序方式 :
- 自然排序 :
我們的元素必須實現 Comparable 接口.可比較的.實現 CompareTo 方法.
- 比較器排序 :
我們需要自定義類,實現Comparetor接口,這個類就是比較器實現 compare 方法.
然后在創(chuàng)建 TreeSet 的時候,把比較器對象作為參數傳遞給 TreeSet.
Map:
Map是一個雙列集合蔓纠,其中保存的是鍵值對辑畦,鍵要求保持唯一性,值可以重復
鍵值是一一對應的贺纲,一個鍵只能對應一個值
Map的特點:是存取無序航闺,鍵不可重復
Map在存儲的時候,將鍵值傳入Entry猴誊,然后存儲Entry對象
其中下面有HashMap潦刃,LinkedHashMap和TreeMap
HashMap:
是基于哈希表結構實現的,所以存儲自定義對象作為鍵時懈叹,必須重寫hasCode和equals方法乖杠。存取無序的
下面演示HashMap以自定義對象作為鍵:
package 好好學java;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
public class Test {
public static void main(String[] args) {
// 利用HashMap存儲,自定義對象Person作為鍵
// 為了保證鍵的唯一性澄成,必須重寫hashCode和equals方法
HashMap<Person,String> map = new HashMap<Person,String>();
map.put(new Person("張三", 12), "JAVA");
map.put(new Person("李四", 13), "IOS");
map.put(new Person("小花", 22), "JS");
map.put(new Person("小黑", 32), "PHP");
map.put(new Person("張三", 12), "C++");
Set<Entry<Person, String>> entrySet = map.entrySet();
Iterator<Entry<Person, String>> it = entrySet.iterator();
while (it.hasNext()){
Entry<Person, String> entry = it.next();
System.out.println(entry.getKey() + "---" + entry.getValue());
}
// 結果:存入的時候添加了兩個張三胧洒,如果Map中鍵相同的時候,當后面的值會覆蓋掉前面的值
/*
Person [name=李四, age=13]---IOS
Person [name=張三, age=12]---C++
Person [name=小黑, age=32]---PHP
Person [name=小花, age=22]---JS
*/
}
}
LinkedHashMap:
用法跟HashMap基本一致墨状,它是基于鏈表和哈希表結構的所以具有存取有序卫漫,鍵不重復的特性
下面演示利用LinkedHashMap存儲,注意存的順序和遍歷出來的順序是一致的:
package 好好學java;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
public class Test {
public static void main(String[] args) {
// 利用LinkedHashMap存儲肾砂,自定義對象Person作為鍵
// 為了保證鍵的唯一性列赎,必須重寫hashCode和equals方法
LinkedHashMap<Person,String> map = new LinkedHashMap<Person,String>();
map.put(new Person("張三", 12), "JAVA");
map.put(new Person("李四", 13), "IOS");
map.put(new Person("小花", 22), "JS");
map.put(new Person("小黑", 32), "PHP");
map.put(new Person("張三", 12), "C++");
// foreach遍歷
for (Entry<Person,String> entry : map.entrySet()){
System.out.println(entry.getKey()+"==="+entry.getValue());
}
// 結果:存入的時候添加了兩個張三,如果Map中鍵相同的時候镐确,當后面的值會覆蓋掉前面的值
// 注意:LinkedHashMap的特點就是存取有序包吝,取出來的順序就是和存入的順序保持一致
/*
Person [name=張三, age=12]===C++
Person [name=李四, age=13]===IOS
Person [name=小花, age=22]===JS
Person [name=小黑, age=32]===PHP
*/
}
}
TreeMap:
給TreeMap集合中保存自定義對象,自定義對象作為TreeMap集合的key值源葫。由于TreeMap底層使用的二叉樹诗越,其中存放進去的所有數據都需要排序,要排序息堂,就要求對象具備比較功能嚷狞。對象所屬的類需要實現Comparable接口。或者給TreeMap集合傳遞一個Comparator接口對象感耙。
利用TreeMap存入自定義對象作為鍵:
package 好好學java;
import java.util.Comparator;
import java.util.Map.Entry;
import java.util.TreeMap;
public class Test {
public static void main(String[] args) {
// 利用TreeMap存儲褂乍,自定義對象Person作為鍵
// 自定義對象實現Comparable接口或者傳入Comparator比較器
TreeMap<Person,String> map = new TreeMap<Person,String>(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
if (o1 == o2){
return 0;
}
int result = o1.getAge() - o2.getAge();
if (result == 0){
return o1.getName().compareTo(o2.getName());
}
return result;
}
});
map.put(new Person("張三", 12), "JAVA");
map.put(new Person("李四", 50), "IOS");
map.put(new Person("小花", 32), "JS");
map.put(new Person("小黑", 32), "PHP");
map.put(new Person("張三", 12), "C++");
// foreach遍歷
for (Entry<Person,String> entry : map.entrySet()){
System.out.println(entry.getKey()+"==="+entry.getValue());
}
// 結果:存入的時候添加了兩個張三持隧,如果Map中鍵相同的時候即硼,當后面的值會覆蓋掉前面的值
// 注意:TreeMap 取出來的順序是經過排序的,是根據compara方法排序的
/*
Person [name=張三, age=12]===C++
Person [name=小花, age=32]===JS
Person [name=小黑, age=32]===PHP
Person [name=李四, age=50]===IOS
*/
}
}
二屡拨、集合進階總結
數組和第一類對象
無論使用的數組屬于什么類型只酥,數組標識符實際都是指向真實對象的一個句柄。那些對象本身是在內存
“堆”里創(chuàng)建的呀狼。堆對象既可“隱式”創(chuàng)建(即默認產生)裂允,亦可“顯式”創(chuàng)建(即明確指定,用一個 new
表達式)哥艇。堆對象的一部分(實際是我們能訪問的唯一字段或方法)是只讀的length(長度)成員绝编,它告訴
我們那個數組對象里最多能容納多少元素。對于數組對象貌踏,“ []”語法是我們能采用的唯一另類訪問方法十饥。
對象數組和基本數據類型數組在使用方法上幾乎是完全一致的。唯一的差別在于對象數組容納的是句柄祖乳,而基本數據類型數組容納的是具體的數值
public class ArraySize {
public static void main(String[] args) {
// Arrays of objects:
Weeble[] a; // Null handle
Weeble[] b = new Weeble[5]; // Null handles
Weeble[] c = new Weeble[4];
for (int i = 0; i < c.length; i++)
c[i] = new Weeble();
Weeble[] d = { new Weeble(), new Weeble(), new Weeble() };
// Compile error: variable a not initialized:
// !System.out.println("a.length=" + a.length);
System.out.println("b.length = " + b.length);
// The handles inside the array are
// automatically initialized to null:
for (int i = 0; i < b.length; i++)
System.out.println("b[" + i + "]=" + b[i]);
System.out.println("c.length = " + c.length);
System.out.println("d.length = " + d.length);
a = d;
System.out.println("a.length = " + a.length);
// Java 1.1 initialization syntax:
a = new Weeble[] { new Weeble(), new Weeble() };
System.out.println("a.length = " + a.length);
// Arrays of primitives:
int[] e; // Null handle
int[] f = new int[5];
int[] g = new int[4];
for (int i = 0; i < g.length; i++)
g[i] = i * i;
int[] h = { 11, 47, 93 };
// Compile error: variable e not initialized:
// !System.out.println("e.length=" + e.length);
System.out.println("f.length = " + f.length);
// The primitives inside the array are
// automatically initialized to zero:
for (int i = 0; i < f.length; i++)
System.out.println("f[" + i + "]=" + f[i]);
System.out.println("g.length = " + g.length);
System.out.println("h.length = " + h.length);
e = h;
System.out.println("e.length = " + e.length);
// Java 1.1 initialization syntax:
e = new int[] { 1, 2 };
System.out.println("e.length = " + e.length);
}
}
輸出如下:
b.length = 5
b[0]=null
b[1]=null
b[2]=null
b[3]=null
b[4]=null
c.length = 4
d.length = 3
a.length = 3
a.length = 2
f.length = 5
f[0]=0
f[1]=0
f[2]=0
f[3]=0
f[4]=0
g.length = 4
h.length = 3
e.length = 3
e.length = 2
其中逗堵,數組 a 只是初始化成一個 null 句柄。此時眷昆,編譯器會禁止我們對這個句柄作任何實際操作蜒秤,除非已正
確地初始化了它。數組 b 被初始化成指向由 Weeble 句柄構成的一個數組亚斋,但那個數組里實際并未放置任何
Weeble 對象作媚。然而,我們仍然可以查詢那個數組的大小帅刊,因為 b 指向的是一個合法對象纸泡。
換言之,我們只知道數組對象的大小或容量厚掷,不知其實際容納了多少個元素弟灼。
盡管如此,由于數組對象在創(chuàng)建之初會自動初始化成 null冒黑,所以可檢查它是否為 null田绑,判斷一個特定的數組“空位”是否容納一個對象。類似地抡爹,由基本數據類型構成的數組會自動初始化成零(針對數值類型)掩驱、 null(字符類型)或者false(布爾類型)
數組 c 顯示出我們首先創(chuàng)建一個數組對象,再將 Weeble 對象賦給那個數組的所有“空位”。數組 d 揭示出
“集合初始化”語法欧穴,從而創(chuàng)建數組對象(用 new 命令明確進行民逼,類似于數組 c),然后用 Weeble 對象進行
初始化涮帘,全部工作在一條語句里完成拼苍。
下面這個表達式:
a = d;
向我們展示了如何取得同一個數組對象連接的句柄,然后將其賦給另一個數組對象调缨,向我們展示了如何取得同一個數組對象連接的句柄疮鲫,然后將其賦給另一個數組對象
1.基本數據類型集合
集合類只能容納對象句柄。但對一個數組弦叶,卻既可令其直接容納基本類型的數據俊犯,亦可容納指向對象的句
柄。利用象 Integer伤哺、 Double 之類的“ 封裝器”類燕侠,可將基本數據類型的值置入一個集合里。
無論將基本類型的數據置入數組立莉,還是將其封裝進入位于集合的一個類內贱鼻,都涉及到執(zhí)行效率的問題根灯。顯
然,若能創(chuàng)建和訪問一個基本數據類型數組,那么比起訪問一個封裝數據的集合旬渠,前者的效率會高出許多扎即。
數組的返回
假定我們現在想寫一個方法宏胯,同時不希望它僅僅返回一樣東西谜酒,而是想返回一系列東西。此時芦鳍,象C 和 C++這樣的語言會使問題復雜化嚷往,因為我們不能返回一個數組,只能返回指向數組的一個指針柠衅。這樣就非常麻煩皮仁,因為很難控制數組的“存在時間”,它很容易造成內存“漏洞”的出現菲宴。
Java 采用的是類似的方法贷祈,但我們能“返回一個數組”。當然喝峦,此時返回的實際仍是指向數組的指針势誊。但在Java 里,我們永遠不必擔心那個數組的是否可用—— 只要需要谣蠢,它就會自動存在粟耻。而且垃圾收集器會在我們完成后自動將其清除
public class IceCream {
static String[] flav = { "Chocolate", "Strawberry", "Vanilla Fudge Swirl",
"Mint Chip", "Mocha Almond Fudge", "Rum Raisin", "Praline Cream",
"Mud Pie" };
static String[] flavorSet(int n) {
// Force it to be positive & within bounds:
n = Math.abs(n) % (flav.length + 1);
String[] results = new String[n];
int[] picks = new int[n];
for(int i = 0; i < picks.length; i++)
picks[i] = -1;
for(int i = 0; i < picks.length; i++) {
retry:
while(true) {
int t =(int)(Math.random() * flav.length);
for(int j = 0; j < i; j++)213
if(picks[j] == t) continue retry;
picks[i] = t;
results[i] = flav[t];
break;
}
}
return results;
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
System.out.println("flavorSet(" + i + ") = ");
String[] fl = flavorSet(flav.length);
for (int j = 0; j < fl.length; j++)
System.out.println("\t" + fl[j]);
}
}
}
flavorSet()方法創(chuàng)建了一個名為 results 的 String 數組查近。該數組的大小為 n—— 具體數值取決于我們傳遞給方法的自變量。隨后挤忙,它從數組 flav 里隨機挑選一些“香料”( Flavor)霜威,并將它們置入 results 里,并最終返回 results册烈。返回數組與返回其他任何對象沒什么區(qū)別—— 最終返回的都是一個句柄戈泼。
另一方面,注意當 flavorSet()隨機挑選香料的時候茄厘,它需要保證以前出現過的一次隨機選擇不會再次出現矮冬。為達到這個目的,它使用了一個無限 while 循環(huán)次哈,不斷地作出隨機選擇,直到發(fā)現未在 picks 數組里出現過的一個元素為止(當然吆录,也可以進行字串比較窑滞,檢查隨機選擇是否在 results 數組里出現過,但字串比較的效率比較低)恢筝。若成功哀卫,就添加這個元素,并中斷循環(huán)( break)撬槽,再查找下一個( i 值會遞增)此改。但假若 t 是一個已在 picks 里出現過的數組,就用標簽式的 continue 往回跳兩級侄柔,強制選擇一個新 t共啃。 用一個調試程序可以很清楚地看到這個過程。
集合
為容納一組對象暂题,最適宜的選擇應當是數組移剪。而且假如容納的是一系列基本數據類型,更是必須采用數組薪者。
缺點:類型未知
使用 Java 集合的“缺點”是在將對象置入一個集合時丟失了類型信息纵苛。之所以會發(fā)生這種情況,是由于當初編寫集合時言津,那個集合的程序員根本不知道用戶到底想把什么類型置入集合攻人。若指示某個集合只允許特定的類型,會妨礙它成為一個“常規(guī)用途”的工具悬槽,為用戶帶來麻煩怀吻。為解決這個問題,集合實際容納的是類型為 Object 的一些對象的句柄陷谱。
當然烙博,也要注意集合并不包括基本數據類型瑟蜈,因為它們并不是從“任何東西”繼承來的。
Java 不允許人們?yōu)E用置入集合的對象渣窜。假如將一條狗扔進一個貓的集合铺根,那么仍會將集合內的所有東西都看作貓,所以在使用那條狗時會得到一個“違例”錯誤乔宿。在同樣的意義上位迂,假若試圖將一條狗的句柄“造型”到一只貓,那么運行期間仍會得到一個“違例”錯誤
class Cat {
private int catNumber;
Cat(int i) {
catNumber = i;
}
void print() {
System.out.println("Cat #" + catNumber);
}
}
class Dog {
private int dogNumber;
Dog(int i) {
dogNumber = i;
}
void print() {
System.out.println("Dog #" + dogNumber);
}
}
public class CatsAndDogs {
public static void main(String[] args) {
Vector cats = new Vector();
for (int i = 0; i < 7; i++)
cats.addElement(new Cat(i));
// Not a problem to add a dog to cats:
cats.addElement(new Dog(7));
for (int i = 0; i < cats.size(); i++)
((Cat) cats.elementAt(i)).print();
// Dog is detected only at run-time
}
}
- 錯誤有時并不顯露出來
在某些情況下详瑞,程序似乎正確地工作掂林,不造型回我們原來的類型。第一種情況是相當特殊的: String 類從編譯器獲得了額外的幫助坝橡,使其能夠正常工作眯勾。只要編譯器期待的是一個String 對象,但它沒有得到一個仁锯,就會自動調用在 Object 里定義匙姜、并且能夠由任何 Java 類覆蓋的 toString()方法。這個方法能生成滿足要求的String 對象番宁,然后在我們需要的時候使用元莫。因此,為了讓自己類的對象能顯示出來蝶押,要做的全部事情就是覆蓋toString()方法踱蠢。
class Mouse {
private int mouseNumber;
Mouse(int i) {
mouseNumber = i;
}
// Magic method:
public String toString() {
return "This is Mouse #" + mouseNumber;
}
void print(String msg) {
if (msg != null)
System.out.println(msg);
System.out.println("Mouse number " + mouseNumber);
}
}
class MouseTrap {
static void caughtYa(Object m) {
Mouse mouse = (Mouse) m; // Cast from Object
mouse.print("Caught one!");
}
}
public class WorksAnyway {
public static void main(String[] args) {
Vector mice = new Vector();
for(int i = 0; i < 3; i++)
mice.addElement(new Mouse(i));
for(int i = 0; i < mice.size(); i++) {
// No cast necessary, automatic call
// to Object.toString():
System.out.println(
"Free mouse: " + mice.elementAt(i));
MouseTrap.caughtYa(mice.elementAt(i));
}
}
}
可在 Mouse 里看到對 toString()的重定義代碼。在 main()的第二個 for 循環(huán)中棋电,可發(fā)現下述語句:
System.out.println("Free mouse: " +
mice.elementAt(i));
在“ +”后茎截,編譯器預期看到的是一個 String 對象。 elementAt()生成了一個 Object离陶,所以為獲得希望的String稼虎,編譯器會默認調用 toString()。但不幸的是招刨,只有針對 String 才能得到象這樣的結果霎俩;其他任何類型都不會進行這樣的轉換。
隱藏造型的第二種方法已在 Mousetrap 里得到了應用沉眶。 caughtYa()方法接收的不是一個 Mouse打却,而是一個Object。隨后再將其造型為一個 Mouse谎倔。當然柳击,這樣做是非常冒失的,因為通過接收一個 Object片习,任何東西都可以傳遞給方法捌肴。然而蹬叭,假若造型不正確—— 如果我們傳遞了錯誤的類型—— 就會在運行期間得到一個違例錯誤。這當然沒有在編譯期進行檢查好状知,但仍然能防止問題的發(fā)生秽五。注意在使用這個方法時毋需進行造型:
MouseTrap.caughtYa(mice.elementAt(i));
- 生成能自動判別類型的 Vector
一個更“健壯”的方案是用 Vector 創(chuàng)建一個新類,使其只接收我們指定的
類型饥悴,也只生成我們希望的類型坦喘。
class Gopher {
private int gopherNumber;
Gopher(int i) {
gopherNumber = i;
}
void print(String msg) {
if (msg != null)
System.out.println(msg);
System.out.println("Gopher number " + gopherNumber);
}
}
class GopherTrap {
static void caughtYa(Gopher g) {
g.print("Caught one!");
}
}
class GopherVector {
private Vector v = new Vector();
public void addElement(Gopher m) {
v.addElement(m);
}
public Gopher elementAt(int index) {
return (Gopher) v.elementAt(index);
}
public int size() {
return v.size();
}
public static void main(String[] args) {
GopherVector gophers = new GopherVector();
for (int i = 0; i < 3; i++)
gophers.addElement(new Gopher(i));
for (int i = 0; i < gophers.size(); i++)
GopherTrap.caughtYa(gophers.elementAt(i));
}
}
新的 GopherVector 類有一個類型為 Vector 的 private 成員(從 Vector 繼承有些麻煩,理由稍后便知)西设,而且方法也和 Vector 類似瓣铣。然而,它不會接收和產生普通 Object贷揽,只對 Gopher 對象
感興趣棠笑。
由于 GopherVector 只接收一個 Gopher(地鼠),所以假如我們使用:
gophers.addElement(new Pigeon());
就會在編譯期間獲得一條出錯消息擒滑。采用這種方式腐晾,盡管從編碼的角度看顯得更令人沉悶,但可以立即判斷出是否使用了正確的類型丐一。注意在使用 elementAt()時不必進行造型—— 它肯定是一個 Gopher
枚舉器
容納各種各樣的對象正是集合的首要任務。在 Vector 中淹冰, addElement()便是我們插入對象采用的方法库车,而 elementAt()是
提取對象的唯一方法。 Vector 非常靈活樱拴,我們可在任何時候選擇任何東西柠衍,并可使用不同的索引選擇多個元素。
若從更高的角度看這個問題晶乔,就會發(fā)現它的一個缺陷:需要事先知道集合的準確類型珍坊,否則無法使用。乍看來正罢,這一點似乎沒什么關系阵漏。但假若最開始決定使用Vector,后來在程序中又決定(考慮執(zhí)行效率的原因)改變成一個 List(屬于 Java1.2 集合庫的一部分)翻具,這時又該如何做呢履怯?
我們通常認為反復器是一種“輕量級”對象;也就是說裆泳,創(chuàng)建它只需付出極少的代價叹洲。但也正是由于這個原因,我們常發(fā)現反復器存在一些似乎很奇怪的限制工禾。例如运提,有些反復器只能朝一個方向移動蝗柔。
Java 的 Enumeration(枚舉,注釋②)便是具有這些限制的一個反復器的例子民泵。除下面這些外癣丧,不可再用它
做其他任何事情:
(1) 用一個名為 elements()的方法要求集合為我們提供一個 Enumeration。我們首次調用它的 nextElement()
時洪灯,這個 Enumeration 會返回序列中的第一個元素坎缭。
(2) 用 nextElement() 獲得下一個對象。
(3) 用 hasMoreElements()檢查序列中是否還有更多的對象
class Hamster {
private int hamsterNumber;
Hamster(int i) {
hamsterNumber = i;
}
public String toString() {
return "This is Hamster #" + hamsterNumber;
}
}
class Printer {
static void printAll(Enumeration e) {
while (e.hasMoreElements())
System.out.println(e.nextElement().toString());
}
}
public class HamsterMaze {
public static void main(String[] args) {
Vector v = new Vector();
for (int i = 0; i < 3; i++)
v.addElement(new Hamster(i));
Printer.printAll(v.elements());
}
}
仔細研究一下打印方法:
static void printAll(Enumeration e) {
while(e.hasMoreElements())
System.out.println(
e.nextElement().toString());
}
注意其中沒有與序列類型有關的信息签钩。我們擁有的全部東西便是Enumeration掏呼。為了解有關序列的情況,一個 Enumeration 便足夠了:可取得下一個對象铅檩,亦可知道是否已抵達了末尾憎夷。取得一系列對象,然后在其中遍歷昧旨,從而執(zhí)行一個特定的操作—— 這是一個頗有價值的編程概念
集合的類型
V e c t o r
崩潰 Java
Java 標準集合里包含了 toString()方法拾给,所以它們能生成自己的 String 表達方式,包括它們容納的對象兔沃。
例如在 Vector 中蒋得, toString()會在 Vector 的各個元素中步進和遍歷,并為每個元素調用 toString()乒疏。假定我們現在想打印出自己類的地址额衙。看起來似乎簡單地引用 this 即可(特別是 C++程序員有這樣做的傾向):
public class CrashJava {
public String toString() {
return "CrashJava address: " + this + "\n";
}
public static void main(String[] args) {
Vector v = new Vector();
for (int i = 0; i < 10; i++)
v.addElement(new CrashJava());
System.out.println(v);
}
}
此時發(fā)生的是字串的自動類型轉換怕吴。當我們使用下述語句時:
“CrashJava address: ” + this
編譯器就在一個字串后面發(fā)現了一個“ +”以及好象并非字串的其他東西窍侧,所以它會試圖將 this 轉換成一個字串。轉換時調用的是 toString()转绷,后者會產生一個遞歸調用伟件。若在一個 Vector 內出現這種事情,看起來堆棧就會溢出议经,同時違例控制機制根本沒有機會作出響應斧账。
若確實想在這種情況下打印出對象的地址,解決方案就是調用 Object 的 toString 方法爸业。此時就不必加入this其骄,只需使用 super.toString()。當然扯旷,采取這種做法也有一個前提:我們必須從 Object 直接繼承拯爽,或者沒有一個父類覆蓋了 toString 方法。
B i t S e t
BitSet 實際是由“ 二進制位”構成的一個 Vector钧忽。如果希望高效率地保存大量“開-關”信息毯炮,就應使用BitSet逼肯。它只有從尺寸的角度看才有意義;如果希望的高效率的訪問桃煎,那么它的速度會比使用一些固有類型的數組慢一些篮幢。
BitSet 的最小長度是一個長整數( Long)的長度: 64 位。這意味著假如我們準備保存比這更小的數據为迈,如 8 位數據三椿,那么 BitSet 就顯得浪費了。所以最好創(chuàng)建自己的類葫辐,用它容納自己的標志位搜锰。
S t a c k
Stack 有時也可以稱為“后入先出”( LIFO)集合。換言之耿战,我們在堆棧里最后“壓入”的東西將是以后第
一個“彈出”的蛋叼。和其他所有 Java 集合一樣,我們壓入和彈出的都是“對象”剂陡,所以必須對自己彈出的東西
進行“造型”解寝。
下面是一個簡單的堆棧示例扛禽,它能讀入數組的每一行同木,同時將其作為字串壓入堆棧有序。
public class Stacks {
static String[] months = { "January", "February", "March", "April", "May",
"June", "July", "August", "September", "October", "November",
"December" };
public static void main(String[] args) {
Stack stk = new Stack();
for (int i = 0; i < months.length; i++)
stk.push(months[i] + " ");
System.out.println("stk = " + stk);
// Treating a stack as a Vector:
stk.addElement("The last line");
System.out.println("element 5 = " + stk.elementAt(5));
System.out.println("popping elements:");
while (!stk.empty())
System.out.println(stk.pop());
}
}
months 數組的每一行都通過 push()繼承進入堆棧,稍后用 pop()從堆棧的頂部將其取出晕鹊。要聲明的一點是骆姐,Vector 操作亦可針對 Stack 對象進行。這可能是由繼承的特質決定的—— Stack“屬于”一種 Vector捏题。因此,能對 Vector 進行的操作亦可針對 Stack 進行肉渴,例如 elementAt()方法
H a s h t a b l e
Vector 允許我們用一個數字從一系列對象中作出選擇公荧,所以它實際是將數字同對象關聯起來了。
但假如我們想根據其他標準選擇一系列對象呢同规?堆棧就是這樣的一個例子:它的選擇標準是“最后壓入堆棧的東西”循狰。
這種“從一系列對象中選擇”的概念亦可叫作一個“映射”、“字典”或者“關聯數組”券勺。從概念上講绪钥,它看起來象一個 Vector,但卻不是通過數字來查找對象关炼,而是用另一個對象來查找它們程腹!這通常都屬于一個程序中的重要進程。
在 Java 中儒拂,這個概念具體反映到抽象類 Dictionary 身上寸潦。該類的接口是非常直觀的 size()告訴我們其中包含了多少元素色鸳; isEmpty()判斷是否包含了元素(是則為 true); put(Object key, Object value)添加一個值(我們希望的東西)见转,并將其同一個鍵關聯起來(想用于搜索它的東西)命雀; get(Object key)獲得與某個鍵對應的值;而 remove(Object Key)用于從列表中刪除“鍵-值”對斩箫。還可以使用枚舉技術: keys()產生對鍵的一個枚舉( Enumeration)吏砂;而 elements()產生對所有值的一個枚舉。這便是一個 Dict ionary(字典)的全部乘客。
public class AssocArray extends Dictionary {
private Vector keys = new Vector();
private Vector values = new Vector();
public int size() {
return keys.size();
}
public boolean isEmpty() {
return keys.isEmpty();
}
public Object put(Object key, Object value) {
keys.addElement(key);
values.addElement(value);
return key;
}
public Object get(Object key) {
int index = keys.indexOf(key);
// indexOf() Returns -1 if key not found:
if (index == -1)
return null;
return values.elementAt(index);
}
public Object remove(Object key) {
int index = keys.indexOf(key);
if (index == -1)
return null;
keys.removeElementAt(index);
Object returnval = values.elementAt(index);
values.removeElementAt(index);
return returnval;
}
public Enumeration keys() {
return keys.elements();
}
public Enumeration elements() {
return values.elements();
}
// Test it:
public static void main(String[] args) {
AssocArray aa = new AssocArray();
for (char c = 'a'; c <= 'z'; c++)
aa.put(String.valueOf(c), String.valueOf(c).toUpperCase());
char[] ca = { 'a', 'e', 'i', 'o', 'u' };
for (int i = 0; i < ca.length; i++)
System.out.println("Uppercase: " + aa.get(String.valueOf(ca[i])));
}
}
在對 AssocArray 的定義中狐血,我們注意到的第一個問題是它“擴展”了字典。這意味著 AssocArray 屬于Dictionary 的一種類型寨典,所以可對其發(fā)出與 Dictionary 一樣的請求氛雪。如果想生成自己的 Dictionary,而且就在這里進行耸成,那么要做的全部事情只是填充位于 Dictionary 內的所有方法(而且必須覆蓋所有方法报亩,因為
它們—— 除構建器外—— 都是抽象的)。
標準 Java 庫只包含 Dictionary 的一個變種井氢,名為 Hashtable(散列表弦追,注釋③)。 Java 的散列表具有與AssocArray 相同的接口(因為兩者都是從 Dictionary 繼承來的)花竞。但有一個方面卻反映出了差別:執(zhí)行效率劲件。若仔細想想必須為一個 get()做的事情,就會發(fā)現在一個 Vector 里搜索鍵的速度要慢得多约急。但此時用散列表卻可以加快不少速度零远。不必用冗長的線性搜索技術來查找一個鍵,而是用一個特殊的值厌蔽,名為“散列碼”牵辣。散列碼可以獲取對象中的信息,然后將其轉換成那個對象“相對唯一”的整數( int)奴饮。所有對象都有一個散列碼纬向,而 hashCode()是根類 Object 的一個方法。 Hashtable 獲取對象的 hashCode()戴卜,然后用它快速查找鍵逾条。
class Counter {
int i = 1;
public String toString() {
return Integer.toString(i);
}
}
class Statistics {
public static void main(String[] args) {
Hashtable ht = new Hashtable();
for (int i = 0; i < 10000; i++) {
// Produce a number between 0 and 20:
Integer r = new Integer((int) (Math.random() * 20));
if (ht.containsKey(r))
((Counter) ht.get(r)).i++;
else
ht.put(r, new Counter());
}
System.out.println(ht);
}
}
- 創(chuàng)建“關鍵”類
但在使用散列表的時候,一旦我們創(chuàng)建自己的類作為鍵使
用投剥,就會遇到一個很常見的問題师脂。例如,假設一套天氣預報系統(tǒng)將Groundhog(土拔鼠)對象匹配成Prediction(預報) 。這看起來非常直觀:我們創(chuàng)建兩個類危彩,然后將Groundhog 作為鍵使用攒磨,而將Prediction 作為值使用。如下所示:
class Groundhog {
int ghNumber;
Groundhog(int n) {
ghNumber = n;
}
}
class Prediction {
boolean shadow = Math.random() > 0.5;
public String toString() {
if (shadow)
return "Six more weeks of Winter!";
else
return "Early Spring!";
}
}
public class SpringDetector {
public static void main(String[] args) {
Hashtable ht = new Hashtable();
for (int i = 0; i < 10; i++)
ht.put(new Groundhog(i), new Prediction());
System.out.println("ht = " + ht + "\n");
System.out.println("Looking up prediction for groundhog #3:");
Groundhog gh = new Groundhog(3);
if (ht.containsKey(gh))
System.out.println((Prediction) ht.get(gh));
}
}
問題在于Groundhog 是從通用的 Object 根類繼承的(若當初未指
定基礎類汤徽,則所有類最終都是從 Object 繼承的)娩缰。事實上是用 Object 的 hashCode()方法生成每個對象的散列碼,而且默認情況下只使用它的對象的地址谒府。所以拼坎, Groundhog(3)的第一個實例并不會產生與Groundhog(3)第二個實例相等的散列碼,而我們用第二個實例進行檢索
或許認為此時要做的全部事情就是正確地覆蓋 hashCode()完疫。但這樣做依然行不能泰鸡,除非再做另一件事情:覆蓋也屬于 Object 一部分的 equals()。當散列表試圖判斷我們的鍵是否等于表內的某個鍵時壳鹤,就會用到這個方法盛龄。同樣地,默認的 Object.equals()只是簡單地比較對象地址芳誓,所以一個 Groundhog(3)并不等于
另一個 Groundhog(3)余舶。
因此,為了在散列表中將自己的類作為鍵使用锹淌,必須同時覆蓋 hashCode()和 equals()匿值,就象下面展示的那樣:
class Groundhog {
int ghNumber;
Groundhog(int n) {
ghNumber = n;
}
}
class Prediction {
boolean shadow = Math.random() > 0.5;
public String toString() {
if (shadow)
return "Six more weeks of Winter!";
else
return "Early Spring!";
}
}
public class SpringDetector {
public static void main(String[] args) {
Hashtable ht = new Hashtable();
for (int i = 0; i < 10; i++)
ht.put(new Groundhog(i), new Prediction());
System.out.println("ht = " + ht + "\n");
System.out.println("Looking up prediction for groundhog #3:");
Groundhog gh = new Groundhog(3);
if (ht.containsKey(gh))
System.out.println((Prediction) ht.get(gh));
}
}
Groundhog2.hashCode()將土拔鼠號碼作為一個標識符返回(在這個例子中,程序員需要保證沒有兩個土拔鼠用同樣的 ID 號碼并存)赂摆。為了返回一個獨一無二的標識符挟憔,并不需要 hashCode(), equals()方法必須能夠嚴格判斷兩個對象是否相等烟号。
equals()方法要進行兩種檢查:檢查對象是否為 null绊谭;若不為 null ,則繼續(xù)檢查是否為 Groundhog2 的一個實例(要用到 instanceof 關鍵字)汪拥。即使為了繼續(xù)執(zhí)行 equals()龙誊,它也應該是一個Groundhog2。正如大家看到的那樣喷楣,這種比較建立在實際 ghNumber 的基礎上。這一次一旦我們運行程序鹤树,就會看到它終于產生了正確的輸出(許多 Java 庫的類都覆蓋了 hashcode() 和 equals()方法铣焊,以便與自己提供的內容適應)。
再論枚舉器
將穿越一個序列的操作與那個序列的基礎結構分隔開罕伯。在下面的例子里曲伊, PrintData 類用一個 Enumeration 在一個序列中移動,并為每個對象都調用toString()方法。此時創(chuàng)建了兩個不同類型的集合:一個 Vector 和一個 Hashtable坟募。并且在它們里面分別填
充 Mouse 和 Hamster 對象岛蚤,由于 Enumeration 隱藏了基層集合的結構,所以PrintData 不知道或者不關心 Enumeration 來自于什么類型的集合:
class PrintData {
static void print(Enumeration e) {
while (e.hasMoreElements())
System.out.println(e.nextElement().toString());
}
}
class Enumerators2 {
public static void main(String[] args) {
Vector v = new Vector();
for (int i = 0; i < 5; i++)
v.addElement(new Mouse(i));
Hashtable h = new Hashtable();
for (int i = 0; i < 5; i++)
h.put(new Integer(i), new Hamster(i));
System.out.println("Vector");
PrintData.print(v.elements());
System.out.println("Hashtable");
PrintData.print(h.elements());
}
}
注意 PrintData.print()利用了這些集合中的對象屬于 Object 類這一事實懈糯,所以它調用了 toString()涤妒。但在
解決自己的實際問題時,經常都要保證自己的 Enumeration 穿越某種特定類型的集合赚哗。例如她紫,可能要求集合
中的所有元素都是一個 Shape(幾何形狀),并含有 draw()方法屿储。若出現這種情況贿讹,必須從
Enumeration.nextElement()返回的 Object 進行下溯造型,以便產生一個 Shape够掠。
排序
編寫通用的排序代碼時民褂,面臨的一個問題是必須根據對象的實際類型來執(zhí)行比較運算,從而實現正確的排序疯潭。當然赊堪,一個辦法是為每種不同的類型都寫一個不同的排序方法。然而袁勺,應認識到假若這樣做雹食,以后增加新類型時便不易實現代碼的重復利用。
程序設計一個主要的目標就是“將發(fā)生變化的東西同保持不變的東西分隔開”期丰。在這里群叶,保持不變的代碼是通用的排序算法,而每次使用時都要變化的是對象的實際比較方法钝荡。因此街立,我們不可將比較代碼“硬編碼”到多個不同的排序例程內,而是采用“回調”技術埠通。
利用回調赎离,經常發(fā)生變化的那部分代碼會封裝到它自己的類內纬霞,而總是保持相同的代碼則“回調”發(fā)生變化的代碼描扯。這樣一來姜骡,不同的對象就可以表達不同的比較方式蝉绷,同時向它們傳遞相同的排序代碼横蜒。
下面這個“接口”( Interface)展示了如何比較兩個對象租副,它將那些“要發(fā)生變化的東西”封裝在內:
interface Compare {
boolean lessThan(Object lhs, Object rhs);
boolean lessThanOrEqual(Object lhs, Object rhs);
}
對這兩種方法來說舟茶, lhs 代表本次比較中的“左手”對象闸餐,而 rhs 代表“右手”對象渗柿。
可創(chuàng)建 Vector 的一個子類个盆,通過 Compare 實現“快速排序”。對于這種算法,包括它的速度以及原理等等
public class SortVector extends Vector {
private Compare compare; // To hold the callback
public SortVector(Compare comp) {
compare = comp;
}
public void sort() {
quickSort(0, size() - 1);
}
private void quickSort(int left, int right) {
if (right > left) {
Object o1 = elementAt(right);
int i = left - 1;
int j = right;
while (true) {
while (compare.lessThan(elementAt(++i), o1))
;
while (j > 0)
if (compare.lessThanOrEqual(elementAt(--j), o1))
break; // out of while
if (i >= j)
break;
swap(i, j);
}
swap(i, right);
quickSort(left, i - 1);
quickSort(i + 1, right);
}
}
private void swap(int loc1, int loc2) {
Object tmp = elementAt(loc1);
setElementAt(elementAt(loc2), loc1);
setElementAt(tmp, loc2);
}
}
為使用 SortVector颊亮,必須創(chuàng)建一個類柴梆,令其為我們準備排序的對象實現 Compare。此時內部類并不顯得特別重要终惑,但對于代碼的組織卻是有益的绍在。下面是針對 String 對象的一個例子
public class StringSortTest {
static class StringCompare implements Compare {
public boolean lessThan(Object l, Object r) {
return ((String) l).toLowerCase().compareTo(
((String) r).toLowerCase()) < 0;
}
public boolean lessThanOrEqual(Object l, Object r) {
return ((String) l).toLowerCase().compareTo(
((String) r).toLowerCase()) <= 0;
}
}
public static void main(String[] args) {
SortVector sv = new SortVector(new StringCompare());
sv.addElement("d");
sv.addElement("A");
sv.addElement("C");
sv.addElement("c");
sv.addElement("b");
sv.addElement("B");
sv.addElement("D");
sv.addElement("a");
sv.sort();
Enumeration e = sv.elements();
while (e.hasMoreElements())
System.out.println(e.nextElement());
}
}
一旦設置好框架,就可以非常方便地重復使用象這樣的一個設計—— 只需簡單地寫一個類狠鸳,將“需要發(fā)生變化”的東西封裝進去揣苏,然后將一個對象傳給SortVector 即可
繼承( extends)在這兒用于創(chuàng)建一種新類型的 Vector—— 也就是說, SortVector 屬于一種 Vector件舵,并帶有一些附加的功能卸察。繼承在這里可發(fā)揮很大的作用,但了帶來了問題铅祸。它使一些方法具有了final 屬性坑质,所以不能覆蓋它們。如果想創(chuàng)建一個排好序的 Vector临梗,令其只接收和生成 String 對象涡扼,就會遇到麻煩。因為 addElement()和 elementAt()都具有 final 屬性盟庞,而且它們都是我們必須覆蓋的方法吃沪,否則便無法實現只能接收和產生 String 對象。
但在另一方面什猖,請考慮采用“合成”方法:將一個對象置入一個新類的內部票彪。此時,不是改寫上述代碼來達到這個目的不狮,而是在新類里簡單地使用一個 SortVector降铸。在這種情況下,用于實現 Compare 接口的內部類就可以“匿名”地創(chuàng)建
import java.util.*;
public class StrSortVector {
private SortVector v = new SortVector(
// Anonymous inner class:
new Compare() {
public boolean lessThan(Object l, Object r) {
return ((String) l).toLowerCase().compareTo(
((String) r).toLowerCase()) < 0;
}
public boolean lessThanOrEqual(Object l, Object r) {
return ((String) l).toLowerCase().compareTo(
((String) r).toLowerCase()) <= 0;
}
});
private boolean sorted = false;
public void addElement(String s) {
v.addElement(s);
sorted = false;
}
public String elementAt(int index) {
if(!sorted) {
v.sort();232
sorted = true;
}
return (String)v.elementAt(index);
}
public Enumeration elements() {
if (!sorted) {
v.sort();
sorted = true;
}
return v.elements();
}
// Test it:
public static void main(String[] args) {
StrSortVector sv = new StrSortVector();
sv.addElement("d");
sv.addElement("A");
sv.addElement("C");
sv.addElement("c");
sv.addElement("b");
sv.addElement("B");
sv.addElement("D");
sv.addElement("a");
Enumeration e = sv.elements();
while (e.hasMoreElements())
System.out.println(e.nextElement());
}
}
新集合
這張圖剛開始的時候可能讓人有點兒摸不著頭腦摇零,相信大家會真正理解它實際只有三個集合組件: Map推掸, List 和 Set。而且每個組件實際只有兩驻仅、三種實現方式
虛線框代表“接口”谅畅,點線框代表“抽象”類,而實線框代表普通(實際)類噪服。點線箭頭表示一個特定的類準備實現一個接口(在抽象類的情況下铃彰,則是“部分”實現一個接口)。雙線箭頭表示一個類可生成箭頭指向的那個類的對象芯咧。
致力于容納對象的接口是 Collection, List, Set 和 Map敬飒。在傳統(tǒng)情況下邪铲,我們需要寫大量代碼才能同這些接口打交道。而且為了指定自己想使用的準確類型无拗,必須在創(chuàng)建之初進行設置带到。所以可能創(chuàng)建下面這樣的一
個 List:
List x = new LinkedList();
當然,也可以決定將 x 作為一個 LinkedList 使用(而不是一個普通的 List)英染,并用 x 負載準確的類型信息揽惹。使用接口的好處就是一旦決定改變自己的實施細節(jié),要做的全部事情就是在創(chuàng)建的時候改變它四康,就象下面這樣:
List x = new ArrayList();
在類的分級結構中搪搏,可看到大量以“ Abstract ”(抽象)開頭的類,這剛開始可能會使人感覺迷惑闪金。它們實際上是一些工具疯溺,用于“部分”實現一個特定的接口。舉個例子來說哎垦,假如想生成自己的Set囱嫩,就不是從 Set接口開始,然后自行實現所有方法漏设。相反墨闲,我們可以從 AbstractSet 繼承,只需極少的工作即可得到自己的新類郑口。盡管如此鸳碧,新集合庫仍然包含了足夠的功能,可滿足我們的幾乎所有需求潘酗。所以考慮到我們的目的杆兵,可忽略所有以“ Abstract”開頭的類。
因此仔夺,在觀看這張示意圖時琐脏,真正需要關心的只有位于最頂部的“接口”以及普通(實際)類—— 均用實線方框包圍。通常需要生成實際類的一個對象缸兔,將其上溯造型為對應的接口日裙。以后即可在代碼的任何地方使用那個接口。下面是一個簡單的例子惰蜜,它用 String 對象填充一個集合昂拂,然后打印出集合內的每一個元素:
public class SimpleCollection {
public static void main(String[] args) {
Collection c = new ArrayList();
for (int i = 0; i < 10; i++)
c.add(Integer.toString(i));
Iterator it = c.iterator();
while (it.hasNext())
System.out.println(it.next());
}
}
main()的第一行創(chuàng)建了一個 ArrayList 對象,然后將其上溯造型成為一個集合抛猖。由于這個例子只使用了Collection 方法格侯,所以從 Collection 繼承的一個類的任何對象都可以正常工作鼻听。但 ArrayList 是一個典型的 Collection,它代替了 Vector 的位置联四。
add()方法的作用是將一個新元素置入集合里撑碴。然而,用戶文檔謹慎地指出 add()“保證這個集合包含了指定的元素”朝墩。這一點是為 Set 作鋪墊的醉拓,后者只有在元素不存在的前提下才會真的加入那個元素。對于ArrayList 以及其他任何形式的 List收苏, add()肯定意味著“直接加入”亿卤。
利用 iterator()方法,所有集合都能生成一個“反復器”( Iterator)鹿霸。反復器其實就象一個“枚舉”( Enumeration)排吴,是后者的一個替代物,只是:
(1) 它采用了一個歷史上默認杜跷、而且早在 OOP 中得到廣泛采納的名字(反復器)傍念。
(2) 采用了比 Enumeration 更短的名字: hasNext()代替了 hasMoreElement(),而 next()代替了nextElement()葛闷。
(3) 添加了一個名為 remove()的新方法憋槐,可刪除由 Iterator 生成的上一個元素。所以每次調用 next()的時候淑趾,只需調用 remove()一次
使用 C o l l e c t i o n s
下面這張表格總結了用一個集合能做的所有事情(亦可對 Set 和 List 做同樣的事情阳仔,盡管 List 還提供了一
些額外的功能)。 Map 不是從 Collection 繼承的扣泊,所以要單獨對待
boolean add(Object) *保證集合內包含了自變量近范。如果它沒有添加自變量,就返回 false(假)
boolean addAll(Collection) *添加自變量內的所有元素延蟹。如果沒有添加元素评矩,則返回 true(真)
void clear() *刪除集合內的所有元素
boolean contains(Object) 若集合包含自變量,就返回“真”
boolean containsAll(Collection) 若集合包含了自變量內的所有元素阱飘,就返回“真”
boolean isEmpty() 若集合內沒有元素斥杜,就返回“真”
Iterator iterator() 返回一個反復器,以用它遍歷集合的各元素
boolean remove(Object) *如自變量在集合里沥匈,就刪除那個元素的一個實例蔗喂。如果已進行了刪除,就返回
“真”
boolean removeAll(Collection) *刪除自變量里的所有元素高帖。如果已進行了任何刪除缰儿,就返回“真”
boolean retainAll(Collection) *只保留包含在一個自變量里的元素(一個理論的“交集”)。如果已進
行了任何改變散址,就返回“真”
int size() 返回集合內的元素數量
Object[] toArray() 返回包含了集合內所有元素的一個數組
*這是一個“可選的”方法乖阵,有的集合可能并未實現它宣赔。若確實如此,該方法就會遇到一個
UnsupportedOperatiionException瞪浸,即一個“操作不支持”違例拉背。
下面這個例子向大家演示了所有方法。同樣地默终,它們只對從集合繼承的東西有效钓觉,一個ArrayList 作為一種“不常用的分母”使用
public class Collection1 {
// Fill with 'size' elements, start
// counting at 'start':
public static Collection fill(Collection c, int start, int size) {
for (int i = start; i < start + size; i++)
c.add(Integer.toString(i));
return c;
}
// Default to a "start" of 0:
public static Collection fill(Collection c, int size) {
return fill(c, 0, size);
}
// Default to 10 elements:
public static Collection fill(Collection c) {
return fill(c, 0, 10);
}
// Create & upcast to Collection:
public static Collection newCollection() {
return fill(new ArrayList());
// ArrayList is used for simplicity, but it's
// only seen as a generic Collection
// everywhere else in the program.
}
// Fill a Collection with a range of values:
public static Collection newCollection(int start, int size) {
return fill(new ArrayList(), start, size);
}
// Moving through a List with an iterator:
public static void print(Collection c) {
for (Iterator x = c.iterator(); x.hasNext();)
System.out.print(x.next() + " ");
System.out.println();
}
public static void main(String[] args) {
Collection c = newCollection();
c.add("ten");
c.add("eleven");
print(c);
// Make an array from the List:
Object[] array = c.toArray();
// Make a String array from the List:
String[] str = (String[]) c.toArray(new String[1]);
// Find max and min elements; this means
// different things depending on the way
// the Comparable interface is implemented:
System.out.println("Collections.max(c) = " + Collections.max(c));
System.out.println("Collections.min(c) = " + Collections.min(c));
// Add a Collection to another Collection
c.addAll(newCollection());
print(c);
c.remove("3"); // Removes the first one
print(c);
c.remove("3"); // Removes the second one
print(c);
// Remove all components that are in the
// argument collection:
c.removeAll(newCollection());
print(c);
c.addAll(newCollection());
print(c);
// Is an element in this Collection?
System.out.println("c.contains(\"4\") = " + c.contains("4"));
// Is a Collection in this Collection?
System.out.println("c.containsAll(newCollection()) = "
+ c.containsAll(newCollection()));
Collection c2 = newCollection(5, 3);
// Keep all the elements that are in both
// c and c2 (an intersection of sets):
c.retainAll(c2);
print(c);
// Throw away all the elements in c that
// also appear in c2:
c.removeAll(c2);
System.out.println("c.isEmpty() = " + c.isEmpty());
c = newCollection();
print(c);
c.clear(); // Remove all elements
System.out.println("after c.clear():");
print(c);
}
}
newCollection()的兩個版本都創(chuàng)建了 ArrayList妇智,用于包含不同的數據集,并將它們作為集合對象返回派歌。所以很明顯床估,除了 Collection 接口之外含滴,不會再用到其他什么。
使用 L i s t s
List(接口) 順序是 List 最重要的特性丐巫;它可保證元素按照規(guī)定的順序排列谈况。 List 為 Collection 添加了大量方法,以便我們在 List 中部插入和刪除元素(只推薦對 LinkedList 這樣做)递胧。 List 也會生成一個ListIterator(列表反復器)碑韵,利用它可在一個列表里朝兩個方向遍歷,同時插入和刪除位于列表中部的元素(同樣地缎脾,只建議對 LinkedList 這樣做)
ArrayList 由一個數組后推得到的 List祝闻。作為一個常規(guī)用途的對象容器使用,用于替換原先的 Vector遗菠。允許我們快速訪問元素联喘,但在從列表中部插入和刪除元素時,速度卻嫌稍慢辙纬。一般只應該用ListIterator 對一個 ArrayList 進行向前和向后遍歷豁遭,不要用它刪除和插入元素;與 LinkedList 相比贺拣,它的效率要低許多LinkedList 提供優(yōu)化的順序訪問性能蓖谢,同時可以高效率地在列表中部進行插入和刪除操作。但在進行隨機訪問時纵柿,速度卻相當慢蜈抓,此時應換用 ArrayList。
也提供了 addFirst()昂儒, addLast()沟使, getFirst(),getLast()渊跋, removeFirst() 以及 removeLast()
(未在任何接口或基礎類中定義)腊嗡,以便將其作為一個規(guī)格着倾、隊列以及一個雙向隊列使用
public class List1 {
// Wrap Collection1.fill() for convenience:
public static List fill(List a) {
return (List) Collection1.fill(a);
}
// You can use an Iterator, just as with a
// Collection, but you can also use random
// access with get():
public static void print(List a) {
for (int i = 0; i < a.size(); i++)
System.out.print(a.get(i) + " ");
System.out.println();
}
static boolean b;
static Object o;
static int i;
static Iterator it;
static ListIterator lit;
public static void basicTest(List a) {
a.add(1, "x"); // Add at location 1
a.add("x"); // Add at end
// Add a collection:
a.addAll(fill(new ArrayList()));
// Add a collection starting at location 3:
a.addAll(3, fill(new ArrayList()));
b = a.contains("1"); // Is it in there?
// Is the entire collection in there?
b = a.containsAll(fill(new ArrayList()));
// Lists allow random access, which is cheap
// for ArrayList, expensive for LinkedList:
o = a.get(1); // Get object at location 1
i = a.indexOf("1"); // Tell index of object
// indexOf, starting search at location 2:
i = a.indexOf("1", 2);
b = a.isEmpty(); // Any elements inside?
it = a.iterator(); // Ordinary Iterator
lit = a.listIterator(); // ListIterator
lit = a.listIterator(3); // Start at loc 3
i = a.lastIndexOf("1"); // Last match
i = a.lastIndexOf("1", 2); // ...after loc 2
a.remove(1); // Remove location 1
a.remove("3"); // Remove this object
a.set(1, "y"); // Set location 1 to "y"
// Keep everything that's in the argument
// (the intersection of the two sets):
a.retainAll(fill(new ArrayList()));
// Remove elements in this range:
a.removeRange(0, 2);
// Remove everything that's in the argument:
a.removeAll(fill(new ArrayList()));
i = a.size(); // How big is it?
a.clear(); // Remove all elements
}
public static void iterMotion(List a) {
ListIterator it = a.listIterator();
b = it.hasNext();
b = it.hasPrevious();
o = it.next();
i = it.nextIndex();
o = it.previous();
i = it.previousIndex();
}
public static void iterManipulation(List a) {
ListIterator it = a.listIterator();
it.add("47");
// Must move to an element after add():
it.next();
// Remove the element that was just produced:
it.remove();
// Must move to an element after remove():
it.next();
// Change the element that was just produced:
it.set("47");
}
public static void testVisual(List a) {
print(a);
List b = new ArrayList();
fill(b);
System.out.print("b = ");
print(b);
a.addAll(b);
a.addAll(fill(new ArrayList()));
print(a);
// Shrink the list by removing all the
// elements beyond the first 1/2 of the list
System.out.println(a.size());
System.out.println(a.size() / 2);
a.removeRange(a.size() / 2, a.size() / 2 + 2);
print(a);
// Insert, remove, and replace elements
// using a ListIterator:
ListIterator x = a.listIterator(a.size() / 2);
x.add("one");
print(a);
System.out.println(x.next());
x.remove();
System.out.println(x.next());
x.set("47");
print(a);
// Traverse the list backwards:
x = a.listIterator(a.size());
while (x.hasPrevious())
System.out.print(x.previous() + " ");
System.out.println();
System.out.println("testVisual finished");
}
// There are some things that only
// LinkedLists can do:
public static void testLinkedList() {
LinkedList ll = new LinkedList();
Collection1.fill(ll, 5);
print(ll);
// Treat it like a stack, pushing:
ll.addFirst("one");
ll.addFirst("two");
print(ll);
// Like "peeking" at the top of a stack:
System.out.println(ll.getFirst());
// Like popping a stack:
System.out.println(ll.removeFirst());
System.out.println(ll.removeFirst());
// Treat it like a queue, pulling elements
// off the tail end:
System.out.println(ll.removeLast());
// With the above operations, it's a dequeue!
print(ll);
}
public static void main(String args[]) {
// Make and fill a new list each time:
basicTest(fill(new LinkedList()));
basicTest(fill(new ArrayList()));
iterMotion(fill(new LinkedList()));
iterMotion(fill(new ArrayList()));
iterManipulation(fill(new LinkedList()));
iterManipulation(fill(new ArrayList()));
testVisual(fill(new LinkedList()));
testLinkedList();
}
}
在 basicTest()和 iterMotiion() 中,只是簡單地發(fā)出調用燕少,以便揭示出正確的語法卡者。而且盡管捕獲了返回
值,但是并未使用它客们。在某些情況下崇决,之所以不捕獲返回值,是由于它們沒有什么特別的用處底挫。在正式使用
它們前恒傻,應仔細研究一下自己的聯機文檔,掌握這些方法完整建邓、正確的用法盈厘。
ArrayList使用實例
import java.awt.List;
import java.util.ArrayList;
import java.util.Iterator;
/**
* @author sihai
* @time 2018/4/19
* ArrayList用法示例說明
*
*/
public class Main {
public static void main(String[] args) {
//ArrayList用法示例
ArrayList<String> m_ArrayList=new ArrayList<String>();
m_ArrayList.add("Evankaka");
m_ArrayList.add("sihai");
m_ArrayList.add("德德");
m_ArrayList.add("Evankaka");
m_ArrayList.add("小紅");
m_ArrayList.set(2,"sihai2");// 將索引位置為2的對象修改
m_ArrayList.add(3,"好好學java");// 將對象添加到索引位置為3的位置
//ArrayList遍歷方法1
Iterator<String> it_ArrayList = m_ArrayList.iterator();
System.out.println("ArrayList遍歷方法1");
while (it_ArrayList.hasNext()) {
System.out.println(it_ArrayList.next());
}
//ArrayList遍歷方法2
System.out.println("ArrayList遍歷方法2");
for(Object o:m_ArrayList){
System.out.println(o);
}
//ArrayList遍歷方法2
System.out.println("ArrayList遍歷方法3");
for(int i = 0; i<m_ArrayList.size(); i++){
System.out.println(m_ArrayList.get(i));
}
//刪除元素
m_ArrayList.remove("Evankaka");
it_ArrayList = m_ArrayList.iterator();
System.out.println("ArrayList刪除元素后的遍歷");
while (it_ArrayList.hasNext()) {
String m_String=it_ArrayList.next();
if(m_String.equals("好好學java")){
it_ArrayList.remove();
}else{
System.out.println(m_String);
}
}
}
}
輸出結果:
ArrayList遍歷方法1
Evankaka
sihai
sihai2
好好學java
Evankaka
小紅
ArrayList遍歷方法2
Evankaka
sihai
sihai2
好好學java
Evankaka
小紅
ArrayList遍歷方法3
Evankaka
sihai
sihai2
好好學java
Evankaka
小紅
ArrayList刪除元素后的遍歷
sihai
sihai2
Evankaka
小紅
ArrayList注意
(1)使用Iterator迭代集合過程中,不可修改集合元素官边,否則會引發(fā)異常沸手。并且Iterator只能向后迭代
(2)如果你想在循環(huán)過程中去掉某個元素,只能調用it.remove方法, 不能使用list.remove方法, 否則一定出并發(fā)訪問的錯誤.
使用 S e t s
Set完全就是一個 Collection,只是具有不同的行為(這是實例和多形性最理想的應用:用于表達不同的行為)注簿。在這里契吉,一個 Set 只允許每個對象存在一個實例(正如大家以后會看到的那樣,一個對象的“值”的構成是相當復雜的)
Set(接口) 添加到 Set 的每個元素都必須是獨一無二的滩援;否則 Set 就不會添加重復的元素栅隐。添加到 Set 里的對象必須定義 equals(),從而建立對象的唯一性玩徊。 Set 擁有與 Collection 完全相同的接口租悄。一個 Set 不能保證自己可按任何特定的順序維持自己的元素
HashSet 用于除非常小的以外的所有 Set。對象也必須定義 hashCode()
ArraySet 由一個數組后推得到的 Set恩袱。面向非常小的 Set 設計泣棋,特別是那些需要頻繁創(chuàng)建和刪除的。對于小
Set畔塔,與 HashSet 相比潭辈, ArraySet 創(chuàng)建和反復所需付出的代價都要小得多。但隨著 Set 的增大澈吨,它的性能也
會大打折扣把敢。不需要 HashCode()
TreeSet 由一個“紅黑樹”后推得到的順序 Set(注釋⑦)。這樣一來谅辣,我們就可以從一個 Set 里提到一個
順序集合
public class Set1 {
public static void testVisual(Set a) {
Collection1.fill(a);
Collection1.fill(a);
Collection1.fill(a);
Collection1.print(a); // No duplicates!
// Add another set to this one:
a.addAll(a);
a.add("one");
a.add("one");
a.add("one");
Collection1.print(a);
// Look something up:
System.out.println("a.contains(\"one\"): " + a.contains("one"));
}
public static void main(String[] args) {
testVisual(new HashSet());
testVisual(new TreeSet());
}
}
重復的值被添加到 Set修赞,但在打印的時候,我們會發(fā)現 Set 只接受每個值的一個實例桑阶。運行這個程序時柏副,會注意到由 HashSet 維持的順序與 ArraySet 是不同的勾邦。這是由于它們采用了不同的方法來保存元素,以便它們以后的定位割择。 ArraySet 保持著它們的順序狀態(tài)眷篇,而 HashSet 使用一個散列函數,這是特別為快速檢索設計的)荔泳。
class MyType implements Comparable {
private int i;
public MyType(int n) {
i = n;
}
public boolean equals(Object o) {
return (o instanceof MyType) && (i == ((MyType) o).i);
}
public int hashCode() {
return i;
}
public String toString() {
return i + " ";
}
public int compareTo(Object o) {
int i2 = ((MyType) o).i;
return (i2 < i ? -1 : (i2 == i ? 0 : 1));
}
}
public class Set2 {
public static Set fill(Set a, int size) {
for (int i = 0; i < size; i++)
a.add(new MyType(i));
return a;
}
public static Set fill(Set a) {
return fill(a, 10);
}
public static void test(Set a) {
fill(a);
fill(a); // Try to add duplicates
fill(a);
a.addAll(fill(new TreeSet()));
System.out.println(a);
}
public static void main(String[] args) {
test(new HashSet());
test(new TreeSet());
}
}
但只有要把類置入一個 HashSet 的前提下蕉饼,才有必要使用 hashCode()—— 這種情況是完全有可能的,因為通常應先選擇作為一個 Set 實現玛歌。
使用 M a p s
Map(接口) 維持“鍵-值”對應關系(對)椎椰,以便通過一個鍵查找相應的值
HashMap 基于一個散列表實現(用它代替 Hashtable)。針對“鍵-值”對的插入和檢索沾鳄,這種形式具有最穩(wěn)定的性能∪泛可通過構建器對這一性能進行調整译荞,以便設置散列表的“能力”和“裝載因子”
ArrayMap 由一個 ArrayList 后推得到的 Map。對反復的順序提供了精確的控制休弃。面向非常小的 Map 設計吞歼,特別是那些需要經常創(chuàng)建和刪除的。對于非常小的Map塔猾,創(chuàng)建和反復所付出的代價要比
HashMap 低得多篙骡。但在Map 變大以后,性能也會相應地大幅度降低
TreeMap 在一個“紅-黑”樹的基礎上實現丈甸。查看鍵或者“鍵-值”對時糯俗,它們會按固定的順序排列(取決于 Comparable 或 Comparator,稍后即會講到)睦擂。 TreeMap 最大的好處就是我們得到的是已排好序的結果得湘。TreeMap 是含有 subMap()方法的唯一一種 Map,利用它可以返回樹的一部分
public class Map1 {
public final static String[][] testData1 = {
{ "Happy", "Cheerful disposition" },
{ "Sleepy", "Prefers dark, quiet places" },
{ "Grumpy", "Needs to work on attitude" },
{ "Doc", "Fantasizes about advanced degree" },
{ "Dopey", "'A' for effort" },
{ "Sneezy", "Struggles with allergies" },
{ "Bashful", "Needs self-esteem workshop" }, };
public final static String[][] testData2 = {
{ "Belligerent", "Disruptive influence" },
{ "Lazy", "Motivational problems" },
{ "Comatose", "Excellent behavior" } };
public static Map fill(Map m, Object[][] o) {
for (int i = 0; i < o.length; i++)
m.put(o[i][0], o[i][1]);
return m;
}
// Producing a Set of the keys:
public static void printKeys(Map m) {
System.out.print("Size = " + m.size() + ", ");
System.out.print("Keys: ");
Collection1.print(m.keySet());
}
// Producing a Collection of the values:
public static void printValues(Map m) {
System.out.print("Values: ");
Collection1.print(m.values());
}
// Iterating through Map.Entry objects (pairs):
public static void print(Map m) {
Collection entries = m.entries();
Iterator it = entries.iterator();
while (it.hasNext()) {
Map.Entry e = (Map.Entry) it.next();
System.out.println("Key = " + e.getKey() + ", Value = "
+ e.getValue());
}
}
public static void test(Map m) {
fill(m, testData1);
// Map has 'Set' behavior for keys:
fill(m, testData1);
printKeys(m);
printValues(m);
print(m);
String key = testData1[4][0];
String value = testData1[4][1];
System.out.println("m.containsKey(\"" + key + "\"): "
+ m.containsKey(key));
System.out.println("m.get(\"" + key + "\"): " + m.get(key));
System.out.println("m.containsValue(\"" + value + "\"): "
+ m.containsValue(value));
Map m2 = fill(new TreeMap(), testData2);
m.putAll(m2);
printKeys(m);
m.remove(testData2[0][0]);
printKeys(m);
m.clear();
System.out.println("m.isEmpty(): " + m.isEmpty());
fill(m, testData1);
// Operations on the Set change the Map:
m.keySet().removeAll(m.keySet());
System.out.println("m.isEmpty(): " + m.isEmpty());
}
public static void main(String args[]) {
System.out.println("Testing HashMap");
test(new HashMap());
System.out.println("Testing TreeMap");
test(new TreeMap());
}
}
遍歷map實例
package com.test;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class Test {
public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();
map.put("first", "linlin");
map.put("second", "好好學java");
map.put("third", "sihai");
map.put("first", "sihai2");
// 第一種:通過Map.keySet遍歷key和value
System.out.println("===================通過Map.keySet遍歷key和value:===================");
for (String key : map.keySet()) {
System.out.println("key= " + key + " and value= " + map.get(key));
}
// 第二種:通過Map.entrySet使用iterator遍歷key和value
System.out.println("===================通過Map.entrySet使用iterator遍歷key和value:===================");
Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> entry = it.next();
System.out.println("key= " + entry.getKey() + " and value= "
+ entry.getValue());
}
// 第三種:通過Map.entrySet遍歷key和value
System.out.println("===================通過Map.entrySet遍歷key和value:===================");
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println("key= " + entry.getKey() + " and value= "
+ entry.getValue());
}
// 第四種:通過Map.values()遍歷所有的value顿仇,但是不能遍歷鍵key
System.out.println("===================通過Map.values()遍歷所有的value:===================");
for (String v : map.values()) {
System.out.println("value= " + v);
}
}
}
輸出結果如下:
===================通過Map.keySet遍歷key和value:===================
key= third and value= sihai
key= first and value= sihai2
key= second and value= 好好學java
===================通過Map.entrySet使用iterator遍歷key和value:===================
key= third and value= sihai
key= first and value= sihai2
key= second and value= 好好學java
===================通過Map.entrySet遍歷key和value:===================
key= third and value= sihai
key= first and value= sihai2
key= second and value= 好好學java
===================通過Map.values()遍歷所有的value:===================
value= sihai
value= sihai2
value= 好好學java
決定使用哪種集合
ArrayList淘正, LinkedList 以及 Vector(大致等價于 ArrayList)都實現了List 接口,所以無論選用哪一個臼闻,我們的程序都會得到類似的結果鸿吆。然而, ArrayList(以及 Vector)是由一個數組后推得到的述呐;而 LinkedList 是根據常規(guī)的雙重鏈接列表方式實現的惩淳,因為每個單獨的對象都包含了數據以及指向列表內前后元素的句柄。正是由于這個原因市埋,假如想在一個列表中部進行大量插入和刪除操作黎泣,那么 LinkedList 無疑是最恰當的選擇( LinkedList 還有一些額外的功能恕刘,建立于AbstractSequentialList 中)。若非如此抒倚,就情愿選擇 ArrayList褐着,它的速度可能要快一些。
作為另一個例子托呕, Set 既可作為一個 ArraySet 實現含蓉,亦可作為 HashSet 實現。 ArraySet 是由一個 ArrayList
后推得到的项郊,設計成只支持少量元素馅扣,特別適合要求創(chuàng)建和刪除大量 Set 對象的場合使用。然而着降,一旦需要在自己的 Set 中容納大量元素差油, ArraySet 的性能就會大打折扣。寫一個需要 Set 的程序時任洞,應默認選擇HashSet蓄喇。而且只有在某些特殊情況下(對性能的提升有迫切的需求),才應切換到 ArraySet交掏。
1. 決定使用何種 List
為體會各種 List 實施方案間的差異妆偏,最簡便的方法就是進行一次性能測驗。
public class ListPerformance {
private static final int REPS = 100;
private abstract static class Tester {
String name;
int size; // Test quantity
Tester(String name, int size) {
this.name = name;
this.size = size;
}
abstract void test(List a);
}
private static Tester[] tests = { new Tester("get", 300) {
void test(List a) {
for (int i = 0; i < REPS; i++) {
for (int j = 0; j < a.size(); j++)
a.get(j);
}
}
}, new Tester("iteration", 300) {
void test(List a) {
for (int i = 0; i < REPS; i++) {
Iterator it = a.iterator();
while (it.hasNext())
it.next();
}
}
}, new Tester("insert", 1000) {
void test(List a) {
int half = a.size() / 2;
String s = "test";
ListIterator it = a.listIterator(half);
for (int i = 0; i < size * 10; i++)
it.add(s);
}
}, new Tester("remove", 5000) {
void test(List a) {
ListIterator it = a.listIterator(3);
while (it.hasNext()) {
it.next();
it.remove();
}
}
}, };
public static void test(List a) {
// A trick to print out the class name:
System.out.println("Testing " + a.getClass().getName());
for (int i = 0; i < tests.length; i++) {
Collection1.fill(a, tests[i].size);
System.out.print(tests[i].name);
long t1 = System.currentTimeMillis();
tests[i].test(a);
long t2 = System.currentTimeMillis();
System.out.println(": " + (t2 - t1));
}
}
public static void main(String[] args) {
test(new ArrayList());
test(new LinkedList());
}
}
內部類 Tester 是一個抽象類盅弛,用于為特定的測試提供一個基礎類钱骂。它包含了一個要在測試開始時打印的字串、一個用于計算測試次數或元素數量的 size 參數挪鹏、用于初始化字段的一個構建器以及一個抽象方法test()见秽。 test()做的是最實際的測試工作。各種類型的測試都集中到一個地方: tests 數組讨盒。我們用繼承于Tester 的不同匿名內部類來初始化該數組张吉。為添加或刪除一個測試項目,只需在數組里簡單地添加或移去一個內部類定義即可催植,其他所有工作都是自動進行的肮蛹。
Type Get Iteration Insert Remove
A r r a y L i s t 110 490 3790 8730
LinkedList 1980 220 110 110
在 ArrayList 中進行隨機訪問(即 get())以及循環(huán)反復是最劃得來的抬伺;但對于 LinkedList 卻是一個不小的開銷喷众。但另一方面,在列表中部進行插入和刪除操作對于 LinkedList 來說卻比 ArrayList 劃算得多掂僵。我們最好的做法也許是先選擇一個 ArrayList 作為自己的默認起點稿辙。以后若發(fā)現由于大量的插入和刪除造成了性能的降低昆码,再考慮換成 LinkedList 不遲。
2. 決定使用何種 Set
可在 ArraySet 以及 HashSet 間作出選擇,具體取決于 Set 的大懈逞省(如果需要從一個 Set 中獲得一個順序列表旧噪,請用 TreeSet;)
public class SetPerformance {
private static final int REPS = 200;
private abstract static class Tester {
String name;
Tester(String name) {
this.name = name;
}
abstract void test(Set s, int size);
}
private static Tester[] tests = { new Tester("add") {
void test(Set s, int size) {
for (int i = 0; i < REPS; i++) {
s.clear();
Collection1.fill(s, size);
}
}
}, new Tester("contains") {
void test(Set s, int size) {
for (int i = 0; i < REPS; i++)
for (int j = 0; j < size; j++)
s.contains(Integer.toString(j));
}
}, new Tester("iteration") {
void test(Set s, int size) {
for (int i = 0; i < REPS * 10; i++) {
Iterator it = s.iterator();
while (it.hasNext())
it.next();
}
}
}, };
public static void test(Set s, int size) {
// A trick to print out the class name:
System.out.println("Testing " + s.getClass().getName() + " size "
+ size);
Collection1.fill(s, size);
for (int i = 0; i < tests.length; i++) {
System.out.print(tests[i].name);
long t1 = System.currentTimeMillis();
tests[i].test(s, size);
long t2 = System.currentTimeMillis();
System.out.println(": " + ((double) (t2 - t1) / (double) size));
}
}
public static void main(String[] args) {
// Small:
test(new TreeSet(), 10);
test(new HashSet(), 10);
// Medium:
test(new TreeSet(), 100);
test(new HashSet(), 100);
// Large:
test(new HashSet(), 1000);
test(new TreeSet(), 1000);
}
}
進行 add()以及 contains()操作時脓匿, HashSet 顯然要比 ArraySet 出色得多淘钟,而且性能明顯與元素的多寡關系不大。一般編寫程序的時候陪毡,幾乎永遠用不著使用 ArraySet
3.決定使用何種 Map
選擇不同的 Map 實施方案時米母,注意 Map 的大小對于性能的影響是最大的,下面這個測試程序清楚地闡示了這
一點:
public class MapPerformance {
private static final int REPS = 200;
public static Map fill(Map m, int size) {
for (int i = 0; i < size; i++) {
String x = Integer.toString(i);
m.put(x, x);
}
return m;
}
private abstract static class Tester {
String name;
Tester(String name) {
this.name = name;
}
abstract void test(Map m, int size);
}
private static Tester[] tests = { new Tester("put") {
void test(Map m, int size) {
for (int i = 0; i < REPS; i++) {
m.clear();
fill(m, size);
}
}
}, new Tester("get") {
void test(Map m, int size) {
for (int i = 0; i < REPS; i++)
for (int j = 0; j < size; j++)
m.get(Integer.toString(j));
}
}, new Tester("iteration") {
void test(Map m, int size) {
for (int i = 0; i < REPS * 10; i++) {
Iterator it = m.entries().iterator();
while (it.hasNext())
it.next();
}
}
}, };
public static void test(Map m, int size) {
// A trick to print out the class name:
System.out.println("Testing " + m.getClass().getName() + " size "
+ size);
fill(m, size);
for (int i = 0; i < tests.length; i++) {
System.out.print(tests[i].name);
long t1 = System.currentTimeMillis();
tests[i].test(m, size);
long t2 = System.currentTimeMillis();
System.out.println(": " + ((double) (t2 - t1) / (double) size));
}
}
public static void main(String[] args) {
// Small:
test(new Hashtable(), 10);
test(new HashMap(), 10);
test(new TreeMap(), 10);
// Medium:
test(new Hashtable(), 100);
test(new HashMap(), 100);
test(new TreeMap(), 100);
// Large:
test(new HashMap(), 1000);
test(new Hashtable(), 1000);
test(new TreeMap(), 1000);
}
}
由于 Map 的大小是最嚴重的問題毡琉,所以程序的計時測試按Map 的大刑鳌(或容量)來分割時間,以便得到令人
信服的測試結果桅滋。下面列出一系列結果(在你的機器上可能不同):
即使大小為 10慧耍, ArrayMap 的性能也要比 HashMap 差—— 除反復循環(huán)時以外。而在使用 Map 時丐谋,反復的作用通常并不重要( get()通常是我們時間花得最多的地方)蜂绎。 TreeMap 提供了出色的 put()以及反復時間,但 get()的性能并不佳笋鄙。但是,我們?yōu)槭裁慈匀恍枰褂肨reeMap 呢怪瓶?這樣一來萧落,我們可以不把它作為 Map 使用,而作為創(chuàng)建順序列表的一種途徑洗贰。一旦填充了一個 TreeMap找岖,就可以調用 keySet()來獲得鍵的一個 Set“景象”。然后用 toArray()產生包含了那些鍵的一個數組敛滋。隨后许布,可用 static 方法 Array.binarySearch()快速查找排好序的數組中的內容。當然绎晃,也許只有在 HashMap 的行為不可接受的時候蜜唾,才需要采用這種做法。因為HashMap 的設計宗旨就是進行快速的檢索操作庶艾。最后袁余,當我們使用 Map 時,首要的選擇應該是 HashMap咱揍。只有在極少數情況下才需要考慮其他方法
public class MapCreation {
public static void main(String[] args) {
final long REPS = 100000;
long t1 = System.currentTimeMillis();
System.out.print("Hashtable");
for (long i = 0; i < REPS; i++)
new Hashtable();
long t2 = System.currentTimeMillis();
System.out.println(": " + (t2 - t1));
t1 = System.currentTimeMillis();
System.out.print("TreeMap");
for (long i = 0; i < REPS; i++)
new TreeMap();
t2 = System.currentTimeMillis();
System.out.println(": " + (t2 - t1));
t1 = System.currentTimeMillis();
System.out.print("HashMap");
for (long i = 0; i < REPS; i++)
new HashMap();
t2 = System.currentTimeMillis();
System.out.println(": " + (t2 - t1));
}
}
TreeMap 的創(chuàng)建速度比其他兩種類型明顯快得多(但你應親自嘗試一下颖榜,因為據說新版本可能會改善 ArrayMap 的性能)。考慮到這方面的原因掩完,同時由于前述 TreeMap 出色的 put()性能噪漾,所以如
果需要創(chuàng)建大量 Map,而且只有在以后才需要涉及大量檢索操作且蓬,那么最佳的策略就是:創(chuàng)建和填充TreeMap欣硼;以后檢索量增大的時候,再將重要的 TreeMap 轉換成 HashMap—— 使用 HashMap(Map)構建器缅疟。
未支持的操作
利用 static(靜態(tài))數組 Arrays.toList()分别,也許能將一個數組轉換成 List
public class Unsupported {
private static String[] s = { "one", "two", "three", "four", "five", "six",
"seven", "eight", "nine", "ten", };
static List a = Arrays.toList(s);
static List a2 = Arrays.toList(new String[] { s[3], s[4], s[5] });
public static void main(String[] args) {
Collection1.print(a); // Iteration
System.out.println("a.contains(" + s[0] + ") = " + a.contains(s[0]));
System.out.println("a.containsAll(a2) = " + a.containsAll(a2));
System.out.println("a.isEmpty() = " + a.isEmpty());
System.out.println("a.indexOf(" + s[5] + ") = " + a.indexOf(s[5]));
// Traverse backwards:
ListIterator lit = a.listIterator(a.size());
while (lit.hasPrevious())
System.out.print(lit.previous());
System.out.println();
// Set the elements to different values:
for (int i = 0; i < a.size(); i++)
a.set(i, "47");
Collection1.print(a);
// Compiles, but won't run:
lit.add("X"); // Unsupported operation
a.clear(); // Unsupported
a.add("eleven"); // Unsupported
a.addAll(a2); // Unsupported
a.retainAll(a2); // Unsupported
a.remove(s[0]); // Unsupported
a.removeAll(a2); // Unsupported
}
}
從中可以看出,實際只實現了 Collection 和 List 接口的一部分存淫。剩余的方法導致了不受歡迎的一種情況耘斩,名為UnsupportedOperationException。
在實現那些接口的集合類中桅咆,或者提供括授、或者沒有提供對那些方法的支持。若調用一個未獲支持的方法岩饼,就會導致一個 UnsupportedOperationException(操作未支持違例)荚虚,這表明出現了一個編程錯誤。
Arrays.toList()產生了一個 List(列表)籍茧,該列表是由一個固定長度的數組后推出來的版述。因此唯一能夠支持的就是那些不改變數組長度的操作。在另一方面寞冯,若請求一個新接口表達不同種類的行為(可能叫作“ FixedSizeList” —— 固定長度列表)渴析,就有遭遇更大的復雜程度的危險。這樣一來吮龄,以后試圖使用庫的時候俭茧,很快就會發(fā)現自己不知從何處下手。
對那些采用 Collection漓帚, List母债, Set 或者 Map 作為參數的方法,它們的文檔應當指出哪些可選的方法是必須實現的尝抖。舉個例子來說毡们,排序要求實現 set()和 Iterator.set()方法,但不包括 add()和 remove()昧辽。
排序和搜索
數組
Arrays類為所有基本數據類型的數組提供了一個過載的 sort()和 binarySearch()
漏隐,它們亦可用于 String 和Object。
public class Array1 {
static Random r = new Random();
static String ssource = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz";
static char[] src = ssource.toCharArray();
// Create a random String
public static String randString(int length) {
char[] buf = new char[length];
int rnd;
for (int i = 0; i < length; i++) {
rnd = Math.abs(r.nextInt()) % src.length;
buf[i] = src[rnd];
}
return new String(buf);
}
// Create a random array of Strings:
public static String[] randStrings(int length, int size) {
String[] s = new String[size];
for (int i = 0; i < size; i++)
s[i] = randString(length);
return s;
}
public static void print(byte[] b) {
for (int i = 0; i < b.length; i++)
System.out.print(b[i] + " ");
System.out.println();
}
public static void print(String[] s) {
for (int i = 0; i < s.length; i++)
System.out.print(s[i] + " ");
System.out.println();
}
public static void main(String[] args) {
byte[] b = new byte[15];
r.nextBytes(b); // Fill with random bytes
print(b);
Arrays.sort(b);
print(b);
int loc = Arrays.binarySearch(b, b[10]);
System.out.println("Location of " + b[10] + " = " + loc);
// Test String sort & search:
String[] s = randStrings(4, 10);
print(s);
Arrays.sort(s);
print(s);
loc = Arrays.binarySearch(s, s[4]);
System.out.println("Location of " + s[4] + " = " + loc);
}
}
在 main()中奴迅, Random.nextBytes()
用隨機選擇的字節(jié)填充數組自變量(沒有對應的Random 方法用于創(chuàng)建其他基本數據類型的數組)青责。獲得一個數組后挺据,便可發(fā)現為了執(zhí)行 sort()或者 binarySearch(),只需發(fā)出一次方法調用即可脖隶。與 binarySearch()有關的還有一個重要的警告:若在執(zhí)行一次 binarySearch()之前不調用 sort()扁耐,便會發(fā)生不可預測的行為,其中甚至包括無限循環(huán)产阱。
對 String 的排序以及搜索是相似的婉称,但在運行程序的時候,我們會注意到一個有趣的現象:排序遵守的是字典順序构蹬,亦即大寫字母在字符集中位于小寫字母的前面王暗。因此,所有大寫字母都位于列表的最前面庄敛,后面再跟上小寫字母—— Z 居然位于 a 的前面俗壹。似乎連電話簿也是這樣排序的。
- 可比較與比較器
若想對一個 Object 數組進行排序藻烤,那么必須解決一個問題绷雏。根據什么來判定兩個 Object 的順序呢?不幸的是怖亭,最初的 Java 設計者并不認為這是一個重要的問題涎显,否則就已經在根類 Object 里定義它了。這樣造成的一個后果便是:必須從外部進行 Object 的排序兴猩,而且新的集合庫提供了實現這一操作的標準方式(最理想的是在 Object 里定義它)期吓。
針對 Object 數組(以及 String,它當然屬于 Object 的一種)倾芝,可使用一個 sort()讨勤,并令其接納另一個參數:實現了 Comparator 接口(即“比較器”接口,新集合庫的一部分)的一個對象蛀醉,并用它的單個compare()方法進行比較。這個方法將兩個準備比較的對象作為自己的參數使用—— 若第一個參數小于第二個衅码,返回一個負整數拯刁;若相等,返回零逝段;若第一個參數大于第二個垛玻,則返回正整數。基于這一規(guī)則奶躯,上述例子的 String 部分便可重新寫過帚桩,令其進行真正按字母順序的排序:
通過造型為 String, compare()方法會進行“暗示”性的測試嘹黔,保證自己操作的只能是 String 對象—— 運期系統(tǒng)會捕獲任何差錯账嚎。將兩個字串都強迫換成小寫形式后莫瞬, String.compareTo()方法會產生預期的結果若用自己的 Comparator 來進行一次 sort(),那么在使用 binarySearch()時必須使用那個相同的Comparator郭蕉。
Arrays 類提供了另一個 sort()方法疼邀,它會采用單個自變量:一個 Object 數組,但沒有 Comparator召锈。這個
sort()方法也必須用同樣的方式來比較兩個 Object旁振。通過實現 Comparable 接口,它采用了賦予一個類的“自然比較方法”涨岁。 這個接口含有單獨一個方法—— compareTo()拐袜,能分別根據它小于、等于或者大于自變量而返回負數梢薪、零或者正數蹬铺,從而實現對象的比較。
public class CompClass implements Comparable {
private int i;
public CompClass(int ii) {
i = ii;
}
public int compareTo(Object o) {
// Implicitly tests for correct type:258
int argi = ((CompClass) o).i;
if (i == argi)
return 0;
if (i < argi)
return -1;
return 1;
}
public static void print(Object[] a) {
for (int i = 0; i < a.length; i++)
System.out.print(a[i] + " ");
System.out.println();
}
public String toString() {
return i + "";
}
public static void main(String[] args) {
CompClass[] a = new CompClass[20];
for (int i = 0; i < a.length; i++)
a[i] = new CompClass((int) (Math.random() * 100));
print(a);
Arrays.sort(a);
print(a);
int loc = Arrays.binarySearch(a, a[3]);
System.out.println("Location of " + a[3] + " = " + loc);
}
}
- 列表
可用與數組相同的形式排序和搜索一個列表( List)沮尿。用于排序和搜索列表的靜態(tài)方法包含在類Collections 中丛塌,但它們擁有與 Arrays 中差不多的簽名: sort(List)用于對一個實現了 Comparable 的對象列表進行排序; binarySearch(List,Object)用于查找列表中的某個對象畜疾; sort(List,Comparator)利用一個“比較器”對一個列表進行排序赴邻;而binarySearch(List,Object,Comparator)則用于查找那個列表中的一個對象
public class ListSort {
public static void main(String[] args) {
final int SZ = 20;
// Using "natural comparison method":
List a = new ArrayList();
for(int i = 0; i < SZ; i++)
a.add(new CompClass(
(int)(Math.random() *100)));
Collection1.print(a);
Collections.sort(a);
Collection1.print(a);
Object find = a.get(SZ/2);259
int loc = Collections.binarySearch(a, find);
System.out.println("Location of " + find +
" = " + loc);
// Using a Comparator:
List b = new ArrayList();
for(int i = 0; i < SZ; i++)
b.add(Array1.randString(4));
Collection1.print(b);
AlphaComp ac = new AlphaComp();
Collections.sort(b, ac);
Collection1.print(b);
find = b.get(SZ/2);
// Must use the Comparator to search, also:
loc = Collections.binarySearch(b, find, ac);
System.out.println("Location of " + find +
" = " + loc);
}
}
這些方法的用法與在 Arrays 中的用法是完全一致的,只是用一個列表代替了數組啡捶。
TreeMap 也必須根據 Comparable 或者 Comparator 對自己的對象進行排序
Collections 類中的實用工具:
enumeration(Collection) 為自變量產生原始風格的 Enumeration(枚舉)
max(Collection)姥敛, min(Collection) 在自變量中用集合內對象的自然比較方法產生最大或最小元素
max(Collection,Comparator), min(Collection,Comparator) 在集合內用比較器產生最大或最小元素
nCopies(int n, Object o) 返回長度為 n 的一個不可變列表瞎暑,它的所有句柄均指向 o
subList(List,int min,int max) 返回由指定參數列表后推得到的一個新列表彤敛。可將這個列表想象成一個
“窗口”了赌,它自索引為 min 的地方開始墨榄,正好結束于 max 的前面
注意 min()和 max()都是隨同 Collection 對象工作的,而非隨同 List勿她,所以不必擔心 Collection 是否需要排序(就象早先指出的那樣袄秩,在執(zhí)行一次 binarySearch()—— 即二進制搜索—— 之前,必須對一個 List 或者一個數組執(zhí)行 sort())
1. 使 Collection 或 Map 不可修改
通常逢并,創(chuàng)建 Collection 或 Map 的一個“只讀”版本顯得更有利一些之剧。 Collections 類允許我們達到這個目標,方法是將原始容器傳遞進入一個方法砍聊,并令其傳回一個只讀版本背稼。這個方法共有四種變化形式跟衅,分別用于 Collection(如果不想把集合當作一種更特殊的類型對待)犀填、 List、 Set 以及 Map。
public class ReadOnly {
public static void main(String[] args) {
Collection c = new ArrayList();
Collection1.fill(c); // Insert useful data
c = Collections.unmodifiableCollection(c);
Collection1.print(c); // Reading is OK
// ! c.add("one"); // Can't change it
List a = new ArrayList();
Collection1.fill(a);
a = Collections.unmodifiableList(a);
ListIterator lit = a.listIterator();
System.out.println(lit.next()); // Reading OK
// ! lit.add("one"); // Can't change it
Set s = new HashSet();
Collection1.fill(s);
s = Collections.unmodifiableSet(s);
Collection1.print(s); // Reading OK
// ! s.add("one"); // Can't change it
Map m = new HashMap();
Map1.fill(m, Map1.testData1);
m = Collections.unmodifiableMap(m);
Map1.print(m); // Reading OK
// ! m.put("Ralph", "Howdy!");
}
}
對于每種情況拢肆,在將其正式變?yōu)橹蛔x以前丸冕,都必須用有有效的數據填充容器迄本。一旦載入成功爷辙,最佳的做法就是用“不可修改”調用產生的句柄替換現有的句柄。這樣做可有效避免將其變成不可修改后不慎改變其中的內容竹椒。
在另一方面童太,該工具也允許我們在一個類中將能夠修改的容器保持為private 狀態(tài),并可從一個方法調用中返回指向那個容器的一個只讀句柄胸完。這樣一來书释,雖然我們可在類里修改它,但其他任何人都只能讀赊窥。
為特定類型調用“不可修改”的方法不會造成編譯期間的檢查爆惧,但一旦發(fā)生任何變化,對修改特定容器的方法的調用便會產生一個 UnsupportedOperationException 違例锨能。
2.Collection 或 Map 的同步
在這兒扯再,大家只需注意到 Collections 類提供了對整個容器進行自動同步的一種途徑。它的語法與“不可修改”的方法是類似的:
public class Synchronization {
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());
}
}
總結
(1) 數組包含了對象的數字化索引址遇。它容納的是一種已知類型的對象熄阻,所以在查找一個對象時,不必對結果進行造型處理倔约。數組可以是多維的秃殉,而且能夠容納基本數據類型。但是浸剩,一旦把它創(chuàng)建好以后钾军,大小便不能變化了。
(2) Vector(矢量)也包含了對象的數字索引—— 可將數組和 Vector 想象成隨機訪問集合绢要。當我們加入更多的元素時吏恭, Vector 能夠自動改變自身的大小。但 Vector 只能容納對象的句柄重罪,所以它不可包含基本數據類型樱哼;而且將一個對象句柄從集合中取出來的時候,必須對結果進行造型處理蛆封。
(3) Hashtable(散列表)屬于 Dictionary(字典)的一種類型唇礁,是一種將對象(而不是數字)同其他對象關聯到一起的方式勾栗。散列表也支持對對象的隨機訪問惨篱,事實上,它的整個設計方案都在突出訪問的“高速度”围俘。
(4) Stack(堆棧)是一種“后入先出”( LIFO)的隊列
對于 Hashtable砸讳,可將任何東西置入其中琢融,并以非常快的速度檢索簿寂;對于 Enumeration(枚舉)漾抬,可遍歷一個序列,并對其中的每個元素都采取一個特定的操作常遂。那是一種功能足夠強勁的工具纳令。
但 Hashtable 沒有“順序”的概念。 Vector 和數組為我們提供了一種線性順序克胳,但若要把一個元素插入它們任何一個的中部平绩,一般都要付出“慘重”的代價。除此以外漠另,隊列捏雌、拆散隊列、優(yōu)先級隊列以及樹都涉及到元素的“排序” —— 并非僅僅將它們置入笆搓,以便以后能按線性順序查找或移動它們性湿。
三、各集合類對比總結
集(Set):集里的對象不按任何特定的方式排列满败,按索引值來操作數據肤频,不能有重復的元素
列表(List):序列中的對象以線性方式存儲,按索引值來操作數據葫录,可以有重復的元素
映射(Map):映射的每一項為“名稱—數值”對着裹,名稱不可以重復,值可以重復米同,一個名稱對應一個唯一的值
迭代器Iterator
迭代器是按次序一個一個地獲取集合中所有的對象骇扇,是訪問集合中每個元素的標準機制。
迭代器的創(chuàng)建:Collection接口的iterator()方法返回一個Iterator
Iterator it=test.iterator(); //將test集合對象轉為迭代器
迭代器的常用方法:
hasNext() //判斷迭代器中是否有下一個元素
next() //返回迭代的下一個元素
Remove() //將迭代器新返回的元素刪除
public interface Iterator {
boolean hasNext();
Object next();
void remove(); // Optional
}
在調用remove()方法的時候, 必須調用一次next()方法.
remove()方法實際上是刪除上一個返回的元素.
List常用方法
void add(int index, Object element) :添加對象element到位置index上
boolean addAll(int index, Collection collection) :在index位置后添加容器collection中所有的元素
Object get(int index) :取出下標為index的位置的元素
int indexOf(Object element) :查找對象element 在List中第一次出現的位置
int lastIndexOf(Object element) :查找對象element 在List中最后出現的位置
Object remove(int index) :刪除index位置上的元素
ListIterator listIterator(int startIndex) :返回一個ListIterator 跌代器面粮,開始位置為startIndex
List subList(int fromIndex, int toIndex) :返回一個子列表List ,元素存放為從 fromIndex 到toIndex之前的一個元素
ArrayList
可以將其看作是能夠自動增長容量的數組少孝。
利用ArrayList的toArray()返回一個數組。
Arrays.asList()返回一個列表熬苍。
迭代器(Iterator) 給我們提供了一種通用的方式來訪問集合中的元素稍走。
ArrayList可以自動擴展容量
ArrayList.ensureCapacity(int minCapacity)
首先得到當前elementData 屬性的長度oldCapacity。
然后通過判斷oldCapacity和minCapacity參數誰大來決定是否需要擴容, 如果minCapacity大于 oldCapacity柴底,那么我們就對當前的List對象進行擴容婿脸。
擴容的的策略為:取(oldCapacity * 3)/2 + 1和minCapacity之間更大的那個。然后使用數組拷 貝的方法柄驻,把以前存放的數據轉移到新的數組對象中如果minCapacity不大于oldCapacity那么就不進行擴容狐树。
LinkedList
LinkedList是采用雙向循環(huán)鏈表實現的。
利用LinkedList可以實現棧(stack)鸿脓、隊列(queue)抑钟、雙向隊列(double-ended queue )涯曲。
它具有方法addFirst()、addLast()在塔、getFirst()幻件、getLast()、removeFirst()蛔溃、removeLast()等绰沥。
ArrayList和LinkedList的比較
1.ArrayList是實現了基于動態(tài)數組的數據結構,LinkedList基于鏈表的數據結構贺待。
2.對于隨機訪問get和set揪利,ArrayList覺得優(yōu)于LinkedList,因為LinkedList要移動指針狠持。
3.對于新增和刪除操作add和remove疟位,LinedList比較占優(yōu)勢,因為ArrayList要移動數據喘垂。
盡量避免同時遍歷和刪除集合甜刻。因為這會改變集合的大小正勒;
for( Iterator<ComType> iter = ComList.iterator(); iter.hasNext();){
ComType com = iter.next();
if ( !com.getName().contains("abc")){
ComList.remove(com);}
}
推薦:
for( Iterator<ComType> iter = ComList.iterator(); iter.hasNext();){
ComType com = iter.next();
if ( !com.getName().contains("abc")){
iter.remove(com); }
無限制的在lst中add element得院,勢必會造成lst占用內存過高
Map常用方法
常用方法:
Object put(Object key,Object value):用來存放一個鍵-值對Map中
Object remove(Object key):根據key(鍵),移除鍵-值對章贞,并將值返回
void putAll(Map mapping) :將另外一個Map中的元素存入當前的Map中
void clear() :清空當前Map中的元素
Object get(Object key) :根據key(鍵)取得對應的值
boolean containsKey(Object key) :判斷Map中是否存在某鍵(key)
boolean containsValue(Object value):判斷Map中是否存在某值(value)
public Set keySet() :返回所有的鍵(key)祥绞,并使用Set容器存放
public Collection values() :返回所有的值(Value),并使用Collection存放
public Set entrySet() :返回一個實現 Map.Entry 接口的元素 Set
HashMap
Map 主要用于存儲鍵(key)值(value)對鸭限,根據鍵得到值蜕径,因此鍵不允許重復,但允許值重復。
HashMap 是一個最常用的Map,它根據鍵的HashCode 值存儲數據,根據鍵可以直接獲取它的值败京,具有很快的訪問速度兜喻。
HashMap最多只允許一條記錄的鍵為Null;允許多條記錄的值為 Null;
HashMap不支持線程的同步,即任一時刻可以有多個線程同時寫HashMap;可能會導致數據的不一致赡麦。如果需要同步朴皆,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力,或者使用ConcurrentHashMap
使用HashMap 泛粹,當一個對象被當作鍵值需要對equals()和hashCode()同時覆寫
LinkedHashMap和HashMap遂铡,TreeMap對比
Hashtable與 HashMap類似,它繼承自Dictionary類,不同的是:它不允許記錄的鍵或者值為空;它支持線程的同步晶姊,即任一時刻只有一個線程能寫Hashtable,因此也導致了 Hashtable在寫入時會比較慢扒接。
Hashmap 是一個最常用的Map,它根據鍵的HashCode 值存儲數據,根據鍵可以直接獲取它的值,具有很快的訪問速度,遍歷時珠增,取得數據的順序是完全隨機的。
LinkedHashMap保存了記錄的插入順序砍艾,在用Iterator遍歷LinkedHashMap時蒂教,先得到的記錄肯定是先插入的.也可以在構造時用帶參數,按照應用次數排序脆荷。在遍歷的時候會比HashMap慢凝垛,不過有種情況例外,當HashMap容量很大蜓谋,實際數據較少時梦皮,遍歷起來可能會比LinkedHashMap慢,因為LinkedHashMap的遍歷速度只和實際數據有關桃焕,和容量無關剑肯,而HashMap的遍歷速度和他的容量有關。
TreeMap實現SortMap接口观堂,能夠把它保存的記錄根據鍵排序,默認是按鍵值的升序排序让网,也可以指定排序的比較器,當用Iterator 遍歷TreeMap時师痕,得到的記錄是排過序的溃睹。
我們用的最多的是HashMap,HashMap里面存入的鍵值對在取出的時候是隨機的,在Map 中插入、刪除和定位元素胰坟,HashMap 是最好的選擇因篇。
TreeMap取出來的是排序后的鍵值對。但如果您要按自然順序或自定義順序遍歷鍵笔横,那么TreeMap會更好竞滓。
LinkedHashMap 是HashMap的一個子類,如果需要輸出的順序和輸入的相同,那么用LinkedHashMap可以實現,它還可以按讀取順序來排列吹缔,像連接池中可以應用虽界。
Set的使用
不允許重復元素
對 add()、equals() 和 hashCode() 方法添加了限制
HashSet和TreeSet是Set的實現
Set—》HashSet LinkedHashSet
SortedSet —》 TreeSet
HashSet
public boolean contains(Object o) :如果set包含指定元素涛菠,返回true
public Iterator iterator()返回set中元素的迭代器
public Object[] toArray() :返回包含set中所有元素的數組public Object[] toArray(Object[] a) :返回包含set中所有元素的數組莉御,返回數組的運行時類型是指定數組的運行時類型
public boolean add(Object o) :如果set中不存在指定元素,則向set加入
public boolean remove(Object o) :如果set中存在指定元素俗冻,則從set中刪除
public boolean removeAll(Collection c) :如果set包含指定集合礁叔,則從set中刪除指定集合的所有元素
public boolean containsAll(Collection c) :如果set包含指定集合的所有元素,返回true迄薄。如果指定集合也是一個set琅关,只有是當前set的子集時,方法返回true
實現Set接口的HashSet,依靠HashMap來實現的涣易。
我們應該為要存放到散列表的各個對象定義hashCode()和equals()画机。
HashSet如何過濾重復元素
調用元素HashCode獲得哈希碼–》判斷哈希碼是否相等,不相等則錄入—》相等則判斷equals()后是否相等新症,不相等在進行hashcode錄入步氏,相等不錄入
TreeSet
TreeSet是依靠TreeMap來實現的。
TreeSet是一個有序集合徒爹,TreeSet中元素將按照升序排列荚醒,缺省是按照自然順序進行排列,意味著TreeSet中元素要實現Comparable接口隆嗅,我們可以在構造TreeSet對象時界阁,傳遞實現了Comparator接口的比較器對象挠阁。
HashSet與TreeSet與LinkedHashSet對比
HashSet不能保證元素的排列順序食寡,順序有可能發(fā)生變化,不是同步的崎逃,集合元素可以是null,但只能放入一個null
TreeSet是SortedSet接口的唯一實現類丽焊,TreeSet可以確保集合元素處于排序狀態(tài)精续。TreeSet支持兩種排序方式,自然排序 和定制排序粹懒,其中自然排序為默認的排序方式重付。向 TreeSet中加入的應該是同一個類的對象。
TreeSet判斷兩個對象不相等的方式是兩個對象通過equals方法返回false凫乖,或者通過CompareTo方法比較沒有返回0
自然排序
自然排序使用要排序元素的CompareTo(Object obj)方法來比較元素之間大小關系确垫,然后將元素按照升序排列。
定制排序
自然排序是根據集合元素的大小帽芽,以升序排列删掀,如果要定制排序,應該使用Comparator接口导街,實現 int compare(To1,To2)方法
LinkedHashSet集合同樣是根據元素的hashCode值來決定元素的存儲位置披泪,但是它同時使用鏈表維護元素的次序。這樣使得元素看起 來像是以插入順 序保存的搬瑰,也就是說款票,當遍歷該集合時候,LinkedHashSet將會以元素的添加順序訪問集合的元素泽论。
LinkedHashSet在迭代訪問Set中的全部元素時艾少,性能比HashSet好,但是插入時性能稍微遜色于HashSet翼悴。
參考資料:
- 《java編程思想 》
- https://blog.csdn.net/u012124438/article/details/76698331
- http://www.cnblogs.com/xiaoshitoutest/p/6963798.html
文章有不當之處缚够,歡迎指正,你也可以關注我的微信公眾號:
好好學java
,獲取優(yōu)質資源谍椅。