Java Collections API作为许多标准Java数组及其所有缺点的急需替代品而传给了许多Java开发人员。 将集合主要与ArrayList
并不是一个错误,但是对于那些寻找的人来说,集合还有很多。
类似地,尽管Map
(及其经常选择的实现, HashMap
)非常适合执行名称-值或键-值对,但没有理由将自己局限于这些熟悉的工具。 您可以使用正确的API甚至正确的Collection修复很多容易出错的代码。
这5件事系列的第二篇文章是专门针对Collections的第一篇,因为它们对于我们在Java编程中的工作是如此重要。 我将从一开始就研究最快(但可能不是最常见)的日常工作方式,例如将Array
换成List
。 之后,我们将深入研究鲜为人知的内容,例如编写自定义Collections类和扩展Java Collections API。
1.收集王牌数组
刚接触Java技术的开发人员可能不知道数组最初是包含在该语言中的,以防止1990年代初期C ++开发人员对性能的批评。 好吧,自那时以来我们已经走了很长一段路,而与Java Collections库相比,数组的性能优势通常会显得不足。
例如,将数组内容转储到字符串中,需要遍历数组并将内容串联在一起成为String
; 而Collections实现都具有可行的toString()
实现。
除极少数情况外,优良作法是尽快将自己遇到的任何数组转换为集合。 哪个提出了问题,最简单的切换方式是什么? 事实证明,Java Collections 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()
使用varargs参数将元素添加到List
,因此您还可以使用它轻松地new
ed对象创建List
。
2.迭代效率低下
想要将一个集合(尤其是从数组中制造出来的集合)的内容移到另一个集合中,或者从较大的集合中删除一小部分对象,这种情况并不少见。
您可能会想简单地遍历集合并添加或删除找到的每个元素,但是不要这么做。
在这种情况下,迭代具有主要缺点:
- 通过每次添加或删除来调整集合的大小将是低效的。
- 每次获取锁,执行操作并释放锁时,可能存在并发噩梦。
- 在添加或删除过程中,其他线程在您的集合上跳动会导致竞争状况。
通过使用addAll
或removeAll
传递包含要添加或删除的元素的集合,可以避免所有这些问题。
3.用于遍历任何Iterable
增强的for循环是Java 5中Java语言的一大便利,它消除了使用Java集合的最后障碍。
以前,开发人员必须手动获取Iterator
,使用next()
获取从Iterator
指向的对象,然后通过hasNext()
查看是否有更多对象可用。 在Java 5之后,我们可以自由使用for-loop变体来静默处理上述所有问题。
实际上,此增强功能可与实现Iterable
接口的任何对象一起使用,而不仅仅是Collections
。
清单2显示了一种使Person
对象的子级列表可用作Iterator
。 Person
类型实现了Iterable
,而不是分发对内部List
的引用(这将使Person
外部的呼叫者可以将孩子添加到您的家庭中-大多数父母会觉得不舒服)。 这种方法还使增强的for循环可以遍历子级。
清单2.增强循环功能:向我展示您的孩子
// 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()
方法“隐式”支持这样一个对象集合。 但是,对于Iterable
明显且明显的情况, Iterable
使针对域类型的编程变得更加容易和明显。
4.经典和自定义算法
您是否曾经想过Collection
,但是相反呢? 那就是经典Java Collections算法派上用场的地方。
上面清单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设计中的硬道理,例如,我更喜欢不直接修改(传入的Collection的)内容的方法。 因此,您可以编写自己的自定义算法是一件好事,如清单4所示:
清单4. ReverseIterator变得更简单
class MyCollections
{
public static <T> List<T> reverse(List<T> src)
{
List<T> results = new ArrayList<T>(src);
Collections.reverse(results);
return results;
}
}
5.扩展Collections API
上面的定制算法说明了有关Java Collections API的最后一点:始终打算对其进行扩展和变形以适合开发人员的特定目的。
因此,例如,假设您需要始终按年龄对Person
类中的孩子列表进行排序。 尽管您可以编写代码来对子项进行一遍又一遍的排序(也许使用Collections.sort
方法),但是最好有一个Collection
类为您排序。
实际上,您甚至可能都不关心保留将对象插入Collection
的顺序(这是List
的主要原理)。 您可能只想使它们保持排序。
java.util
没有Collection
类可以满足这些要求,但是编写一个很简单。 您需要做的就是创建一个接口,该接口描述Collection
应该提供的抽象行为。 对于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 Collections API从来都不打算成为与集合相关的所有事物的最终词汇。 它既需要并鼓励扩展。
当然,某些扩展将属于“重型”品种,例如java.util.concurrent
引入的那些扩展。 但是其他方法将像编写自定义算法或对现有Collection
类的简单扩展一样简单。
扩展Java Collections API似乎不堪重负,但是一旦开始这样做,就会发现它远没有您想象的那么难。
结论
像Java序列化一样,Java Collections API充满了无法探索的角落和缝隙-这就是为什么我们没有完成本主题的原因。 5件事系列中的下一篇文章将为您提供五种使用Java 6 Collections API进行更多操作的方法。
翻译自: https://www.ibm.com/developerworks/java/library/j-5things2/index.html