Java集合概述

在编程时,常常需要集中存放多个数据。我们可以使用数组来保存多个对象,但数组长度不可变化,如果需要保存数量变化的数据,数组就有点无能为力了;而且数组无法保存具有映射关系的数据,如成绩表:语文—79,数学—80,这种数据看上去像两个数组,但这两个数组的元素之间有一定的关联关系。
为了保存数量不确定的数据,以及保存具有映射关系的数据(也被称为关联数组),Java提供了集合类。集合类主要负责保存、盛装其他数据,因此集合类也被称为容器类。
集合类和数组不一样,数组元素既可以是基本类型的值,也可以是对象(实际上保存的是对象的引用变量);而集合里只能保存对象(实际上只是保存对象的引用变量,但通常习惯上认为集合里保存的是对象)。
Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类。
如下为Collection集合体系的继承树:
《疯狂java讲义》学习(27):Collection和Iterator接口
其中粗线圈出的Set和List接口是Collection接口派生的两个子接口,它们分别代表了无序集合和有序集合;Queue是Java提供的队列实现,有点类似于List。
下面是Map体系的继承树:
《疯狂java讲义》学习(27):Collection和Iterator接口
所有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()   // 删除最后一个元素

相关文章:

  • 2022-12-23
  • 2021-12-22
  • 2021-11-23
  • 2022-01-07
  • 2021-12-22
  • 2021-12-31
  • 2021-04-01
  • 2021-07-24
猜你喜欢
  • 2021-09-21
  • 2022-01-21
  • 2021-05-21
  • 2021-12-22
  • 2021-12-22
  • 2021-12-14
  • 2021-05-11
相关资源
相似解决方案