Java 集合框架
2015-07-01 10:28
477 查看
体系概述
Java平台提供了一个全新的集合框架,集合框架主要由一组用来操作对象的接口组成,不同的接口描述一组不同的数据类型。下面描述的6个接口表示不同的集合类型,是Java集合框架的基础。Collection接口:
List接口:
ArrayList具体类*
LinkedList具体类*
Set接口:
HashSet具体类*
TreeSet具体类*
Map接口:
HashMap具体类*
TreeMap具体类 *
Iterator接口:
ListIterator接口
这是简化的一张Java集合框架图,也一并复制上来
Collection接口
Collection是最基本的集合接口,它定义了一组允许重复的对象;也是List和Set集合的根接口,其中定义了有关集合操作的普遍性方法。--新增
boolean add(E e); // 将指定对象添加到集合中
boolean addAll(Collection c); // 将指定的集合中的所有元素添加到集合中
--删除
boolean remove(Object o); // 移除指定元素的单个实例
boolean removeAll(Collection c); // 移除包含Collection中的元素实例
viod clear(); // 移除Collection中的所有元素
--查找
boolean contains(Object o); // 如果包含指定元素,则返回true
boolean isEmpty(); // 如果集合不包含元素,则返回true
int size(); // 返回集合中元素个数
Iterator iterator(); // 获取集合的迭代器
--其它
boolean retainAll(Collenction c); // 对集合取交集
<T> toArray(); // 返回集合的数组
迭代器
迭代器是一种模式,它可以使得遍历与对象相分离,即我们无需关心对象序列的底层结构,只有拿到这个对象的迭代器就可以遍历对象内部。Java提供了一个专门的迭代器<interface> Iterator,我们可以对某个序列实现该接口,来提供标准的Java迭代器。Collection接口中的子类就是通过内部类实现Iterator接口,创建各自的迭代器,再重写iterator()方法返回迭代器,调用者只需拿到迭代器便可按照Iterator提供的标准来遍历对象。Iterator接口:
boolean hasNext(); // 如果还有元素可迭代,则返回true
E next(); // 返回迭代的下一个元素
void remove(); // 删除迭代返回的最后的一个元素
import java.util.ArrayList; import java.util.Iterator; public class IteratorDemo { public static void main(String args[]) { ArrayList al = new ArrayList(); al.add("java 01"); al.add("java 02"); al.add("java 03"); Iterator it = al.iterator(); while(it.hasNext()) { System.out.println(it.next()); } } }Iterable接口:
这是Java 5新特性,Iteratable接口实现后的功能是返回一个迭代器,实现Iterable接口允许对象成为foreach语句目标。我们常用的实现Iterable接口的类有:List<E>, Set<E>。
import java.util.ArrayList; public class IteratorDemo { public static void main(String args[]) { ArrayList al = new ArrayList(); al.add("java 01"); al.add("java 02"); al.add("java 03"); for(Object obj: al) { System.out.println(obj); } } }可以看出for each循环语句更简洁,将迭代器的显示调用改为隐式调用。但这样也有局限性,隐藏了迭代器后就无法使用迭代器的方法,比如该例遍历中就无法对集合进行删除。
List集合
List继承自Collection接口。List是有序的Collection,使用List接口能够精确的控制每个元素插入的位置。用户能够使用索引来访问元素,这类似于Java的数据。跟Set集合不同的是,List允许有重复的元素。对于满足e1.equals(e2)条件的两个元素,可以同时存在于List集合中。
除了具有Collection接口必备的Iterator()方法外,List还提供了一个ListIterator()方法,返回一个ListIterator接口。ListIterator和Iterator相比添加对元素的增删改的方法。
实现List接口的子类有LinkedList,ArrayList,Vector,Stack。
LinkedList类
LinkedList实现了List接口,允许null元素。是以指针链表结构来实现的。此外LinkedList还提供了一些额外的方法。void addFirst(E e); // 将指定的元素插入到列表的开头
void addLast(E e); // 将指定的元素插入到列表的结尾
// 获取元素,但不删除元素。如果集合中没有元素,则会抛出NoSuchElementException异常
E getFirst(); // 获取列表中的第一个元素
E getLast(); // 获取列表中的最后一个元素
// 获取元素,并且删除元素。如果没有元素,则会抛出NoSuchElementException异常
E removeFirst(); // 获取并移除列表中的第一个元素
E removeLast(); // 获取并移除列表中的最后一个元素
在JDK1.6中出现了替代方法。
boolean offerFirst(E e); // 将指定的元素插入到列表的开头
boolean offerLast(E e); // 将指定的元素插入到列表的结尾
// 获取元素,但不删除元素。如果集合中没有元素,则返回null
E peerFirst(); // 获取列表中的第一个元素
E peerLast(); // 获取列表中的最后一个元素
// 获取元素,并且删除元素。如果没有元素,则返回null
E pollFirst(); // 获取并移除列表中的第一个元素
E pollLast(); // 获取并移除列表中的最后一个元素
这些操作使得LinkedList可被用作为堆栈、队列等。另外LinkedList是非线程安全的,如果多线程同时访问一个List,需要自己实现同步。
ArrayList类
ArrayList实现了List接口,允许null元素。是以数组结构来实现的。也提供了自己的get(), set() 方法,和LinkedList一样也是非线程安全的。ArrayList和LinkedList同作为List接口下非常重要的子类,就此对二者做简单的对比。LinkedList使用的是链表结构,优点是增、删、改速度较快;不足的是查找需要逐个遍历链表,速度较慢。(链表结构增删改都是通过修改相关元素的指针来实现,不需要挪动元素,也不受长度限制;但在查找时需要逐个查询)
ArrayList使用的数组结构,优点是查找较快,不足的是新增和删除很慢。(数组结构查询可以按角标随机访问,但在新增或删除需要去挪动数组里的元素,因此较慢;如果当长度不够时,就需要重新开辟空间复制数据,比较影响性能)
Vector类
Vector的功能和实现原理和ArrayList类似,Vector是在ArrayList之前出现的,后来基本上被ArrayList替代了。但Vector和ArrayList还是有稍微的不同。Vector是线程安全的;Vector还有自己独有的枚举(elements(), hasMoreElements(), nextElements()),后来被迭代器取代了。Stack类
Stack继承自Vector类,实现一个后进先出的堆栈,提供了额外的5个方法来操作堆栈。E push(E e); // 向栈顶添加元素
E pop(); // 移除栈顶元素
E peek(); // 查看堆栈顶部对象
boolean empty(); // 测试堆栈是否为空
int search(Object o); // 返回对象在堆栈中的位置
Set集合
Set继承自Collection接口。Set是一种不能包含有重复,且元素无序的集合,即对于满足e1.equals(e2)条件的e1和e2对象元素不能同时存于一个Set集合里,Set中也是可以有null元素的。因为Set特性,使用Set时应该:为Set集合里的元素的实现类实现一个有效的equals()方法。实现了Set接口的主要有HashSet、TreeSet、LinkedHashSet。HashSet类
HashSet使用的是哈希表来存储元素的,使用HashSet能够快速的获取集合中的元素,效率非常高。会根据hashcode和equals来判断是否为同一个对象,如果hashcode值一样,并且equals返回true,则是同一个对象,不能重复存放。import java.util.HashSet; import java.util.Iterator; public class HashSetDemo { public static void main(String args[]) { HashSet hs = new HashSet(); Person p1 = new Person("java01", 11); Person p2 = new Person("java02", 12); Person p3 = new Person("java03", 13); Person p4 = new Person("java02", 12); hs.add(p1); hs.add(p2); hs.add(p3); hs.add(p4); Iterator it = hs.iterator(); while(it.hasNext()) { Person p = (Person)it.next(); System.out.println("name: " + p.name + "; " + "age: " + p.age); } } } class Person { String name; int age; Person(String name, int age) { this.name = name; this.age = age; } public int hashCode() { return this.name.hashCode() + this.age; } public boolean equals(Object obj) { if (!(obj instanceof Person)) { return false; } Person p = (Person) obj; return this.name.equals(p.name) && (this.age == p.age); } }上段代码重写了hashCode()和equals()方法,如果不重写则会默认使用父类中的hashCode()和equals()方法;注意不同的类生成hashCode的方法不同
e193
,equals的比较方法也不同。哈希表的存储原理是:对每个对象按照指定的规则生成一个int型值,int值不同的对象肯定是不同对象,将该对象的地址保存到该哈希值下的链表中;int值如果相同,在比较equals方法将该对象和哈希值链表下的对象比较,如果返回false,则将该对象也存放到该哈希值下的链表中,如果返回true则认为以后包含该对象不再存储。
TreeSet类
TreeSet描述的是一种可以实现排序功能的Set集合,存储元素的数据结构是二叉树,如果存放的对象不能排序则会报错,所以存放的对象必须指定排序规则,排序规则包含自然排序和客户排序。自然排序:在TreeSet要添加的那个对象类上实现java.lang.Comparable接口,并重写compareTo()方法,返回0表示是同一个对象,否则不是同一个对象,TreeSet默认是自然排序。
客户排序:建立一个第三方类并实现java.util.Comparator接口,并重写compare()方法。定义集合形式为TreeSet ts = new TreeSet(new 第三方类);
import java.util.Comparator; import java.util.Iterator; import java.util.TreeSet; public class TreeSetDemo { public static void main(String args[]) { TreeSet hs = new TreeSet(); // 自然排序 // TreeSet hs = new TreeSet(); // 客户排序 hs.add(new Person("java01", 11)); hs.add(new Person("java02", 12)); hs.add(new Person("java03", 13)); hs.add(new Person("java04", 12)); Iterator it = hs.iterator(); while(it.hasNext()) { Person p = (Person)it.next(); System.out.println("name: " + p.name + "; " + "age: " + p.age); } } } class MyCompare implements Comparator{ // 客户排序,对指定对象先按名称排序,如果名称相同则再按年龄排序 // 原则是先比主要条件,再比次要条件,如果都一样则认为这两个对象是同一个对象; public int compare(Object obj1, Object obj2) { Person p1 = (Person) obj1; Person p2 = (Person) obj2; int num = p1.name.compareTo(p2.name); if (num == 0) { return new Integer(p1.age).compareTo(new Integer(p2.age)); } return num; } } class Person implements Comparable{ String name; int age; Person(String name, int age) { this.name = name; this.age = age; } // 自然排序,对本对象先按年龄升序,如果年龄相同再按名称排序; // 原则是先比主要条件,再比次要条件,如果都一样则认为这两个对象是同一个对象; public int compareTo(Object obj) { Person p = (Person) obj; System.out.println(this.name + "和" + p.name + "比较了!!!"); // 返回正数表示当前对象大于比较对象 if (this.age > p.age) return 1; // 返回0表示两个对象相同 if (this.age == p.age) { // String类已经实现了Comparable接口,直接调用它的排序方法即可 return this.name.compareTo(p.name); } // 返回负数表示当前对象小于比较对象 return -1; } }
要想用TreeSet集合就必须指定排序规则,而指定排序规则的方式就是重写比较方法;当集合有指定的客户排序规则时,优先使用客户排在,如果没有才会使用对象提供的默认排序规则,都没有则编译失败。下面以默认排序为例来简述TreeSet的排序情况。TreeSet类在添加对象时会自动调用对象的compareTo()方法来实现TreeSet集合中数据的排序。在重写compareTo()方法时,当返回值为0时一定要注意,此时新增对象会被认为和已有的对象相同,而不被添加到集合中来。TreeSet新增、删除、查找元素都是通过比较compareTo()的返回值来确定的,如果比较的返回值为0则认为是相同的,如果比较的返回值不为0则认为不是同一个元素。
Map集合
将键映射到值的对象。一个映射不能包含重复的键,每个键最多只能映射到一个值。Map接口提供了三种collection视图,允许以键集、值集和键值映射关系的形式查看某个映射的内容。某些映射实现可以保证其顺序,如TreeMap类;某些映射实现不能保证其顺序,如HashMap类。下面是Map接口的一些方法。-- 新增
V put(K key, V value); // 将键与值关联,返回的V的值是指定的键之前对应的值,如果之前该键不存在则返回null
void putAll(Map<? extends K, ? extends V> map); // 将指定的映射关系复制到当前映射关系中
-- 删除
V remove(Object key); // 通过指定的键来删除键值映射关系
void clear(); // 移除此集合中所有映射关系
-- 判断
boolean containsKey(Object key); // 如果此映射中包含指定的键,则返回true
boolean containsValue(Object value); // 如果此映射中包含指定的值,则返回true
boolean isEmpty(); // 判断集合是否为空,非空则返回true
-- 获取
V get(Object value); // 返回指定键对应的值,无此键则返回null
int size(); // 返回集合中键值映射关系的条数
-- 获取视图
Collection<V> values(); // 返回映射中包含值的视图
Set<K> keySet(); // 返回映射中包含键的Set视图
Set<Map.Entry<K, V>> entrySet(); // 返回映射中包含的映射关系的Set视图
除了获取视图外,其它方法比较好理解,这里对获取视图的几个方法做下详细的说明:
1、value返回的Collection集合,而其它的都是返回Set集合:这个是因为value可能存在重复的值,所以需要提升为Set的父类;而key和关系映射肯定是唯一不重复的,且里面的值是无序的,所以使用Set接口较好。
2、Map.Entry<K, V> 作为Map的内部类,其和普通对象无差别,类似于Set<Object>;只是替代Object的是Entry类,并且这个类是泛型类,其参数和Map中的K、V整好对应,所以当Map的K、V指定后,Entry中的K、V也就确定了。
3、这三种视图提供了对Map集合迭代的方法,都是先获取某种集合,然后再用迭代器对Collection或Set进行迭代,从而实现对Map的迭代。
HashMap类
基于哈希表的Map接口实现。此实现类提供了所有可选的映射操作,并且是非同步和允许是使用null值和null键;此类不保证映射的顺序,特别是不保证该顺序恒久不变。下面通过一段实例来了解下HashMap。import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; public class HashMapDemo { public static void main(String args[]) { // 使用HashMap时,要注意key对象的hashCode()和equals()方法 // 该方法用来判断键值是否相同 HashMap<Student, String> map = new HashMap<Student, String>(); map.put(new Student("zhangsan01", 11), "beijing"); map.put(new Student("zhangsan02", 21), "shanghai"); map.put(new Student("zhangsan03", 31), "nanjing"); map.put(new Student("zhangsan04", 11), "beijing"); map.put(new Student("zhangsan02", 21), "wuhan"); // 用keySet视图来迭代map Set<Student> keySet = map.keySet(); Iterator<Student> it = keySet.iterator(); while(it.hasNext()) { Student stu = it.next(); String addr = map.get(stu); System.out.println("学生:" + stu + "; 地址:" +addr); } // 用entrySet视图迭代map Set<Map.Entry<Student, String>> entrySet = map.entrySet(); Iterator<Map.Entry<Student, String>> iter = entrySet.iterator(); while(iter.hasNext()) { Map.Entry<Student, String> entry = iter.next(); Student stu = entry.getKey(); String addr = entry.getValue(); System.out.println("学生:" + stu + "; 地址:" +addr); } } } /** * 学生测试类 * 学生属性:姓名,年龄。 * 这里假设当姓名和年龄都相同时视为同一个学生。 */ class Student implements Comparable<Student> { private String name; private Integer age; Student(String name, Integer age) { this.name = name; this.age = age; } // 因为该对象可能要存入到HashMap中,并作为key键 // 所以需要重写哈希比较中用到的两个方法,来保证学生的唯一性 public int hashCode() { return name.hashCode() + age.hashCode(); } public boolean equals(Object obj) { // 注意参数必须是Object对象,因为是重写,那么参数类型必须一致 Student s = (Student) obj; return this.name.equals(s.name) && this.age.equals(s.age); } // 因为该对象可能要存入到TreeMap中,并作为key键 // 所以需要实现Comparable接口,并重写compare方法,让该类具有可比性 public int compareTo(Student s) { // 注意这里在Comparable接口上使用了泛型,并且compareTo方法参数类型也是泛型 // 所以这里重写compareTo方法传入的参数类型时和接口上指定的类型一致为Student int num = this.age.compareTo(s.age); if (num == 0) { return this.name.compareTo(s.name); } return num; } //重写toString方法 public String toString() { return name + ", " + age; } }上面对HashMap的使用做了演示,更对Set<Key>和Set<Map.Entry<K, V>>视图的使用做了较详细的描述。只是要注意:使用到哈希数据结构的,判断对象是否相同,都是通过hashCode()和equals()方法,在使用时要注意对象的这两个方法。
TreeMap类
基于红黑树的Map接口实现。该映射根据其键的自然顺序或者创建映射是提供的Comparator进行排序。注意:此集合是通过K的compareTo()方法或者初始化时构造函数中的Comparator对象来确定的,与map接口中的hashCode()和equals()方法无关,所以要想让TreeMap集合能正确实现Map接口,就必须有映射保持顺序和equals一致。下面通过一个简单的实例了解下TreeMap的使用。import java.util.Comparator; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.TreeMap; public class TreeMapDemo { public static void main(String args[]) { // 使用TreeMap时,要注意key对象的compareTo()方法或者比较器中的compare()方法 // 该方法用来判断键值是否相同 TreeMap<Student, String> map = new TreeMap<Student, String>(new StudentComparator()); map.put(new Student("zhangsan01", 11), "beijing"); map.put(new Student("zhangsan02", 21), "shanghai"); map.put(new Student("zhangsan03", 31), "nanjing"); map.put(new Student("zhangsan02", 21), "beijing"); map.put(new Student("zhangsan02", 20), "wuhan"); // 用keySet视图来迭代map Set<Student> keySet = map.keySet(); Iterator<Student> it = keySet.iterator(); while(it.hasNext()) { Student stu = it.next(); String addr = map.get(stu); System.out.println("学生:" + stu + "; 地址:" +addr); } // 用entrySet视图迭代map Set<Map.Entry<Student, String>> entrySet = map.entrySet(); Iterator<Map.Entry<Student, String>> iter = entrySet.iterator(); while(iter.hasNext()) { Map.Entry<Student, String> entry = iter.next(); Student stu = entry.getKey(); String addr = entry.getValue(); System.out.println("学生:" + stu + "; 地址:" +addr); } } } /** * 构造一个比较器 * 比较类型为通过泛型设置为Student * 先比姓名,如果相同再比较年龄,都相同则认为是同一个Student对象 */ class StudentComparator implements Comparator<Student> { // 实现compare方法,参数的类型和泛型上指定的类型相同 public int compare(Student s1, Student s2) { int num = s1.getName().compareTo(s2.getName()); if (num == 0) { return s1.getAge().compareTo(s2.getAge()); } return num; } } /** * 学生测试类 * 学生属性:姓名,年龄。 * 这里假设当姓名和年龄都相同时视为同一个学生。 */ class Student implements Comparable<Student> { private String name; private Integer age; Student(String name, Integer age) { this.name = name; this.age = age; } public String getName() { return name; } public Integer getAge() { return age; } // 因为该对象可能要存入到HashMap中,并作为key键 // 所以需要重写哈希比较中用到的两个方法,来保证学生的唯一性 public int hashCode() { return name.hashCode() + age.hashCode(); } public boolean equals(Object obj) { // 注意参数必须是Object对象,因为是重写,那么参数类型必须一致 Student s = (Student) obj; return this.name.equals(s.name) && this.age.equals(s.age); } // 因为该对象可能要存入到TreeMap中,并作为key键 // 所以需要实现Comparable接口,并重写compare方法,让该类具有可比性 public int compareTo(Student s) { // 注意这里在Comparable接口上使用了泛型,并且compareTo方法参数类型也是泛型 // 所以这里重写compareTo方法传入的参数类型时和接口上指定的类型一致为Student int num = this.age.compareTo(s.age); if (num == 0) { return this.name.compareTo(s.name); } return num; } //重写toString方法 public String toString() { return name + ", " + age; } }上面对TreeMap的使用做了演示;对怎么重写对象中的比较方法,和构造一个比较器去对key键进行排序也做了比较详细的描述。注意:TreeMap和HashMap比较key值是否相同的方式不一样,是否正确实现了Map接口,要看equals方法是否和compare或者compareTo方法一致。
Hashtable类
除了线程同步和不允许使用null外,Hashtable和HashMap大致相同,其实可以把HashMap看着是升级版的Hashtable。总结:到此对Java集合作了个概要的讲解。上面是按照集合的体系讲述的,着也是通用的认知模式。下面我简单的说下按照集合的实现原理对集合的划分。
线性表结构
ArrayList:数组结构的线性表(元素可重复)
LinkedList:链表结构的线性表(元素可重复)
哈希表结构
HashSet:哈希表结构(无序,只有键,并且键不能重复)
HashMap:哈希表结构(无序,键--值对,键不能重复,但值可以重复)
红黑树(平衡二叉树)结构
TreeSet:红黑树结构(有序,只有键,并且键不能重复)
TreeMap:红黑树结构(有序,键--值对,键不能重复,但值可以重复)
注意Map中的值是可以重复的,返回值对象的集合是Collection,而返回键对象的集合是Set。
相关文章推荐
- Java线程的生命周期
- Spring的注解积累
- 《Java课程实习》日志(周三)
- Java的常见异常的意思
- eclipse查看java方法域
- POJO和javabean的异同
- 【Spring学习笔记-MVC-1.0】Spring MVC架构介绍
- JavaCard开发环境搭建
- JAVA学习(一):Java介绍及其平台、开发环境的配置与搭建
- JAVA学习(一):Java介绍及其平台、开发环境的配置与搭建
- java.util.zip类 ZipFile
- 配置Spring数据源
- java.util.zip类 ZipInputStream
- java.util.zip类 ZipOutputStream
- Struts2标签中$,#,%的用法
- Eclipse使用技巧和方法
- 一个Maven工程中,不同的模块需要不同的JDK进行编译
- JAVA实验第三天
- Spring事务传播方式
- 《编程导论(Java)·1.2类》