java集合是多个对象的容方法,容方法中放了好多对象,集合框架就是java语言的重要组成部分,包含系统而完整的集合层次体系,封装了大量的数据结构的实现。深刻理解java集合框架的
组成结构及其中实现类和算法,会极大地提高程序员的编码的能力。
集合有时又称容方法,简单来说,他就是一个对象,能将具有相同性质的多个元素汇聚成一个整体,集合被用于储存、获取、操纵、传输聚合的数据。集合代表形成一个统一的体系结构。
Collection接口
Collection接口是java集合框架的最顶层接口。它提供了大量通用的集合操纵方法。Collection接口是Sort接口和List接口的父接口。
转换构造方法
Collection接口的实现都有一个带一个集合参数的构造方法。这个构造方法作为“转换构造方法”,将新的集合初始化为包含参数的所有元素,不论是给定接口的子接口或其实现类型。换句
话来说,即允许程序员来“转换”集合的类型。
例如,假设有一个集合:
Collection<String>a;
a可以使一个List、Set或另外一种Collection。通常,习惯地创建一个新的ArrayList(List接口的一个实现),初始化为包含a中的所有元素。
例如:
List<String>list=new ArrayList<String>a;
这里的ArrayList是List一个接口的实现类,在创建实例对象时,将一个集合a作为参数传递给其构造方法,由a中的所有元素初始化list。也就是说,通过使用“转换构造方法”,list对象就
包含了集合a中的所有元素。
Collection接口的定义
通过了解接口的定义,掌握在Collection接口中定义的通用方法,这些通用方法同样会被Collection接口的子接口继承。
public interface Collection<E> extends Iterable<E>{ //基本操作 int size(); boolean isEmpty(); boolean contains(Object element); boolean add(E element); // 可选的 boolean remove(Object element); // 可选的 Iterator<E> iterator(); //批量操作 boolean containAll(Collection<?>c); boolean addAll(Collection<?extends E>c); // 可选的 boolean removeAll(Collection<?>c); // 可选的 boolean retainAll(Collection<?>c); // 可选的 void clear(); //数组操作 Object[] toArray(); <T> T[] toArray(T[] a); }
在Collection接口中,定义了对于集合元素进行基本操作。批量操作和数组操作的方法。
Collection接口的基本操作
从Collection的定义中可以看出,Collection接口的基本操作有6个。使用这些方法,可以知道集合中有多少元素(即集合的大小,size()和isEmpty()方法),可以检查集合中是否包含
某个元素(contain()方法),可以向集合中添加元素和集合中移除元素(add()方法、remove()方法),以及获取访问集合元素的一个迭代方法(iterator()方法)。
其中,add方法被定义得相当通用,所以他对于允许重复的元素集合和不允许重复元素的集合都一样有意义。该方法保证在调用结束以后,Collection将包含指定的元素没并且如果调用的结果
是Collection发生了改变,那么返回true。同样,remove()方法从Collection中移除指定的元素,,并且如果作为该方法调用的结果Collection被修改了,那么返回true。
遍历Collection接口
要遍历集合中的元素,有两种方法:使用for-each结构和使用Iterator迭代方法。
1:for-each结构
for-each结构允许使用for循环简洁的遍历一个集合或数组,下面的代码使用for-each结构在每一行输出一个集合的每一个元素:
for (Object o:collection)
System.out.println(o);
2:iterator迭代方法
迭代方法是一个对象,通过它可以遍历有一个集合并从集合中有选择的移除元素。通过调用集合的iterator方法来获得集合的迭代方法。接口iterator的定义如下:
public interface Iterator<E>{
boolean hasNext(); //判断是否有下一个元素
E next(); //获得下一个元素
void remove(); //删除当前的元素,这个方法时可选的。
}
当迭代方法中还有更多元素时,,hasNext方法返回true,next方法返回迭代方法中的下一个元素。remove方法移除有next方法从Collection中返回的最后一个元素。每次调用next方法以后只
能调用一次remove方法一次,如果违法此规则,就会抛出一个异常。
需要注意的是,在迭代其间,Iterator.remove是修改集合的唯一安全的方法,在迭代其间,任何修改其他集合的方式都会造成不确定的结果。下面几种情况,使用Iterator代替for-each结
构:
1:移除当前元素。for-each结构隐藏迭代方法,因此不能调用remove方法。所以,for-each结构不能用于清除集合元素。
2:在多重集合上进行并行迭代。
下面方法演示了怎样使用一个Iterator来过滤一个任意的Collection集合,即遍历合并移除指定的元素。
static void filter(Collection<?>c){ for(Iterator<?> it =c.iterator();it.hasNext();) if(!cond(it.next())) it.remove(); }
其中,调用集合c的iterator方法,获得一个迭代方法it,通过迭代方法it的hasNext方法来判断是否有更多的元素。如果有返回true,循环继续。在循环体内,通过调用it的next方法,来获
取迭代方法中的下一个元素。
Collection接口的批量操作
批量操作是指整个集合上的操作。可以通过使用基本操作来实现这些快速操作,不过在大多数的情况下,这样的实现是缺乏效率的。下面是批量操作的一些方法:
containsAll:如果目标集合包含指定集合的所有元素,返回true。
addAll:将指定集合中的所有元素添加到目标集合中。
removeAll:从目标集合中移除所有没有包含在指定集合中的所有元素。也就是说,它只在目标集合中保留指定集合中含有的元素。
clear:从集合中移除所有元素。
如果在执行相应操作的过程中,目标集合被修改了,那么addAll,removeAll,retainAll方法都返回true。下面是一个表现批量操作强大功能的示例,从一个命名为c的Collection中移除一个
指定元素e的所有元素。
c.removeAll(Collection.singleton(e));
在明确情况下,例如,想要从一个Collection中移除所有null元素,可以使用下面的语句:
c.removeAll(Collection.singleton(null));
Collection.singleton是一个静态工厂的方法,返回一个只包含指定元素的不可变的Set集合。例如,Collection.Singleton(e)方法返回值包含元素e的Set集合,然后c集合调用removeAll方
法删除c集合中的所有e元素的实例。同样,Collection。Singleton(null)方法返回只包含元素null的Set集合,然后集合c调用removeAll方法删除c集合中的所有null元素。
Collection接口的数组操作
toArray()方法主要作为集合和老的期望输入数组的API之间的桥梁。数组操作允许Collection中的内容转换到一个数组中去。用这种不带参数的简单的方式创建一个新的Object数组,假如
,假设c是一个Collection,下面的代码将c中的内容放入一个新分配的Object数组中,该长度等于c中元素的长度量。
Object[] a=c.toArray();
有一种更为复杂的形式,允许调用者提供一个数组或选择输出数组的运行类型。例如,假如设一直c只包含字符串。下面代码将c中的内容放入一个新的String数组中,该数组的长度等于c中元
素的数量。
String[] a=c.toArray(new String [0]);
Set接口
Set是一个不能包含重复元素的接口。它是数学集合的抽像模型。Set接口是Collection的子接口,只包含从Collection中继承过来的方法,并增加了对add()方法的使用限制,不允许有重复
的元素。Set还修改了equals()和hashCode()方法的实现,允许对Set实例进行内容上的比较,即便他们的实现类型不同。如果两个Set实例包含相同的元素,那么他们是相等的。
Set接口的定义
下面是set接口的定义。凡是实现了set接口的类,都要实现set接口中的方法。因此先了解一下接口的定义,有助于快速并且全面的掌握这些方法的用法。set接口的定义如下所示:
public interface Set<E> extends Collection<E> { //基本操作 int size(); boolean isEmpty(); boolean contains(Object element); boolean add(E element); // 可选的 boolean remove(Object element); // 可选的 Iterator<E> iterator(); //批量操作 boolean containAll(Collection<?>c); boolean addAll(Collection<?extends E>c); // 可选的 boolean removeAll(Collection<?>c); // 可选的 boolean retainAll(Collection<?>c); // 可选的 void clear(); //数组操作 Object[] toArray(); <T> T[] toArray(T[] a); }
java平台包括3个用于通用目的的Set四线:HashSet、TreeSet及LinkedHashSet。HashSet将其元素存储在一个哈希表中,它具有最好的性能实现:然而不能报纸迭代的顺序。TreeSet将其元素
存储在一个红黑树中,按元素的值顺序排列;实际上它比HashSet要慢。LinkedHashSet是作为一个哈希表实现的,用链表连接这些元素,按元素的插入顺序排列。
下面是一个简单却有用的Set习惯用法,假设存在一个集合c,现在想创建另外一个集合,其包含的元素与c相同,但消除了所有重复的元素。使用下面代码实现这一功能:
Collection<Type>noDups=new HashSet<Type>(c);
这行代码通过创建一个Set(按定义不能包含重复的元素),初始化包含c中的所有元素。下面是这个用法的一个变化,使用LinkedHashSet,既可以移除重复的元素,又可保持原来集合的顺序
不变。
Collection<Type>noDups=new LinkedHashSet<Type>(c);
下面是封装了前面概述的泛型方法,返回一个Set,此Set与传递进来的参数集合有相同的泛型类型。
public static <E> Set<E> removeDups(Collection<E>c){
return new LinkedHashSet<E>(c);
Set操作的基本操作
在Set接口中没有增加新的方法,它只包含从Collection接口继承过来的方法,包括6个基本操作方法。其中:
size()方法返回Set中元素的数量(其基数)。
isEmpty()方法如其名所示,判断Set是否为空。
add方法添加指定的元素到Set中,(如果Set中还没有这个元素),并返回一个boolean值,以说明这个元素已经被添加了。
类似的,remove()方法从Set中移除指定的元素(如果存在的话),并返回一个boolean值,以说明这个元素是否已经被移除。
Iterator()方法返回Set的迭代方法Iterator。
下面通过一个实例程序说明Set接口的基本操作。编写程序,获得命令行参数中的字符串列表,输出其中重复的单词,不重复的单词的数量及消除重复以后单词的列表。代码如下:
import java.util.*; public class FindDups { public static void main(String[] args) { // 声明不能具有重复元素的HashSet对象 Set<String> s = new HashSet<String>(); for (String a : args) // 遍历命令行参数字符串数组 if (!s.add(a)) // 如果添加字符串a不成功,说明s中已经有了相同的元素 System.out.println("重复的元素:" + a); System.out.println(s.size() + "个单独的单词:" + s); }
}
运行结果如下:
重复的元素:i
重复的元素:i
4个单独的单词:[i,left,saw,came]
这里的代码总是按Collection接口类型的Set来引用集合,而不是按Collection的实现类型HashSet来引用的。强烈建议这样来写,因为这样可以带来更大的灵活性,只需要通过该表构造方法
就可以改变实现。如果用于 保存一个集合的变量或用于传递集合的参数被声明为Collection的实现类型而不是声明为接口类型,那么为了改变其实现类型,所以这些变量和参数也必须改变。
此外,只通过集合的接口引用集合可以防止程序员使用任何不规范的操作。从上述示例程序中,Set的实现类型是HashSet,它和Set一样,不保证其他元素的顺序。如果想程序按字母顺序暑促
单词列表,只有将Set的实现类型从HashSet改为TreeSet。只在程序上改动一行代码,那么上述程序在命令行的输出如下:
java FindDups i came i saw i left
重复的元素:i
重复的元素:i
4个单独的单词:[came,i,left,saw]
Set接口的批量操作
批量操作特别适合于Set接口。当应用时,它们执行的标准的集合代数运算。假设s1和s2是Set集合,那么可执行的批量操作如下:
s1.containsAll(s2):如果s2是s1的一个子集合,返回true(如果集合s1包含集合s2中的所有元素,呢么s2是s1的一个子集合)
s1.addAll(s2):将s1转换成s1和s2的并集,(两个集合的并集是包含两个集合的所有元素的集合)。
s1.retainAll(s2):将s1转换成s1和s2的交集(两个集合的交集指的是这个集合只包含两个集合的共同元素)
s1.removeAll(s2):将s1不对称的转换为s1和s2的差集(例如s1减去s2的差集指的是包含所有只在s1中存在的元素但不在s2中存在的元素的集合)
要对联合,交集或差集进行非破坏性的计算(不修改两个集合),调用者必须在调用适合的批量操作之前复制一个集合。下面是据此编写的代码:
Set<Type>union=new HashSet<Type>(s1);//声明HashSet实例对象,使用泛型版本
union.addAll(s2); //向HashSet对象中添加元素s2,结果是并集
//声明HashSet实例对象,使用泛型版本
Set<Type> intersection=new HashSet<Type>(s1);
intersection.retainAll(s2);//获得两个集合的交集
//声明HashSet实例对象,使用泛型版本
Set<Type>difference=new HashSet<Typpe>(s1);
difference.removeAll(s2);//获得两个集合的差集
在上面的用法中,Set所实现的结果类型是HashSet,HashSet是java平台中最全面的Set实现。重新看一下FindDups程序。假如想知道参数列表中的那个单词只出现一次及哪些单词出现超过一
次,但是又不想输出重复的但系,这时可以通过创建两个Set来达到目的:一个包含参数列表中的每一个单词,另一个只包含重复的单词是这两个集合的差。使用HashSet进行单词统计应用程
序示例,代码如下:
import java.util.*; public class FindDups2 { public static void main(String []args){ //声明HashSet实例对象,使用泛型版本 Set<String>uniques=new HashSet<String>(); //声明HashSet实例对象,使用泛型版本 Set<String> dups=new HashSet<String>(); for (String a: args) //遍历命令行参数字符串数组 if(!uniques.add(a)) //如果a已经存在 dups.add(a); //那么,将a添加到dups集合中 //破坏性的集合差 uniques.removeAll(dups); //从uniques中移除所有具有重复的单词 System.out.println("不重复的单词:"+uniques); System.out.println("重复的单词"+dups); } }
运行的结果如下:
不重复的单词:[left,saw,came]
重复的单词:[i]
没有共同元素的集合代数运算是对称集合差(symmetric set difference).堆成集合差是这样的集合,它只包含属于这个集合的元素,或属于另一个集合的元素,但不同时属于两个集合的元
素,下面的代码非破坏性的计算两个集合的对称集合差:
Set接口的数组操作
Set接口的数组操作和任何其他Collection接口都是一样的,没有任何特殊的地方。
List接口
List是一个有序的集合(有时被称序列)。List可以包含重复的元素。除了从Collection继承过来的操作之外,List接口还包括以下操作:
按位置访问:根据元素在序列中的位置索引访问元素
查找:在序列中查找指定的对象,并返回其位置索引。
迭代:扩展了Iterator接口,以利用序列的顺序特性。
List子集合:在序列上执行任意范围的操作。
List接口的定义
如下面代码:
public interface List<E> extends Collection<E>{ //按位置访问 E get(int index); E set(int index,E element); //可选的 boolean add(E element);//可选的 void add(int index,E element);//可选的 E remove(int index);//可选的 boolean addAll(int index,Collection<? extends E>c); //查找 int indexOf(Object o); int lastIndexOf(Object o); //迭代 ListIterator<E> listIterator(); ListIterator<E> listIterator(int index); //子集合 List<E> subList(int from,int to); }
java平台包括两个通用的list实现:ArrayList和LinkedList
从Collection继承的操作
List接口具有从Collection接口继承过来的所有操作的功能。例如,remove()操作总是从List中移除第一个与指定元素匹配的元素。Add()和addAll()操作总将新的元素追加到序列的尾
部。因此,下面的用法可以讲一个List对象与另一个List对象连接。
list.addAll(list2);
下面是一个这种用法的非破坏形式,创建第三个List对象,由第二个List对象追加到第一个List对象上组成。
List<Type> list3=new ArrayList<Type>(list1);
Listr3.addAll(list2);
注意,这种非破坏形式的用法中,利用了ArrayList的标准转换构造方法。和Set接口一样,List接口增强了两个函数:equals()和hashCode(),因而两个List对象可以进行逻辑相等性的
比较,而不用考虑它们的是吸纳类。如果两个List对象包含相同的元素并有相同的顺序,那么它们相等。
按位置访问和查找操作
基本的按位置的访问操作有get(),set(),add(),和remove()方法,它们的功能和在Vector(向量)中相对应得方法(elementAt(),setElementAt(),insertElenmentAt
(),removeElementAt()方法)很类似,有一点要注意:set()和remove()方法返回元素被改写或被移除前的值;而Vector中相应的方法(setElementAt()和removeElementAt())什么也不
返回(void)。查找操作indexOf()和lastIndexOf()功能与Vector中相同名称的操作相同。
addAll()方法将指定集合的所有的元素插入到指定位置。元素按集合的迭代方法返回的顺序被插入List。这种调用时位置访问,与Collection的addAll()操作类似。下面是一个简单的方
法,用来交换List中两个不同索引处的值。
public static <E> void swap(List<E> a,int i,int j){ E tmp=a.get(i); a.set(i,a get(j)); a.set(j,tmp); }
这是一个多态的算法:交换任何List中的两个元素,而不必考虑其实现类型。下面使用前面的swap()方法的另一种多态算法。
public static void shuffle(List<?>list,Random rnd){ for(int i=list.size();i>1;i--) swap(list,i-1,rnd.nextInt(i)); }
这个算法是包含在java平台Collection类的一个算法,它使用指定的随机种子随机的安排指定列表的序列。这个方法的实现时从List对象的底部开始往上,重复的将随机选择的元素与当前位
置的元素交换。这种方式严格的要求list.size()-1次交换。使用shuffle()算法随机的输出参数列表中的单词。代码如下:
import java.util.*; public class shuffleDemo { public static void main(String []args){ List<String>list=new ArrayList<String>();//创建一个动态数组对象 for (String a: args) //循环遍历命令行参数中的每一个元素 list.add(a); //将元素添加到数组中 //使用Collection类的shuffle方法对list随机排序 Collection.shuffle(list,new Random()); System.out.println(list); } }
实际上,这个方法可以更加简介和快速。Array类有一个静态工厂方法,称为asList(),将一个数组当做一个list看待。这个方法并不复制数组。在List中的改变也同样反应在数组当中,反
之亦然。最终的List不是一个通用的List实现,因为它并没有实现add()和remove()操作;数组是不可变大小的。使用Array.asList()并调用方法shuffle()(使用一个默认的随机种子
),可以得到以下精简的程序,其作用与上面的程序相同,使用shuffle算法随机的输出其参数列表中的单词,代码如下:
import java.util.*; public class shuffleDemo2 { public static void main(String []args){ List<String>list=Arrays.asList(args); //创建一个动态数组对象 //使用Collection类的shuffle方法对list随机排序 Collection.shuffle(list); System.out.println(list); //输出随机排序以后的list中的元素 } }
List迭代方法
由List的iterator()方法所返回的Iterator迭代方法,以固有的顺序返回List对象中的元素。List还提供一个更加强大的迭代方法,称为ListIterator,它允许从前到后的遍历List对象,
也可以从后到前的遍历,在迭代其间修改List对象,以及获得迭代方法的当前位置。ListIterator接口代码如下:
public interface ListIterator<E> extends Iterator<E> { boolean hasNext(); E next(); boolean hasPrevious(); int nextIndex(); int previousIndex(); void remove(); //可选的 void set(E e); //可选的 void add(E e);//可选的 }
该接口中有3个方法继承自Iterator(hasNext(),next(),remove()),并且实现同样的功能。hasPrevious()和previous()方法使用方式和hasNext()与next()的方式类似。前
者操作指向隐含游标前的元素,反之,后者指向隐含游标后面的元素。previous操作向后移动游标,而next()向前移动游标。下面是向后迭代一个List对象的规范用法:
for(ListIterator<Type>it=list.listIterator(list.size());it.hasPrevious();){ Type t=it.previous(); ..... }
注意上述代码中的listIterator的参数。List接口有两个形式的listIterator()方法。不带参数的形式返回一个位于List对象起始位置的ListIterator;带有一个int类型参数的形式返回一
个位于List对象指定索引出的ListIterator。初始调用next()方法,将返回索引处的元素。
Iterator接口提供了remove()操作,用来移除使用next()方法集合当中最后返回的元素。对于ListIterator,采用同样的操作移除由next()和previous()所返回的最后的元素。
ListIterator接口还提供了两个额外的操作来修改List对象---set()和 add()。其中,set()方法使用指定的元素覆盖可由next()或previous()方法返回的最后的元素。
在下面的算法中,调用set()方法来使用一个新值替换集合中的某一个指定值。
public static <E> void replace(List<E> list,E val,E newval) { for(ListIterator<E> it=list.listIterator();it.hasNext();) //如果集合中下一个值与val值相等或为null if(val==null? it.next()==null :val.equals(it.next())) it.set(newVal);//使用新的值替换集合与val相同的值或null }
需要特别指明val的null值以防止出现NullPointerException异常。
add()方法将一个行的元素插入到List对象中位于当前游标位置之前,下面的多态算法的例子中,使用一个指定的List对象所包含的序列值来替换所有指定的值。其中用到了add()方法。
public static <E> void replace(List<E> list, E list, List<?extends E>newVals){ for(ListIterator<E> it=list.listIterator();it.hasNext();){ if(val==null? it.next()==null : val.equals(it.next())){ it.remove(); //移除null空值或val相等的值 for (E e:newVals) it.add(e); } } }
Map接口
Map是一种包含键值对元素的集合。Map不能包含重复的键:每个键最多可映射到一个值。它是数学函数的抽像模型。
Map接口的定义
在Map接口中声明了一系列方法,凡是实现Map接口类都要实现这些方法。所以先了解Map接口的定义,掌握其声明方法的含义和用法有助于更快速且全面的了解该接口的用法。Map接口的定义
如下:
public interface Map<K,V>{ //基本操作 V put(K key,V value); V get(Object key); V remove(Object key); boolean containsKey(Object key); boolean containsValue(Object value); int size(); boolean isEmpty(); //批量操作 void putAll(Map<?extends K,?extends V>m); void clear(); //集合试图 public Set<K> keySet(); public Collection<V>values(); public Set<Map.Entry<K,V>>entrySet(); //用于entrySet元素的接口 public interface Entry{ K getKey(); V getValue(); V setValue(V value); } }
java平台包括3种通用的Map实现:Hash
Map、TreeMap和LinkedHashMap。它们的行为和执行性能整好与HashSet。TreeSet和LinkedHashSet类似。另外,HashTable(哈希表)重新实现了Map。