John Jiang

a cup of Java, cheers!
https://github.com/johnshajiang/blog

   :: 首页 ::  :: 联系 :: 聚合  :: 管理 ::
  131 随笔 :: 1 文章 :: 530 评论 :: 0 Trackbacks
你所不知道的五件事情--Java集合框架API(第一部分)
                                                  --定制与扩展Java集合框架
这是Ted NewardIBM developerWorks5 things系列文章中的一篇,讲述了关于Java集合框架的一些应用窍门,值得大家学习。(2010.05.28最后更新)

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

请关注你所不知道的五件事情--Java集合框架API(第二部分)

祝大家五·一假期愉快 ^_^

posted on 2010-05-02 18:21 John Jiang 阅读(6120) 评论(3)  编辑  收藏 所属分类: Java翻译

评论

# re: 你所不知道的五件事情--Java集合框架API(第一部分)(译)[未登录] 2010-05-08 22:05 cc
Person ted = new Person("Ted", "Neward", 39,
new Person("Michael", "Neward", 16),
new Person("Matthew", "Neward", 10));
这段明显跟你的Person构造方法,感觉应该写个kid(String fn, String ln, int a)来继承Person对象;
Person ted = new Person("Ted", "Neward", 39,
new Kid("Michael", "Neward", 16),
new Kid("Matthew", "Neward", 10));
这样才对吧!  回复  更多评论
  

# re: 你所不知道的五件事情--Java集合框架API(第一部分)(译)[未登录] 2010-05-08 22:19 cc
其实也就是说一句话:在jdk1.5中,只要我们的类实现了Iterable接口的,即也可以使用如下语句, for(Object o : iterable);

  回复  更多评论
  

# re: 你所不知道的五件事情--Java集合框架API(第一部分)(译) 2010-05-08 22:47 Sha Jiang
@cc
> 感觉应该写个kid(String fn, String ln, int a)来继承Person对象;
> Person ted = new Person("Ted", "Neward", 39,
> new Kid("Michael", "Neward", 16),
> new Kid("Matthew", "Neward", 10));
我认为不需要创建这个Kid类。
至少从你的示例没看出这种"必要性"。实质上,Kid并没有对Person进行扩展。  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航: