Java的实用工具类库java.util包中提供了一些实用的方法和数据结构,日期(Data)类、日历(Calendar)类来产生和获取日期及时间,随机数(Random)类产生各种类型的随机数,堆栈(Stack)、向量(Vector)、位集合(Bitset)以及哈希表(Hashtable)等类来表示相应的数据结构。
作为Java软件开发的基础类,java.util包也是面试考查的重点。
面试例题18:请讲述集合类的3个组成部分。
考点:考查求职者对集合类的理解。
出现频率:★★★★
解析
从一般意义上来说,集合是用来存储对象数据的。Java中,集合就是存储其他对象的对象,这一点同数组非常类似。
注意:在数组中,可以存放原始数据类型变量和对象,但是Java的集合中只能存放对象,不能存放原始数据类型的变量,如果一定要存放变量,则必须将变量转换为相应的对象实例。
在程序设计的时候,通常需要处理对象组。集合框架具有管理集合的一套标准工具类。这些类由java.util包来提供,由3个主要部分组成。
q(1)接口:接口定义了集合必须实现的方法。
q(2)实现:类似于Hash(哈希表)和Vector(向量)这样实际的类。
q(3)算法:在一个集合中,整理、检索和操作元素的方法。
Java平台提供了一个全新的集合框架,主要由一组用来操作对象的接口组成。不同接口描述一组不同数据类型,如图10.1所示。
q ● 集合接口:6个接口(短虚线表示),表示不同集合类型,是集合框架的基础。
q ● 抽象类:5个抽象类(长虚线表示),对集合接口的部分实现。可扩展为自定义集合类。
q ● 实现类:8个实现类(实线表示),对接口的具体实现。
在很大程度上,一旦理解了接口,就理解了框架。图10.2中主要接口如下。
q ● Collection接口是一组允许重复的对象。
q ● Set接口继承 Collection,但不允许对象重复,使用自己内部的一个排列机制。
q ● List接口继承 Collection,允许对象重复,以元素安插的次序来放置元素,不会重新排列。
q ● Map接口是一组成对的键值对象,Map中不能有重复的key,拥有自己的内部排列机制。
q注意:容器中的元素类型都为Object。从容器取得元素时,必须把它转换成原来的类型。
图10.1 Java 2集合框架图
图10.2是图10.1框架图的简化图。
图10.2 Java 2集合框架简化图
答案
Java集合类的3个部分是接口、实现和算法。
面试例题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()、 |
平衡树 |
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()、 |
平衡树 |
java.util包中的集合实现Collection接口中的所有可选操作。Collection接口常见的方法如下。
(1)单元素添加、删除操作。
q ● boolean add(Object o),将对象添加给集合。
q ● boolean remove(Object o),如果集合中有与o相匹配的对象,则删除对象o。
(2)查询操作。
q ● int size(),返回当前集合中元素的数量。
q ● boolean isEmpty(),判断集合中是否有任何元素。
q ● boolean contains(Object o),查找集合中是否含有对象o。
q ● Iterator iterator(),返回一个迭代器,用来访问集合中的各个元素。
(3)组操作:作用于元素组或整个集合。
q ● boolean containsAll(Collection c),查找集合中是否含有集合c中所有元素。
q ● boolean addAll(Collection c),将集合c中所有元素添加给该集合。
q ● void clear(),删除集合中所有元素。
q ● void removeAll(Collection c),从集合中删除集合c中的所有元素。
q ● void retainAll(Collection c),从集合中删除集合c中不包含的元素。
(4)Collection转换为Object数组:
q ● Object[] toArray(),返回一个内含集合所有元素的数组。
q ● Object[] toArray(Object[] a),返回一个内含集合所有元素的数组。运行期返回的数组和参数a的类型相同。
注意:Collection不提供get()方法。如果要遍历Collectin中的元素,就必须用Iterator。
Collection接口的iterator()方法返回一个Iterator。Iterator接口方法能以迭代方式逐个访问集合中各个元素,并安全地从Collection中除去适当的元素。
图10.4显式了迭代器的UML建模图。
● booleanhasNext():判断是否存在另一个可访问的元素。
q ● Objectnext():返回要访问的下一个元素。如果到达集合结尾,则抛出NoSuchElementException异常。
q ● voidremove():删除上次访问返回的对象。本方法必须紧跟在一个元素的访问后执行。如果上次访问后集合已被修改,方法将抛出IllegalStateException。
迭代器是故障快速修复(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)。
面试例题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集合的实现掌握。
出现频率:★★★★★
解析
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 ● Comparator comparator():返回对元素进行排序时使用的比较器,如果使用Comparable接口的compareTo()方法对元素进行比较,则返回null。
q ● Object first():返回有序集合中第1个元素。
q ● Object last():返回有序集合中最后1个元素。
q ● SortedSet subSet(Object fromElement,Object toElement):返回从fromElement(包括)至toElement(不包括)范围内元素的SortedSet视图(子集)。
q ● SortedSet headSet(Object toElement):返回SortedSet类型的一个视图,各元素皆小于toElement。
q ● SortedSet tailSet(Object fromElement):返回SortedSet类型的一个视图,各元素皆大于或等于fromElement。
(3)HashSet类。
集合框架支持Set接口两种普通的实现:HashSet和TreeSet(TreeSet实现SortedSet接口)。在更多情况下,用户使用HashSet建立元素集合。考虑到效率,添加到HashSet的对象需要实现hashCode()方法。虽然大多数系统类覆盖了Object中默认的hashCode()和equals()实现,但创建自己的要添加到HashSet的类时,一定要覆盖hashCode()和equals()。
HashSet类有如下所示的方法。
q ● HashSet():构建一个空的哈希集。
q ● HashSet(Collection c):构建一个哈希集,并且添加集合c中所有元素。
q ● HashSet(int initialCapacity):构建一个拥有特定容量的空哈希集。
q ● HashSet(int initialCapacity, float loadFactor):构建一个拥有特定容量和加载因子的空哈希集。LoadFactor是0.0~1.0之间的一个数。
当要从集合中以有序的方式插入和抽取元素时,TreeSet实现会有用处。为了能顺利进行,添加到TreeSet的元素必须是可排序的。
(4)LinkedHashSet类。
LinkedHashSet类扩展于HashSet类。如果想跟踪添加给HashSet的元素的顺序,可以创建LinkedHashSet对象。LinkedHashSet对象是一个可以快速访问各个元素的有序集合,它的迭代器按照元素的插入顺序来访问各个元素。同时,增加了实现的代价,因为哈希表元中的各个元素是通过双重链接式列表链接在一起的。
q ● LinkedHashSet():构建一个空的链接式哈希集。
q ● LinkedHashSet(Collection c):构建一个链接式哈希集,并且添加集合c中所有元素。
q ● LinkedHashSet(int initialCapacity):构建一个拥有特定容量的空链接式哈希集。
q ● LinkedHashSet(int initialCapacity, float loadFactor):构建一个拥有特定容量和加载因子的空链接式哈希集。LoadFactor是0.0~1.0之间的一个数。
列表是按照顺序存放元素的集合,可以包含重复的元素,这一点同集Set是不同的。列表可以使用1个从0开始的索引值来访问指定位置的元素。
(1)List接口。
List接口继承了Collection接口,容许定义一个允许重复项的有序集合。该接口不但能够对列表的一部分进行处理,还添加了面向位置的操作。
q ● void add(int index, Object element):在指定位置index上添加元素element。
q ● boolean addAll(int index, Collection c):将集合c的所有元素添加到指定位置index。
q ● Object get(int index):返回List中指定位置的元素。
q ● int indexOf(Object o):返回出现第1个元素o的位置,如果没有找到指定元素则返回-1。
qq int lastIndexOf(Object o):返回出现最后1个元素o的位置,如果没有找到指定元素则返回-1。
q ● Object remove(int index):删除指定位置上的元素。
q ● Object set(int index, Object element):用元素element取代位置index上的元素,并且返回旧的元素。
q ● ListIterator listIterator():返回一个列表迭代器,用来访问列表中的元素。
q ● ListIterator listIterator(int index):返回一个列表迭代器,用来从指定位置index开始访问列表中的元素。
q ● List subList(int fromIndex, int toIndex):返回从指定位置fromIndex(包含)到toIndex(不包含)范围中各个元素的列表视图。
(2)List接口实现类。
Java.util包提供了3个List接口的实现类:LinkedList、ArrayList和Vector。
ArrayList和Vector类都使用数组方式存储数据,数组元素数大于实际存储的数据数目以便增加和插入元素。它们都允许直接按序号索引元素,但是插入元素涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。Vector由于使用了synchronized方法(线程安全),通常性能上较ArrayList差。LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。
q ● LinkedList类实现了List接口,允许包含null元素。
注意:LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List,如下所示:
List list = Collections.synchronizedList(new LinkedList(...));
q ● ArrayList实现了可变大小的数组,它允许包含所有类型的元素,包括null元素。每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加的新元素而自动增加。当需要插入大量元素时,在插入前可以调用ensureCapacity()方法来增加ArrayList的容量以提高插入效率。和LinkedList一样,ArrayList也是非同步的(unsynchronized)。
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++) {