集合是java常要使用的对象。本文对其中重要知识做个总结。
1. Iterator
迭代器是种设计模式,可以屏蔽各种集合的差异。使用时一要注意 它的remove()方法:每次使用它之前必须先调用 next(),方法,因为remove()删除的正是上次next方法返回的元素。二要注意迭代过程中不能修改Collection的元素。类似的,使用foreach迭代时也不能修改集合元素。
2. Set集合
HashSet是set接口的典型实现,有如下特点 :
不能保证元素的排列顺序;不是同步的(线程不安全);元素值可以是null。
在向HashSet集合存入一个元素时,会调用该对象的hashCode()方法来决定HashSet里存储位置。如果两个元素的equals()方法比较返回true,但是hashCode()返回不等,则会存储在不同的位置。简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals()方法比较相等,且hashCode()返回值相同。
因此,如果要把某个自定义类对象保存到HashSet集合,需重写equals()方法和hashCode()方法,并尽量保证两个对象equals()方法返回true时hashCode()返回也是相等。
从上面的分析不难得出结论,假设一个类的hashCode方法和equals方法和它的一个属性值唯一相关,如果修改了对象属性值就可以出现相同对象的情况;但因为HashSet已经把他们存在不同地方,所以这会引起混乱。因此建议不要添加可改变的对象。下面给个实例:
class R{
int count;
public R(int count)
{
this.count = count ;
}
public String toString()
{
return "R[count:"+count+"]";
}
public boolean equals(Object obj)
{
if(this == obj) return true;
if(obj != null && obj.getClass() == R.class)
{
R r = (R)obj;
if(r.count == this.count)
{
return true;
}
}
return false;
}
public int hashCode()
{
return this.count;
}
}
public class SetTest{
public static void main(String[] args)
{
HashSet hs = new HashSet();
hs.add(new R(5));
hs.add(new R(-3));
hs.add(new R(9));
hs.add(new R(-2));
System.out.println(hs);
Iterator it = hs.iterator();
R first =(R)it.next();
first.count = -3;
System.out.println(hs);
hs.remove(new R(-3));
System.out.println(hs);
System.out.println("hs contains -3 ?"+ hs.contains(new R(-3)));
System.out.println("hs contains 5 ?"+ hs.contains(new R(5)));
}
}
代码首先添加四个不同元素,实际顺序是 5,9,-3,-2。修改后是-3,9,-3,2,也就是出现了相同元素。执行remove时,利用它的hashCode找到存储位置,也就是第三个元素被删除。成了 -3,9,2。最后发现 hs.contains(new R(-3))是false,原因也是通过找到存储位置再比较,此时-3存储的位置也不是new R(-3)对象,所以返回false。同理,最后一句也是返回false.
Set的其他实现还有TreeSet、SortSet等,一般我们是考虑使用HashSet的,因为TreeSet需要额外的红黑树来维护元素次序,它的性能不及HashSet。只有需要一个保持排序的Set,才推荐使用TreeSet。(TreeSet判断相等时equals方法和compare方法,这里不展开说明。)
关于上面的线程不安全问题,Collections工具类封装了较好的实现,只需如下代码:
HashSet hs = Collections.synchronizedHaset(new HashSet());
3. List集合
List集合判断相同的条件是equals方法。
考虑下面的代码:
// 类A的equals方法始终返回true
List book = new ArrayList();
book.add(new Stirng("A"));
book.add(new Stirng("B"));
book.add(new Stirng("C"));
book.remove(new A());
上面的remove方法是可以成功执行的,因为内部的机制是先调用new A()对象的equals方法依次和集合元素比较,如果返回true,则成功删除。
List的两种典型实现是ArrayList和Vector,其中Vector是个古老的集合,有很多功能重复的方法,一般推荐使用ArrayList。另一方面,由于Vector是线程安全的,所以Vector的性能比ArrayList差。实际上,使用上面提供的Collections工具类的方法可以解决这一问题。
这里特别指出一个常用的生成List的方法,ArrayList.asList(Object...a),要牢记这个List集合既不是ArrayList实现类,也不是Vector的实现类,而是Arrays的内部类ArrayList的实例,它是个固定长度的集合,不可增删。
4. Map集合
熟悉JDKAPI的读者会发现Map和Set的API有惊人的相似,这是因为Map提供了一个Entry内部类来封装key-value,而计算Entry的存储时只考虑Entry封装的key。从源码来看,如果把所有的value都为null就实现类Set集合。
典型实现有HashMap和Hashtable,,推荐HashMap。因为Hashtable是个古老的类,从它不符合规范的命令就可以看出。除此之外,它们的其他典型区别有:Hashtable线程安全;HashMap允许null为key,value。
类似HashSet,HashMap判断两个key相等的依据是两个key的equals()方法比较true,hashCode返回值相等。
5. Collections工具类
Collections是用以操作Set,List,Map的工具类。提供了像static void reverse(List list),static void sort(List list)等实用方法。
上面也提及了Collections对同步的控制,语法很简单,即使用 synchronizedXxx()方法。
上面也提及推荐在HashSet、HashMap里尽量使用不可变对象。Collections也提供相应的保证。
一种方法是 emptyXxx()方法,返回一个空的、不可变集合对象;二是使用 singletonXxx();三是使用 unmodifiableXxx()。
List list = Collections.emptyList();
Set set = Collections.singleton("A");
6.
最后谈一下古老的接口Enumeration,在很多代码里可能有它的踪影,因此读者会觉得它很神秘。
它只有两个方法 boolean hasMoreElements(),Object nextElement()。
我们是不推荐使用它的,因为Interator能完成它的所有功能,且性能更好。Java 新版本保留它仅是为了兼顾以前的程序。