Java集合概述
在编程时,常常需要集中存放多个数据。我们可以使用数组来保存多个对象,但数组长度不可变化,如果需要保存数量变化的数据,数组就有点无能为力了;而且数组无法保存具有映射关系的数据,如成绩表:语文—79,数学—80,这种数据看上去像两个数组,但这两个数组的元素之间有一定的关联关系。
为了保存数量不确定的数据,以及保存具有映射关系的数据(也被称为关联数组),Java提供了集合类。集合类主要负责保存、盛装其他数据,因此集合类也被称为容器类。
集合类和数组不一样,数组元素既可以是基本类型的值,也可以是对象(实际上保存的是对象的引用变量);而集合里只能保存对象(实际上只是保存对象的引用变量,但通常习惯上认为集合里保存的是对象)。
Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类。
如下为Collection集合体系的继承树:
其中粗线圈出的Set和List接口是Collection接口派生的两个子接口,它们分别代表了无序集合和有序集合;Queue是Java提供的队列实现,有点类似于List。
下面是Map体系的继承树:
所有Map实现类用于保存具有映射关系的数据(也就是前面介绍的关联数组)。
Map接口的众多实现类,这些实现类在功能、用法上存在一定的差异,但它们都有一个功能特征:Map保存的每项数据都是key-value对,也就是由key和value两个值组成。就像前面介绍的成绩单:语文-79,数学-80。Map与此类似,Map里的key是不可重复的,key用于标识集合里的每项数据,如果需要查阅Map中的数据时,总是根据Map的Key来获取。
我们可以把Java的所有集合分成三大类,其中Set集合类似于一个罐子,把一个对象添加到Set集合时,Set集合无法记住添加这个元素的顺序,所以Set里的元素不能重复(否则系统无法准确识别这个元素);List集合非常像一个数组,它可以记住每次添加元素的顺序,只是List的长度可变。Map集合也像一个罐子,只是它里面的每项数据都由两个值组成。
如果访问List集合中的元素,可以直接根据元素的索引来访问;如果访问Map集合中的元素,可以根据每项元素的key来访问其value;如果访问Set集合中的元素,则只能根据元素本身来访问(这也是Set集合里元素不允许重复的原因)。
Collection和Iterator接口
Collection接口时List、Set和Queue接口的父接口,该接口里定义的方法既可以用于操作Set集合,也可以用于操作List和Queue集合。Collection接口里定义了如下操作集合元素的方法。
- boolean add(Object o):该方法用于向集合里添加一个元素。如果集合对象被添加操作改变了,则返回true。
- boolean addAll(Collection c):该方法把集合c里的所有元素添加到指定集合里。如果集合对象被添加操作改变了,则返回true。
- void clear():清除集合里的所有元素,将集合长度变为0。
- boolean contains(Object o):返回集合里是否包含指定元素。
- boolean containsAll(Collection c):返回集合里是否包含集合c里的所有元素。
- boolean isEmpty():返回集合是否为空。当集合长度为0时返回true,否则返回false。
- Iterator iterator():返回一个Iterator对象,用于遍历集合里的元素。
- boolean remove(Object o):删除集合中的指定元素o,当集合中包含了一个或多个元素o时,这些元素将被删除,该方法将返回true。
- boolean removeAll(Collection c):从集合中删除集合c里包含的所有元素(相当于用调用该方法的集合减集合c),如果删除了一个或一个以上的元素,则该方法返回true。
- boolean retainAll(Collection c):从集合中删除集合c里不包含的元素(相当于把调用该方法的集合变成该集合和集合c的交集),如果该操作改变了调用该方法的集合,则该方法返回true。
- int size():该方法返回集合里元素的个数。
- Object[] toArray():该方法把集合转换成一个数组,所有的集合元素变成对应的数组元素。
下面程序将示范如何通过上面方法来操作Collection集合里的元素:
import java.util.*;
public class CollectionTest {
public static void main(String[] args) {
Collection c=new ArrayList();
//添加元素
c.add("孙悟空");
// 虽然集合里不能放基本类型的值,但Java支持自动装箱
c.add(6);
System.out.println("c集合的元素个数为:" + c.size());
// 删除指定元素
c.remove(6);
System.out.println("c集合的元素个数为:" + c.size());
//判断是否包含指定字符串
System.out.println("c集合是否包含\"孙悟空\"字符串:"
+ c.contains("孙悟空"));
c.add("轻量级Java EE企业应用实战");
System.out.println("c集合的元素:" + c);
Collection books=new HashSet();
books.add("轻量级Java EE企业应用实战");
books.add("疯狂Java讲义");
System.out.println("c集合是否完全包含books集合?"+ c.containsAll(books));
//用c集合减去books集合里的元素
c.removeAll(books);
System.out.println("c集合的元素:" + c);
//删除c集合里的所有元素
c.clear();
System.out.println("c集合的元素:" + c);
//books集合里只剩下c集合里也包含的元素
books.retainAll(c);
System.out.println("books集合的元素:" + books);
}
}
上面程序中创建里两个Collection对象,一个是c集合,一个是books集合,其中c集合是ArrayList,而books是HashSet,虽然他们使用的实现类不同,但当把他们当成Collection来使用时,使用add、remove、clear等方法来操作集合元素时没有任何区别。
使用Iterator接口遍历集合元素
Iterator接口也是Java集合框架的成员,但它与Collection系列、Map系列的集合不一样:Collection系列集合、Map系列集合主要用于盛装其他对象,而Iterator则主要用于遍历(即迭代访问)Collection集合中的元素,Iterator对象也被称为迭代器。
Iterator接口隐藏了各种Collection实现类的底层细节,向应用程序提供了遍历Collection集合元素的同一编程接口。Iterator接口里定义了如下三个方法。
- boolean hasNext():如果被迭代的集合元素还没有被遍历,则返回true。
- Object next():返回集合里的下一个元素。
- void remove():删除集合里上一次next方法返回的元素。
下面程序示范了通过Iterator接口来遍历结合元素:
import java.util.*;
public class IteratorTest {
public static void main(String[] args) {
//创建一个集合
Collection books=new HashSet();
books.add("轻量级Java EE企业应用实战");
books.add("疯狂Java讲义");
books.add("疯狂Android讲义");
//获取books集合对应的迭代器
Iterator it=books.iterator();
while(it.hasNext()){
//it.next()方法返回的数据类型是Object类型
//需要强制类型转换
String book=(String)it.next();
System.out.println(book);
if (book.equals("疯狂Java讲义")) {
//从集合中删除上一次next方法返回的元素
it.remove();
}
//对book变量赋值,不会改变集合元素本身
book="测试字符串"; //①
}
System.out.println(books);
}
}
Iterator仅用于遍历集合,Iterator本身并不提供盛装对象的能力。如果需要创建Iterator对象,则必须有一个被迭代的集合。没有集合的Iterator仿佛无本之木,没有任何价值。
上面代码的①行代码对迭代变量book进行赋值,但当再次输出books集合时,我们看到集合里的元素没有任何改变。这就可以得到一个结论:当使用Iterator对集合元素进行迭代时,Iterator并不是把集合元素本身传给了迭代变量,而是把集合元素的值传给了迭代变量,所以修改迭代变量的值对集合元素本身没有任何影响。
当使用Iterator迭代访问Collection集合元素时,Collection集合里的元素不能被改变,只有通过Iterator的remove方法删除上一次next方法返回的集合元素才可以;否则将会引发java.util.Concurrent ModificationException异常。
使用foreach循环遍历集合元素
除了可以使用Iterator接口迭代访问Collection集合里的元素之外,使用foreach循环迭代访问集合元素更加便捷,例子如下:
import java.util.*;
public class ForeachTest {
public static void main(String[] args) {
//创建一个集合
Collection books=new HashSet();
books.add("java学习");
books.add("python学习");
books.add("android学习");
for (Object obj:books){
//此时的book变量也不是集合元素本身
String book=(String)obj;
System.out.println(book);
if (book.equals("python学习")){
//下面代码将引发ConcurrentModificationException异常
books.remove(book);
}
}
System.out.println(books);
}
}
上面代码使用foreach循环来迭代访问Collection集合里的元素更加简洁。与使用Iterator接口迭代访问集合元素类似的是,foreach循环中的迭代变量也不是结合元素本身,系统只是以此把集合元素的值赋给迭代变量,因此在foreach循环中修改迭代变量的值也没有任何实际意义,在使用foreach访问集合元素时,该集合也不能被改变否者会引发异常。
java实例练习
使用ListIterator逆序遍历ArrayList
对于List列表,除了Iterator,Java还提供了一个功能更加强大的ListIterator,它可以实现逆序遍历列表中的元素
1.
新建项目ReverseOrder,并在其中创建一个ReverseOrder.java文件。在该类的主方法中创建一个ArrayList集合为其制定泛型为Integer类型,并添加10个元素。获得迭代器对象,利用hasPrevious()方法逆序输出ArrayList集合中的元素
package ReverseOrder;
import java.util.*;
public class ReverseOrder {
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>(); // 创建列表
for (int i = 0; i < 10; i++) { // 向列表中增加10个元素
list.add(i);
}
System.out.println("列表中的全部元素:" + list);
System.out.println("逆序输出列表中的元素:");
ListIterator<Integer> li = list.listIterator(); // 获得ListIterator对象
for (li = list.listIterator(); li.hasNext();) { // 将游标定位到列表结尾
li.next();
}
for (; li.hasPrevious();) { // 逆序输出列表中的元素
System.out.print(li.previous() + " ");
}
}
}
List和Set都有iterator()来取得其迭代器。对List来说,你也可以通过listIterator()取得其迭代器,两种迭代器在有些时候是不能通用的。Iterator和ListIterator主要区别在以下方面:
- ListIterator有add()方法,可以向List中添加对象,而Iterator不能。
- ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。
- ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
- 都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iterator仅能遍历,不能修改。
猴子选大王2
之前用for循环写过猴子选大王的例子,这里再来使用LinkedList链表来实现同样的功能:100只猴子坐成一个圈,从1开始报数,报到第14的那只猴子退出圈外,并重新开始计数,依次循环下去,直到圈中只剩下一只猴子,就是大王
1.
新建项目Monkey,并在其中创建一个Monkey.java文件。我们这里使用链表来解决这个问题,将每一只猴子的编号存入链表的一个结点中,将这些结点组成一个链表,用一个迭代器指示每次到达链表尾部的时候,又重新回到链表的头部来,可以用一个计数器num模拟报数,当cnt等于14时,就将指向的结点删除,表明这只猴子退出圈外。还需要一个计数器来记录已经删除的结点数,初始值为100,当它等于1时表示已经选出大王。
package Monkey;
import java.util.*;
public class Monkey {
public static void main(String args[]){
LinkedList <Integer> monkeys = new LinkedList<Integer>();// 创建一个元素类型为Integer的链表
int number, cnt;
for (number=1; number<=100; ++number) // 将猴子的编号放入链表中
monkeys.addLast(number);
cnt = 100;
number = 0;
Iterator it = monkeys.iterator();
while(cnt>1){ // 删除退出的猴子,直到只剩下一只
if (it.hasNext()){
it.next(); // 往后面数
++number; // 计数器加1
}else{ // 迭代器已经到达末尾,重新将它置回到链表头部
it = monkeys.iterator();
}
if(number == 14){ // 删除退出圈外的猴子
number = 0;
it.remove();
--cnt;
}
}
System.out.println("大王编号为:"+monkeys.element()); // 最后链表中剩下的就是大王
}
}
LinkedList类是对AbstractSequentialList类的扩展,它提供了一个链接列表的数据结构。LinkedList类有两种构造方法
LinkedList()方法 // 建立一个空的链接列表
LinkedList(Collection c) //建立一个链接列表,该列表由类c中的元素初始化
除了它继承的方法之外,LinkedList类本身还定义了一些有用的方法,这些方法主要用于操作和访问列表。它们具体的作用:
void addFirst(Object obj) //使用addFirst()方法可以在列表头增加元素
void addLast(Object obj) //在列表尾部增加一个元素
getFirst() // 获取第一个元素
getLast() // 获取最后一个元素
removeFirst() //删除第一个元素
removerLast() // 删除最后一个元素