Java 集合框架

来源:互联网 发布:江苏三六五网络 编辑:程序博客网 时间:2024/06/06 07:12

体系概述

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的方法不同,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视图来迭代mapSet<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视图迭代mapSet<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方法传入的参数类型时和接口上指定的类型一致为Studentint 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视图来迭代mapSet<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视图迭代mapSet<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方法传入的参数类型时和接口上指定的类型一致为Studentint 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。

0 0