--定制与扩展Java集合框架
概要:Java集合API远不止是数组的替代品,尽管那是一个不坏的认知起点。Ted Neward展示了5个能更大利用集合框架的窍门,包含一个定制并扩展Java集合API的入门级应用。
对于许多Java开发者而言,Java集合API是标准的Java数组及其全部缺点的必要替代品。将集合框架主要与ArrayList联系起来并不是一个错误,但集合框架中还有许多需要关注的地方。
同样地,尽管对于名-值或键-值对来说,Map(及其它的常选实现HashMap)是极好的,但仍没理由把自己限制在这些熟悉的工具中。你可使用正确的 API,甚至是正确的集合API去修正许多存在潜在错误的代码。
本文是5 things系列的第二篇文章,也是针对集合框架若干篇文章中的第一篇,因为在我们进行Java编程时,集合框架处于核心地位。开始时,将会看到处理常见工作,例如将Array转换成List,的便捷(但并不是最通用的)方法。之后,我们将深入研究集合框架中不太常见的主题,例如扩展Java集合框架API并定制一个集合类.
1. 使用集合对象处理数组
新接触Java技术的开发者可能不知道数组是天然包含在Java语言中的,这是为了应对上世纪九十年代早期来自于C++开发者们有关性能的批评。是的,从那以后,我们已经历了很长时间,当与Java集合类库相比较时,数组的性能优势正在变小。
例如,将数组中的内容导出成一个字符串时,需要遍历数组,然后将其中的内容连接成一个String类型;然而,集合框架的实现都有其各自不同的 toString实现。
除了极少数情况,一种好的实践方式就是尽快将任何数组转换成集合对象。这就提出了一个问题,有什么最简单的方法来做这种转换呢?事实证明,Java集合框架API使这一过程变得简单,如清单1所示:
清单1. ArrayToList
public class ArrayToList
{
public static void main(String[] args)
{
// This gives us nothing good
System.out.println(args);
// Convert args to a List of String
List<String> argList = Arrays.asList(args);
// Print them out
System.out.println(argList);
}
}
注意,返回的List是不可修改的,所以当试图向其中添加新的元素时,将会抛出UnsupportedOperationException。
因为Arrays.asList方法对添加到List中的元素使用vararg(可变长参数),你就可使用它轻松地创建被新建对象的List对象。
2. 迭代的效率不高
将一个集合对象(特别是该集合是由一数组转化而成的)中的内容移入到另一个集合对象,或是从一个较大对象集合中删除一个较小的对象集合,这些都是不常见的需求。
你可能会尝试着迭代该集合,然后向其中添加或删除每一个被找到的元素,但不能这样做。
在这个例子中,迭代有很大的缺点:
- 在每一次添加或删除动作之后,无法高效地重新调整集合的大小。
- 为了进行这些操作,在获取和释放锁时会有潜在的并发性梦魇。
- 当正在进行添加或删除元素时,其它线程也在操作你的集合,这就会导致竞争条件。
3. 利用for循环遍历Iterable对象
在Java 5中加入的最好的Java语言便捷特性之一,改进的for循环,消除了操作Java集合对象的最后一道障碍。
在此之前,开发者们必须得手工地获取Iterator,使用next()方法去获得Iterator中被指定的对象,以及通过hasNext()方法来检查是否还有更多的对象。在Java 5之后,我们可以方便地使用for-loop变体来默默地处理上述所有操作。
准确地说,这种改进并不仅仅针对集合对象,而可用于任何实现了Iterable接口的对象。
清单2展示了如何将Person对象中的children作为一个Iterator去制成一个列表。不同于操作一个指向内部List的引用(这会允许 Person的外部调用者向你的家庭中添加新的子女--大多数父母会对此感到不快的),Person类型实现了Iterable接口。该方法也能使改进的 for循环可遍历其中的孩子列表。
清单2. 改进的for循环:列出你的子女
// Person.java
import java.util.*;
public class Person
implements Iterable<Person>
{
public Person(String fn, String ln, int a, Person kids)
{
this.firstName = fn; this.lastName = ln; this.age = a;
for (Person child : kids)
children.add(child);
}
public String getFirstName() { return this.firstName; }
public String getLastName() { return this.lastName; }
public int getAge() { return this.age; }
public Iterator<Person> iterator() { return children.iterator(); }
public void setFirstName(String value) { this.firstName = value; }
public void setLastName(String value) { this.lastName = value; }
public void setAge(int value) { this.age = value; }
public String toString() {
return "[Person: " +
"firstName=" + firstName + " " +
"lastName=" + lastName + " " +
"age=" + age + "]";
}
private String firstName;
private String lastName;
private int age;
private List<Person> children = new ArrayList<Person>();
}
// App.java
public class App
{
public static void main(String[] args)
{
Person ted = new Person("Ted", "Neward", 39,
new Person("Michael", "Neward", 16),
new Person("Matthew", "Neward", 10));
// Iterate over the kids
for (Person kid : ted)
{
System.out.println(kid.getFirstName());
}
}
}
对于域模型,使用Iterable接口会有一些明显的缺点,因为只有一个对象集合能够通过iterator()方法得到如此"隐式"的支持。在这种情况下,children集合在何处是确定且透明的,然而,Iterable接口可使程序设计在应对域类型时要容易得多,也会更明显。
4. 典型算法与定制算法
你曾经会想过遍历一个集合对象,但是从相反的方向呢?这就是典型的Java集合框架算法用得上的地方。
上述清单2中Person的子女以他们被添加的顺序来排列的;但现在你希望以相反的顺序列出这些子女。当你编写另一个for循环,以相反的顺序向各个对象添加到一个新的ArrayList中时,代码在编写了第三或第四遍之后将变得冗长。
清单3就是未被利用的算法:
清单3. ReverseIterator
public class ReverseIterator
{
public static void main(String[] args)
{
Person ted = new Person("Ted", "Neward", 39,
new Person("Michael", "Neward", 16),
new Person("Matthew", "Neward", 10));
// Make a copy of the List
List<Person> kids = new ArrayList<Person>(ted.getChildren());
// Reverse it
Collections.reverse(kids);
// Display it
System.out.println(kids);
}
}
Collections类有一组这样的"算法",该类所实现的静态方法将Collections作为参数,并且提供了完全独立于任何实现的行为。
另外,Collections类所示的算法在好的API设计中肯定不会是最终版本--例如,我不希望直接修改这些方法(由集合对象传入)的内容。你可以为自己编写定制的算法,这是一件很好的事情,如清单4所示:
清单4. 简洁的反转迭代器
class MyCollections
{
public static <T> List<T> reverse(List<T> src)
{
List<T> results = new ArrayList<T>(src);
Collections.reverse(results);
return results;
}
}
5. 扩展的集合框架API
上述定制的算法证明了Java集合API的最后一个问题:Java集合框架一直被设计为是可扩展的,可被改变以去适应开发者的特殊目的。
例如,假设你需要Person中的children总是按年龄排序。虽然你可以一遍一遍地编写代码去对children进行排序(可能会使用 Collections.sort方法),要是有一个集合类能够帮你作这样的排序则会好得多。
实际上,你可能并不关心添加到集合中的对象是以何种顺序存储的(这就是使用List的主要理由),你只是想让它们保持一定的顺序罢了。
java.util包中没有哪一个集合类能满足这些需求,写一个却很容易。你所需要做的只是创建一个接口,该接口描述了该集合类需要提供的抽象行为。在 SortedCollection这个例子中,上述意图是完全可行的。
清单5. SortedCollection
public interface SortedCollection<E> extends Collection<E>
{
public Comparator<E> getComparator();
public void setComparator(Comparator<E> comp);
}
基本上是虎头蛇尾般地写成了这个新接口的实现:
清单6. ArraySortedCollection
import java.util.*;
public class ArraySortedCollection<E>
implements SortedCollection<E>, Iterable<E>
{
private Comparator<E> comparator;
private ArrayList<E> list;
public ArraySortedCollection(Comparator<E> c)
{
this.list = new ArrayList<E>();
this.comparator = c;
}
public ArraySortedCollection(Collection<? extends E> src, Comparator<E> c)
{
this.list = new ArrayList<E>(src);
this.comparator = c;
sortThis();
}
public Comparator<E> getComparator() { return comparator; }
public void setComparator(Comparator<E> cmp) { comparator = cmp; sortThis(); }
public boolean add(E e)
{ boolean r = list.add(e); sortThis(); return r; }
public boolean addAll(Collection<? extends E> ec)
{ boolean r = list.addAll(ec); sortThis(); return r; }
public boolean remove(Object o)
{ boolean r = list.remove(o); sortThis(); return r; }
public boolean removeAll(Collection<?> c)
{ boolean r = list.removeAll(c); sortThis(); return r; }
public boolean retainAll(Collection<?> ec)
{ boolean r = list.retainAll(ec); sortThis(); return r; }
public void clear() { list.clear(); }
public boolean contains(Object o) { return list.contains(o); }
public boolean containsAll(Collection <?> c) { return list.containsAll(c); }
public boolean isEmpty() { return list.isEmpty(); }
public Iterator<E> iterator() { return list.iterator(); }
public int size() { return list.size(); }
public Object[] toArray() { return list.toArray(); }
public <T> T[] toArray(T[] a) { return list.toArray(a); }
public boolean equals(Object o)
{
if (o == this)
return true;
if (o instanceof ArraySortedCollection)
{
ArraySortedCollection<E> rhs = (ArraySortedCollection<E>)o;
return this.list.equals(rhs.list);
}
return false;
}
public int hashCode()
{
return list.hashCode();
}
public String toString()
{
return list.toString();
}
private void sortThis()
{
Collections.sort(list, comparator);
}
}
这个应急式的实现,未经缜密的思索就写成了,毫无疑问它需要进行重构。但重点是,对于所有与集合相关的事情中,Java集合框架API原本就不是被设计成不需被修改的。它既需要也鼓励扩展。
当然,有一些扩展会是有"重要职责"的变体,例如由java.util.concurrent包引用的扩展实现。但其它的一些则只是简单地编写一个定制算法,或是对已有集合类的一个简单扩展。
扩展Java集合框架API看起来很具颠覆性,但一旦你开始做了,你就会发现并不如你所想像的那般困难。
结论
与Java序列化相同,Java集合框架API满是未被探究的细微之处--这也是为什么我们没有结束该主题的原因。5 things系列的下一篇文章将会向你展示使用Java集合框API做更多事情的另外5种方法。