定制与扩展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

import  java.util. * ;

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. 迭代的效率不高
    将一个集合对象(特别是该集合是由一数组转化而成的)中的内容移入到另一个集合对象,或是从一个较大对象集合中删除一个较小的对象集合,这些都是不常见的需求。
    你可能会尝试着迭代该集合,然后向其中添加或删除每一个被找到的元素,但不能这样做。
    在这个例子中,迭代有很大的缺点:

  • 在每一次添加或删除动作之后,无法高效地重新调整集合的大小。
  • 为了进行这些操作,在获取和释放锁时会有潜在的并发性梦魇。
  • 当正在进行添加或删除元素时,其它线程也在操作你的集合,这就会导致竞争条件。
针对你想添加或删除元素的集合对象,使用addAll或removeAll方法,你就能避免上述所有问题。

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种方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值