命运的奇点

书本可以教你知识和技巧,但不会教你如何专注和坚持

Java集合类

今天在网上搜索了一下,发现一篇关于java集合的博文,里面整理得非常好, 特意copy过来和大家分享一下

本讲内容:集合 collection

讲集合collection之前,我们先分清三个概念:

  1. colection 集合,用来表示任何一种数据结构
  2. Collection 集合接口,指的是 java.util.Collection接口,是 Set、List 和 Queue 接口的超类接口
  3. Collections 集合工具类,指的是 java.util.Collections 类。

SCJP考试要求了解的接口有:Collection , Set , SortedSet , List , Map , SortedMap , Queue , NavigableSet , NavigableMap, 还有一个 Iterator 接口也是必须了解的。

SCJP考试要求了解的类有: HashMap , Hashtable ,TreeMap , LinkedHashMap , HashSet , LinkedHashSet ,TreeSet , ArrayList , Vector , LinkedList , PriorityQueuee , Collections , Arrays

下面给出一个集合之间的关系图:


上图中加粗线的ArrayList 和 HashMap 是我们重点讲解的对象。下面这张图看起来层级结构更清晰些。

我们这里说的集合指的是小写的collection,集合有4种基本形式,其中前三种的父接口是Collection。

  1. List 关注事物的索引列表
  2. Set 关注事物的唯一性
  3. Queue 关注事物被处理时的顺序
  4. Map 关注事物的映射和键值的唯一性

 

一、Collection 接口

Collection接口是 Set 、List 和 Queue 接口的父接口,提供了多数集合常用的方法声明,包括 add()、remove()、contains() 、size() 、iterator() 等。

add(E e) 将指定对象添加到集合中
remove(Object o) 将指定的对象从集合中移除,移除成功返回true,不成功返回false
contains(Object o) 查看该集合中是否包含指定的对象,包含返回true,不包含返回flase
size() 返回集合中存放的对象的个数。返回值为int
clear() 移除该集合中的所有对象,清空该集合。
iterator() 返回一个包含所有对象的iterator对象,用来循环遍历
toArray() 返回一个包含所有对象的数组,类型是Object
toArray(T[] t) 返回一个包含所有对象的指定类型的数组

我们在这里只举一个把集合转成数组的例子,因为Collection本身是个接口所以,我们用它的实现类ArrayList做这个例子:

01 import java.util.ArrayList;
02 import java.util.Collection;
03  
04 public class CollectionTest {
05  
06     publicstatic void main(String[] args) {
07  
08         String a ="a",b="b",c="c";
09         Collection list =new ArrayList();
10         list.add(a);
11         list.add(b);
12         list.add(c);
13  
14         String[] array =  list.toArray(newString[1]);
15  
16         for(String s : array){
17             System.out.println(s);
18         }
19     }
20 }

编译并运行程序,检查结果:

二、几个比较重要的接口和类简介

1、List接口

List 关心的是索引,与其他集合相比,List特有的就是和索引相关的一些方法:get(int index) 、 add(int index,Object o) 、 indexOf(Object o) 。

ArrayList 可以将它理解成一个可增长的数组,它提供快速迭代和快速随机访问的能力。

LinkedList 中的元素之间是双链接的,当需要快速插入和删除时LinkedList成为List中的不二选择。

Vector 是ArrayList的线程安全版本,性能比ArrayList要低,现在已经很少使用

2、Set接口

Set关心唯一性,它不允许重复。

HashSet 当不希望集合中有重复值,并且不关心元素之间的顺序时可以使用此类。

LinkedHashset 当不希望集合中有重复值,并且希望按照元素的插入顺序进行迭代遍历时可采用此类。

TreeSet 当不希望集合中有重复值,并且希望按照元素的自然顺序进行排序时可以采用此类。(自然顺序意思是某种和插入顺序无关,而是和元素本身的内容和特质有关的排序方式,譬如“abc”排在“abd”前面。)

3、Queue接口

Queue用于保存将要执行的任务列表。

LinkedList 同样实现了Queue接口,可以实现先进先出的队列。

PriorityQueue 用来创建自然排序的优先级队列。番外篇中有个例子http://android.yaohuiji.com/archives/3454你可以看一下。

4、Map接口

Map关心的是唯一的标识符。他将唯一的键映射到某个元素。当然键和值都是对象。

HashMap 当需要键值对表示,又不关心顺序时可采用HashMap。

Hashtable 注意Hashtable中的t是小写的,它是HashMap的线程安全版本,现在已经很少使用。

LinkedHashMap 当需要键值对,并且关心插入顺序时可采用它。

TreeMap 当需要键值对,并关心元素的自然排序时可采用它。

三、ArrayList的使用

ArrayList是一个可变长的数组实现,读取效率很高,是最常用的集合类型。

1、ArrayList的创建

在Java5版本之前我们使用:

1 List list = newArrayList();

在Java5版本之后,我们使用带泛型的写法:

1 List<String> list = newArrayList<String>();

上面的代码定义了一个只允许保存字符串的列表,尖括号括住的类型就是参数类型,也成泛型。带泛型的写法给了我们一个类型安全的集合。关于泛型的知识可以参见这里。

2、ArrayList的使用:

01 List<String> list = newArrayList<String>();
02 list.add("nihao!");
03 list.add("hi!");
04 list.add("konikiwa!");
05 list.add("hola");
06 list.add("Bonjour");
07 System.out.println(list.size());
08 System.out.println(list.contains(21));
09 System.out.println(list.remove("hi!"));
10 System.out.println(list.size());

关于List接口中的方法和ArrayList中的方法,大家可以看看JDK中的帮助。

3、基本数据类型的的自动装箱:

我们知道集合中存放的是对象,而不能是基本数据类型,在Java5之后可以使用自动装箱功能,更方便的导入基本数据类型。

1 List<Integer> list = newArrayList<Integer>();
2 list.add(newInteger(42));
3 list.add(43);

4、ArrayList的排序:

ArrayList本身不具备排序能力,但是我们可以使用Collections类的sort方法使其排序。我们看一个例子:

01 import java.util.ArrayList;
02 import java.util.Collections;
03 import java.util.List;
04  
05 public class Test {
06  
07     publicstatic void main(String[] args) {
08         List<String> list =new ArrayList<String>();
09         list.add("nihao!");
10         list.add("hi!");
11         list.add("konikiwa!");
12         list.add("hola");
13         list.add("Bonjour");
14  
15         System.out.println("排序前:"+ list);
16  
17         Collections.sort(list);
18  
19         System.out.println("排序后:"+ list);
20     }
21  
22 }

编译并运行程序查看结果:

排序前:[nihao!, hi!, konikiwa!, hola, Bonjour]

排序后:[Bonjour, hi!, hola, konikiwa!, nihao!]

5、数组和List之间的转换

从数组转换成list,可以使用Arrays类的asList()方法:

01 import java.util.ArrayList;
02 import java.util.Collections;
03 import java.util.List;
04  
05 public class Test {
06  
07     publicstatic void main(String[] args) {
08  
09             String[] sa = {"one","two","three","four"};
10             List list = Arrays.asList(sa);
11             System.out.println("list:"+list);
12             System.out.println("list.size()="+list.size());
13     }
14  
15 }

6、Iterator和for-each

在for-each出现之前,我们想遍历ArrayList中的每个元素我们会使用Iterator接口:

01 import java.util.Arrays;
02 import java.util.Iterator;
03 import java.util.List;
04  
05 public class Test {
06  
07     publicstatic void main(String[] args) {
08  
09         // Arrays类为我们提供了一种list的便捷创建方式
10         List<String> list = Arrays.asList("one","two", "three","four");
11  
12         // 转换成Iterator实例
13         Iterator<String> it = list.iterator();
14  
15         //遍历
16         while(it.hasNext()) {
17             System.out.println(it.next());
18         }
19  
20     }
21  
22 }

在for-each出现之后,遍历变得简单一些:

01 import java.util.Arrays;
02 import java.util.Iterator;
03 import java.util.List;
04  
05 public class Test {
06  
07     publicstatic void main(String[] args) {
08  
09         // Arrays类为我们提供了一种list的便捷创建方式
10         List<String> list = Arrays.asList("one","two", "three","four");
11  
12         for(String s : list) {
13             System.out.println(s);
14         }
15  
16     }
17  
18 }

本讲内容:Map HashMap

前面课程中我们知道Map是个接口,它关心的是映射关系,它里面的元素是成对出现的,键和值都是对象且键必须保持唯一。这一点上看它和Collection是很不相同的。

一、Map接口

Map接口的常用方法如下表所示:

put(K key, V value) 向集合中添加指定的键值对
putAll(Map <? extends K,? extends V> t) 把一个Map中的所有键值对添加到该集合
containsKey(Object key) 如果包含该键,则返回true
containsValue(Object value) 如果包含该值,则返回true
get(Object key) 根据键,返回相应的值对象
keySet() 将该集合中的所有键以Set集合形式返回
values() 将该集合中所有的值以Collection形式返回
remove(Object key) 如果存在指定的键,则移除该键值对,返回键所对应的值,如果不存在则返回null
clear() 移除Map中的所有键值对,或者说就是清空集合
isEmpty() 查看Map中是否存在键值对
size() 查看集合中包含键值对的个数,返回int类型

因为Map中的键必须是唯一的,所以虽然键可以是null,只能由一个键是null,而Map中的值可没有这种限制,值为null的情况经常出现,因此get(Object key)方法返回null,有两种情况一种是确实不存在该键值对,二是该键对应的值对象为null。为了确保某Map中确实有某个键,应该使用的方法是 containsKey(Object key) 。

二、HashMap

HashMap是最常用的Map集合,它的键值对在存储时要根据键的哈希码来确定值放在哪里。

1、HashMap的基本使用:

01 import java.util.Collection;
02 import java.util.HashMap;
03 import java.util.Map;
04 import java.util.Set;
05  
06 public class Test {
07  
08     publicstatic void main(String[] args) {
09  
10         Map<Integer,String> map =new HashMap<Integer,String>();
11  
12         map.put(1,"白菜");
13         map.put(2,"萝卜");
14         map.put(3,"茄子");
15         map.put(4,null);
16         map.put(null,null);
17         map.put(null,null);
18  
19         System.out.println("map.size()="+map.size());
20         System.out.println("map.containsKey(1)="+map.containsKey(2));
21         System.out.println("map.containsKey(null)="+map.containsKey(null));
22         System.out.println("map.get(null)="+map.get(null));
23  
24         System.out.println("map.get(2)="+map.get(2));
25         map.put(null,"黄瓜");
26         System.out.println("map.get(null)="+map.get(null));
27  
28         Set set = map.keySet();
29         System.out.println("set="+set);
30  
31         Collection<String> c = map.values();
32  
33         System.out.println("Collection="+c);
34  
35     }
36  
37 }

编译并运行程序,查看结果:

1 map.size()=5
2 map.containsKey(1)=true
3 map.containsKey(null)=true
4 map.get(null)=null
5 map.get(2)=萝卜
6 map.get(null)=黄瓜
7 set=[null,1, 2,3, 4]
8 Collection=[黄瓜, 白菜, 萝卜, 茄子, null]

2、HashMap 中作为键的对象必须重写Object的hashCode()方法和equals()方法

下面看一个我花了1个小时构思的例子,熟悉龙枪的朋友看起来会比较亲切,设定了龙和龙的巢穴,然后把它们用Map集合对应起来,我们可以根据龙查看它巢穴中的宝藏数量,例子只是为了说明hashCode这个知识点,所以未必有太强的故事性和合理性,凑合看吧:

01 import java.util.HashMap;
02 import java.util.Map;
03  
04 public class Test {
05  
06     publicstatic void main(String[] args) {
07  
08         // 龙和它的巢穴映射表
09         Map<dragon , Nest> map =new HashMap<dragon , Nest>();
10  
11         // 在Map中放入四只克莱恩大陆上的龙
12         map.put(newDragon("锐刃",98), newNest(98));
13         map.put(newDragon("明镜",95), newNest(95));
14         map.put(newDragon("碧雷",176), newNest(176));
15         map.put(newDragon("玛烈",255), newNest(255));
16  
17         // 查看宝藏
18         System.out.println("碧雷巢穴中有多少宝藏:"+ map.get(new Dragon("碧雷", 176)).getTreasure());
19     }
20  
21 }
22  
23 // 龙
24 class Dragon {
25  
26     Dragon(String name,int level) {
27         this.level = level;
28         this.name = name;
29     }
30  
31     // 龙的名字
32     privateString name;
33  
34     // 龙的级别
35     privateint level;
36  
37     publicint getLevel() {
38         returnlevel;
39     }
40  
41     publicvoid setLevel(intlevel) {
42         this.level = level;
43     }
44  
45     publicString getName() {
46         returnname;
47     }
48  
49     publicvoid setName(String name) {
50         this.name = name;
51     }
52  
53 }
54  
55 // 巢穴
56 class Nest {
57  
58     //我研究的龙之常数
59     finalint DRAGON_M = 4162;
60  
61     // 宝藏
62     privateint treasure;
63  
64     // 居住的龙的级别
65     privateint level;
66  
67     Nest(intlevel) {
68         this.level = level;
69         this.treasure = level * level * DRAGON_M;
70     }
71  
72     intgetTreasure() {
73         returntreasure;
74     }
75  
76     publicint getLevel() {
77         returnlevel;
78     }
79  
80     publicvoid setLevel(intlevel) {
81         this.level = level;
82         this.treasure = level * level * DRAGON_M;
83     }
84  
85 }

编译并运行查看结果:

1 Exception in thread "main"java.lang.NullPointerException
2     at Test.main(Test.java:18)

我们发现竟然报了错误,第18行出了空指针错误,也就是说get方法竟然没有拿到预期的巢穴对象。

在这里我们就要研究一下为什么取不到了。我们这里先解释一下HashMap的工作方式。


假设现在有个6张中奖彩票的存根,放在5个桶里(彩票首位只有1-5,首位是1的就放在一号桶,是2的就放在2号桶,依次类推),现在你拿了3张彩票来兑奖,一个号码是113,一个号码是213,一个号码是313。那么现在先兑第一张,取出一号桶里的存根发现存根号码和你的号码不符,所以你第一张没中奖。继续兑第二张,二号桶里就没存根所以就直接放弃了,把三号桶里的所有彩票存根都拿出来对应一番,最后发现有一个存根恰好是313,那么恭喜你中奖了。

HashMap在确定一个键对象和另一个键对象是否是相同时用了同样的方法,每个桶就是一个键对象的散列码值,桶里放的就是散列码相同的彩票存根,如果散列码不同,那么肯定没有相关元素存在,如果散列码相同,那么还要用键的equals()方法去比较是否相同,如果相同才认为是相同的键。简单的说就是 hashCode()相同 && equals()==true 时才算两者相同。

到了这里我们应该明白了,在没有重写一个对象的hashcode()和equals()方法之前,它们执行的是Object中对应的方法。而Object的hashcode()是用对象在内存中存放的位置计算出来的,每个对象实例都不相同。Object的equals()的实现更简单就是看两个对象是否==,也就是两个对象除非是同一个对象,否则根本不会相同。因此上面的例子虽然都是名字叫碧雷的龙,但是HashMap中却无法认可它们是相同的。

因此我们只有重写Key对象的hashCode()和equals()方法,才能避免这种情形出现,好在Eclipse可以帮我们自动生成一个类的hashCode()和equals(),我们把上面的例子加上这两个方法再试试看:

001 import java.util.HashMap;
002 import java.util.Map;
003  
004 public class Test {
005  
006     publicstatic void main(String[] args) {
007  
008         // 龙和它的巢穴映射表
009         Map<dragon , Nest> map =new HashMap<dragon , Nest>();
010  
011         // 在Map中放入四只克莱恩大陆上的龙
012         map.put(newDragon("锐刃",98), newNest(98));
013         map.put(newDragon("明镜",95), newNest(95));
014         map.put(newDragon("碧雷",176), newNest(176));
015         map.put(newDragon("玛烈",255), newNest(255));
016  
017         // 查看宝藏
018         System.out.println("碧雷巢穴中有多少宝藏:"+ map.get(new Dragon("碧雷", 176)).getTreasure());
019     }
020  
021 }
022  
023 // 龙
024 class Dragon {
025  
026     Dragon(String name,int level) {
027         this.level = level;
028         this.name = name;
029     }
030  
031     // 龙的名字
032     privateString name;
033  
034     // 龙的级别
035     privateint level;
036  
037     publicint getLevel() {
038         returnlevel;
039     }
040  
041     publicvoid setLevel(intlevel) {
042         this.level = level;
043     }
044  
045     publicString getName() {
046         returnname;
047     }
048  
049     publicvoid setName(String name) {
050         this.name = name;
051     }
052  
053     @Override
054     publicint hashCode() {
055         finalint PRIME = 31;
056         intresult = 1;
057         result = PRIME * result + level;
058         result = PRIME * result + ((name ==null) ? 0 : name.hashCode());
059         returnresult;
060     }
061  
062     @Override
063     publicboolean equals(Object obj) {
064         if(this == obj)
065             returntrue;
066         if(obj == null)
067             returnfalse;
068         if(getClass() != obj.getClass())
069             returnfalse;
070         finalDragon other = (Dragon) obj;
071         if(level != other.level)
072             returnfalse;
073         if(name == null) {
074             if(other.name != null)
075                 returnfalse;
076         }else if(!name.equals(other.name))
077             returnfalse;
078         returntrue;
079     }
080  
081 }
082  
083 // 巢穴
084 class Nest {
085  
086     //我研究的龙之常数
087     finalint DRAGON_M = 4162;
088  
089     // 宝藏
090     privateint treasure;
091  
092     // 居住的龙的级别
093     privateint level;
094  
095     Nest(intlevel) {
096         this.level = level;
097         this.treasure = level * level * DRAGON_M;
098     }
099  
100     intgetTreasure() {
101         returntreasure;
102     }
103  
104     publicint getLevel() {
105         returnlevel;
106     }
107  
108     publicvoid setLevel(intlevel) {
109         this.level = level;
110         this.treasure = level * level * DRAGON_M;
111     }
112  
113 }

编译并运行查看结果:

1 碧雷巢穴中有多少宝藏:128922112

这一次正常输出了,真不容易^_^

好了本讲就到这里。


阅读更多
个人分类: java基础
上一篇我第一次做开发主管的历程(转贴)
下一篇Java 程序员应该了解的 10 个面向对象设计原则
想对作者说点什么? 我来说一句

java集合类精华大全

2009年02月17日 601KB 下载

Java集合类层次结构

2011年06月01日 8KB 下载

第13讲 JAVA集合类.ppt

2009年10月24日 118KB 下载

java集合类.rar

2011年06月13日 219KB 下载

Java集合排序及java集合类详解

2011年06月13日 773KB 下载

没有更多推荐了,返回首页

关闭
关闭