TA的每日心情 | 开心 2021-3-12 23:18 |
---|
签到天数: 2 天 [LV.1]初来乍到
|
这是Ted Neward在IBM developerWorks中5 things系列文章中的一篇,讲述了关于java集合框架的一些应用窍门,值得大家学习。(2010.05.02最后更新)
概要: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种方法。
源码下载:http://file.javaxxz.com/2014/10/30/235548812.zip |
|