10.2  java.util包

Java的实用工具类库java.util包中提供了一些实用的方法和数据结构,日期(Data)类、日历(Calendar)类来产生和获取日期及时间,随机数(Random)类产生各种类型的随机数,堆栈(Stack)、向量(Vector)、位集合(Bitset)以及哈希表(Hashtable)等类来表示相应的数据结构。

作为Java软件开发的基础类,java.util包也是面试考查的重点。

10.2.1  理解集合类

面试例题18:请讲述集合类的3个组成部分。

考点:考查求职者对集合类的理解。

出现频率:★★★★

解析

从一般意义上来说,集合是用来存储对象数据的。Java中,集合就是存储其他对象的对象,这一点同数组非常类似。

注意:在数组中,可以存放原始数据类型变量和对象,但是Java的集合中只能存放对象,不能存放原始数据类型的变量,如果一定要存放变量,则必须将变量转换为相应的对象实例。

在程序设计的时候,通常需要处理对象组。集合框架具有管理集合的一套标准工具类。这些类由java.util包来提供,由3个主要部分组成。

q(1)接口:接口定义了集合必须实现的方法。

q(2)实现:类似于Hash(哈希表)和Vector(向量)这样实际的类。

q(3)算法:在一个集合中,整理、检索和操作元素的方法。

Java平台提供了一个全新的集合框架,主要由一组用来操作对象的接口组成。不同接口描述一组不同数据类型,如图10.1所示。

q

q

q

在很大程度上,一旦理解了接口,就理解了框架。图10.2中主要接口如下。

q

q

q

q

q注意:容器中的元素类型都为Object。从容器取得元素时,必须把它转换成原来的类型。

 

图10.1  Java 2集合框架图

图10.2是图10.1框架图的简化图。

 

图10.2  Java 2集合框架简化图

答案

Java集合类的3个部分是接口、实现和算法。

10.2.2  核心接口及其实现

面试例题19:下面哪些是集合框架中的核心接口?

请选择3个正确答案。

(a)Set。

(b)Bag。

(c)LinkedList。

(d)Collection。

(e)Map。

考点:考查求职者对Java集合的核心接口的理解。

出现频率:★★★★★

解析

Java集合的核心接口可以如图10.3所示。

 

图10.3  Java集合的核心接口

表10.1对这些接口进行了归纳和整理。

表10.1                                                     集合框架中的核心接口


接    口

说    明

实  体  类

Collection

1种基本接口,它定义了一些普通操作,通过这些操作可以将一个对象集合当作一个独立单元来对其进行存放和处理。

 

Set

Set接口扩展了Collection接口,用来提供集合的维护(该集合中的元素以某一排列顺序存储)所需的功能。

HashSet

LinkedHashSet

SortedSet

SortedSet接口扩展了Set接口,用来提供集合的维护(该集合中的元素以某一排列顺序存储)所需的功能。

TreeSet

List

List接口扩展了Collection接口,用来存放某个元素序列,在该序列中的元素必须是惟一的。

ArrayLIst

Vector

LinkedList

Map

一种基本接口,定义了用来实现键-值(key-value)映射关系维护的操作。

HashMap

HashTable

LinkedHashMap

SortedMap

针对以键序排序存放其映射关系的映射,该接口扩展了Map接口。

TreeMap


Set中的元素必须是惟一的,也就是说该集合中不能存在任何两个相等的元素。List中的元素顺序仍旧被保留着,单个元素可以根据其在列表中的位置来对其进行访问。

Map接口并未扩展Collection接口,因为从概念上说映射不是集合。映射不含有元素,Map包含的是从一套键对象到一套值对象的映射关系,一个键最多能够和一个值关联。SortedMap接口用来存放其以键序排序的映射关系。

如图10.1和图10.2所示,展示了核心接口和相应实现之间的继承关系。

注意:没有一个实现类是直接继承Collection接口的,而是继承自抽象类。集合和映射是不能交换的。所有实体类都实现了Serializable和Cloneable接口,所以这些类的对象可以串行化和克隆。

表10.2给出了集合及映射实现类的详细情况。

表10.2                                                       集合及映射实现类表


实体集合/映射

接    口

重  复  项

有序/排序

元素调用方法

实现类的数据结构

HashSet

Set

元素惟一

无顺序

equals()、hashCode()

Hash表

LinkedHashSet

Set

元素惟一

插入顺序

equals()、hashCode()

Hash表和双向链表

TreeSet

SortedSet

元素惟一

排序

equals()、
compareTo()

平衡树

ArrayLIst

List

可以重复

插入顺序

equals()

可调大小数组

LinkedList

List

可以重复

插入顺序

equals()

链表

Vector

List

可以重复

插入顺序

equals()

可调大小数组

HashMap

Map

键惟一

无顺序

equals()、hashCode()

Hash表

LinkedHashMap

Map

键惟一

键插入顺序/条目访问顺序

equals()、hashCode()

Hash表和双向链表

Hashtable

Map

键惟一

无顺序

equals()、hashCode()

Hash表

TreeMap

SortedMap

键惟一

键序排序

equals()、
compareTo()

平衡树


1.集合

java.util包中的集合实现Collection接口中的所有可选操作。Collection接口常见的方法如下。

(1)单元素添加、删除操作。

q

q

(2)查询操作。

q

q

q

q

(3)组操作:作用于元素组或整个集合。

q

q

q

q

q

(4)Collection转换为Object数组:

q

q

注意:Collection不提供get()方法。如果要遍历Collectin中的元素,就必须用Iterator。

2.迭代器

Collection接口的iterator()方法返回一个Iterator。Iterator接口方法能以迭代方式逐个访问集合中各个元素,并安全地从Collection中除去适当的元素。

图10.4显式了迭代器的UML建模图。

  ● booleanhasNext():判断是否存在另一个可访问的元素。

q

q

迭代器是故障快速修复(fail-fast)的。这意味着,当另一个线程修改底层集合的时候,如果正在用Iterator遍历集合,那么,Iterator就会抛出ConcurrentModificationException异常(另一种RuntimeException异常)并立刻失败。

一个使用迭代器的完整示例代码如下:

package ch10;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.TreeMap;
import java.util.Vector;
public class MyClass {
    public MyClass() {
    }
    // ///List类/
    void testArrayList() {
        ArrayList al = new ArrayList();
        al.add(1);
        al.add(2);
        al.add(3);
        // 用迭代器将其迭代化,将其序列化为一个序列,
        Iterator it = al.iterator();
        // 这样就可以遍历整个序列而不必关心底层结构
        System.out.println(al.size());
        System.out.println(it.toString());
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
    /**
     * Vector是同步、线程安全的,所以如果需要的是速度,并且不在多线程环境中使中,最好选ArrayList
     * ArrayList是非同步,当然也不是线程安全的
     * 且每次Vector容量的自动扩展是按100%扩展,但是ArrayList是按50%扩展,这样使用ArrayList 就会节省内存空间
     */
    void testVector() {
        Vector vector = new Vector();
        vector.add(1);
        vector.add(2);
        vector.add(3);
        vector.add(4);
        Iterator it = vector.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
    /*
     * LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove, insert方法在
     * LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack), 队列(queue)或双向队列(deque)。
     */
    void testLinkedList() {
        LinkedList ll = new LinkedList();
        ll.add(1);
        ll.add(2);
        ll.add(3);
        ll.add(4);
        ll.addFirst(5);// 链表操作可以进行前插或者是后插,中间任意插入
        ll.addLast(6);
        Iterator it = ll.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
    // ///Map类/
    void testHashMap() {
        HashMap hm = new HashMap();
        // HashMap及Hashtable都是散列表,不可以排序,就算是排好了序也会被打乱
        hm.put(1, null);// 可以放入空值
        hm.put(2, "21");
        hm.put(3, "33");
        hm.put(4, "w434");
        hm.put(5, "5we");
        hm.put(6, "df6");
        hm.put(7, "7we");
        hm.put(8, "re8");
        // Map类都要先转换为最老的迭代Collection后,才能够转换为新的迭代Iterator
        Collection c = hm.values();
        Iterator it = c.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
    void testHashTable() {
        Hashtable ht = new Hashtable();
        // ht.put(1, null);//不可以放入空值,这里会报错,并且Hashtable是同步的
        // HashMap及Hashtable都是散列表,不可以排序,就算是排好了序也会被打乱
        ht.put(2, "21");
        ht.put(3, "33");
        ht.put(4, "w434");
        ht.put(5, "5we");
        ht.put(6, "df6");
        ht.put(7, "7we");
        ht.put(8, "re8");
        Collection c = ht.values();
        Iterator it = c.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
    void testTreeMap() {
        TreeMap tm = new TreeMap();
        // ht.put(1, null);//不可以放入空值,这里会报错,并且Hashtable是同步的
        // TreeMap可以自动排序
        tm.put(2, "21");
        tm.put(3, "33");
        tm.put(4, "w434");
        tm.put(5, "5we");
        tm.put(6, "df6");
        tm.put(7, "7we");
        tm.put(8, "re8");
        Collection c = tm.values();
        Iterator it = c.iterator();
        // 输出将会按参数自动排序
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
    // main方法
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        MyClass tc = new MyClass();
        tc.testArrayList();
        tc.testVector();
        tc.testLinkedList();
        tc.testHashMap();
        tc.testHashTable();
        tc.testTreeMap();
    }
}

面试例题19:考查求职者对Java的集合框架核心接口的掌握。

答案:(a)(d)(e)。

面试例题20:按序存放非惟一元素集合接口名字是什么?

有些集合按序存放非惟一元素,用来表示这些集合的接口名字是什么?请选择正确的答案:

(a)Collection。

(b)Set。

(c)SortedSet。

(d)List。

(e)Sequence。

考点:考查求职者对Java集合的List接口的理解。

出现频率:★★★★

解析

List接口是通过集合实现的,这些集合维持着可能不惟一的元素的序列,在序列中元素保持着它们的次序。

答案:(d)。

10.2.3  掌握Set和List

面试例题21:如何实现Java集合?

编译并运行下面程序,结果是什么?

import java.util.*;

public class MyClass {
    public static void main(String[] args) {
        HashSet set1 = new HashSet();
        addRange(set1, 1);
        ArrayList list1 = new ArrayList();
        addRange(list1, 2);
        TreeSet set2 = new TreeSet();
        addRange(set2, 3);
        LinkedList list2 = new LinkedList();
        addRange(list2, 5);
        set1.removeAll(list1);
        list1.addAll(set2);
        list2.addAll(list1);
        set1.removeAll(list2);
        System.out.println(set1);
    }
    static void addRange(Collection col, int step) {
        for (int i = step * 2; i <= 25; i += step) {
            col.add(new Integer(i));
        }
    }
}

请选择正确的答案。

(a)该程序不能编译,因为这些操作是在不兼容的集合实现上执行的。

(b)不能编译,因为在对set2表示的TreeSet元素进行排序时没有给予其可用的Comparetor。

(c)可以正确编译,但运行时会抛出UnSupportedOperationException。

(d)可以正确编译,并打印出所有25以下的素数。

(e)可以正确编译,并打印出某一个其他数字序列。

考点:考查求职者对Java集合的实现掌握。

出现频率:★★★★★

解析

1.Set集

Set接口继承Collection接口,而且它不允许集合中存在重复项,每个具体的Set实现类依赖添加的对象的equals()方法来检查独一性。Set接口没有引入新方法,所以Set就是一个Collection,只不过其行为不同。

(1)哈希表(Hashtable)。

哈希表是一种数据结构,用来查找对象。哈希表为每个对象计算出一个整数,称为HashCode(哈希码)。哈希表是个链接式列表的阵列,每个列表称为一个buckets(哈希表元)。对象所处的位置index=HashCode%buckets,其中HashCode为对象哈希码,buckets为哈希表元总数)。

当添加元素时,有时会遇到已经填充了元素的哈希表元,这种情况称为HashCollisions(哈希冲突)。这时,必须判断该元素是否已经存在于该哈希表中。

如果哈希码是合理地随机分布的,并且哈希表元的数量足够大,那么哈希冲突的数量就会减少。同时,也可以通过设定一个初始的哈希表元数量来更好地控制哈希表的运行。初始哈希表元的数量为buckets=size*150%+1,其中size为预期元素的数量。

如果哈希表中的元素放得太满,就必须进行rehashing(再哈希)。再哈希使哈希表元数增倍,并将原有的对象重新导入新的哈希表元中,而原始的哈希表元被删除。load factor(加载因子)决定何时要对哈希表进行再哈希。在Java编程语言中,加载因子默认值为0.75,默认哈希表元为101。

(2)SortedSet接口。

集合框架提供了一个特殊的Set接口:SortedSet。SortedSet能保持元素的有序顺序。SortedSet接口为集的视图(子集)和它的两端(即头和尾)提供了访问方法。当处理列表的子集时,更改视图会反映到源集。此外,更改源集也会反映在子集上。发生这种情况的原因在于视图由两端的元素而不是下标元素指定,所以如果想要一个特殊的高端元素(toElement)在子集中,必须找到下一个元素。

添加到SortedSet实现类的元素必须实现Comparable接口,否则必须给它的构造函数提供一个Comparator接口的实现。TreeSet类是它的惟一一个实现。

TreeSet类中的方法如下所示。

q

q

q

q

q

q

(3)HashSet类。

集合框架支持Set接口两种普通的实现:HashSet和TreeSet(TreeSet实现SortedSet接口)。在更多情况下,用户使用HashSet建立元素集合。考虑到效率,添加到HashSet的对象需要实现hashCode()方法。虽然大多数系统类覆盖了Object中默认的hashCode()和equals()实现,但创建自己的要添加到HashSet的类时,一定要覆盖hashCode()和equals()。

HashSet类有如下所示的方法。

q

q

q

q

当要从集合中以有序的方式插入和抽取元素时,TreeSet实现会有用处。为了能顺利进行,添加到TreeSet的元素必须是可排序的。

(4)LinkedHashSet类。

LinkedHashSet类扩展于HashSet类。如果想跟踪添加给HashSet的元素的顺序,可以创建LinkedHashSet对象。LinkedHashSet对象是一个可以快速访问各个元素的有序集合,它的迭代器按照元素的插入顺序来访问各个元素。同时,增加了实现的代价,因为哈希表元中的各个元素是通过双重链接式列表链接在一起的。

q

q

q

q

2.列表List

列表是按照顺序存放元素的集合,可以包含重复的元素,这一点同集Set是不同的。列表可以使用1个从0开始的索引值来访问指定位置的元素。

(1)List接口。

List接口继承了Collection接口,容许定义一个允许重复项的有序集合。该接口不但能够对列表的一部分进行处理,还添加了面向位置的操作。

q

q

q

q

qq  int lastIndexOf(Object o):返回出现最后1个元素o的位置,如果没有找到指定元素则返回-1。

q

q

q

q

q

(2)List接口实现类。

Java.util包提供了3个List接口的实现类:LinkedList、ArrayList和Vector。

ArrayList和Vector类都使用数组方式存储数据,数组元素数大于实际存储的数据数目以便增加和插入元素。它们都允许直接按序号索引元素,但是插入元素涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。Vector由于使用了synchronized方法(线程安全),通常性能上较ArrayList差。LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。

q

注意:LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List,如下所示:

List list = Collections.synchronizedList(new LinkedList(...));

q

qVector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。

下面给出一个简单的Vector用法示例:

package ch10;
import java.util.*;
/**
 * 演示Vector的使用。包括Vector的创建、向Vector中添加元素、从Vector中删除元素、
 * 统计Vector中元素的个数和遍历Vector中的元素。
 */
public class MyClass {
    public static void main(String[] args) {
        // Vector的创建
        // 使用Vector的构造方法进行创建
        Vector v = new Vector(4);
        // 向Vector中添加元素
        // 使用add方法直接添加元素
        v.add("Test0");
        v.add("Test1");
        v.add("Test0");
        v.add("Test2");
        v.add("Test2");
        // 从Vector中删除元素
        v.remove("Test0"); // 删除指定内容的元素
        v.remove(0); // 按照索引号删除元素
        // 获得Vector中已有元素的个数
        int size = v.size();
        System.out.println("size:" + size);
        // 遍历Vector中的元素
        for (int i = 0; i < v.size(); i++) {