未验证 提交 fb5a0009 编写于 作者: 飞龙 提交者: GitHub

Merge pull request #8 from renyuhuiharrison/master

第3章译文初版提交
# 第三章 满足规则
> 译者:[renyuhuiharrison](https://github.com/renyuhuiharrison)
在第二章中,我们看了并练习了许多抽象接口(abstract interfaces),从某种意义上来说它们是抽象的,这些接口是用来描述共同功能属性、方法签名(method signatures)、以及所有类型集。我们还没有讲到这些类型内部细节,也还没有讲实现这些接口的具体对象(concrete objects)是如何创建的。
在本章中,我们通过展示填空的方法,来进一步探讨这些接口的具体成员。从某种程度上来讲,它们并不是严格意义上的实现,它们将使用“原始简单”、相对低效的数据结构。不过,我们的目的是熟练面向对象编程的原理,以便你能随处表现自己的构想。
为了帮助编程人员引入我们之前提到的的抽象接口的全新实现,Java标准库提供一个对应类似的抽象类集,其中实现了一些方法。只要你编写出一些关键、未实现的方法,你会“毫无代价地”得到其余方法。在大部分普通程序中,这些部分实现的类不是直接拿来使用的,而仅仅是为类库开发者提供参考。这里给出了一张包含这些类的表格,它们其中一部分的接口是已实现的(都来自java.util包):
抽象类|接口
-|-
AbstractCollection|Collection
AbstractSet|Collection、 Set
AbstractList|Collection、 List
AbstractSequentialList|Collection、 List
AbstractMap|Map
这种调用部分实现的思路是一种叫做模板模式(Template Method)的设计模式。*设计模式*(design pattern)是用在面向对象的编程中,它是“一种在程序设计中用来解决特定问题的核心思路”。这个抽象类是给真正的实现来使用的。我们将这些形如**Abstract...**的类当做模板,用来作真正的实现。使用方法重载(method overriding),在方法中补全实现,在模板的其它每个地方将使用那些方法。
```java
import java.util.*;
import java.lang.reflect.Array;
public class ArrayCollection<T> implements Collection<T> {
private T[] data;
/** An empty Collection */
public ArrayCollection () { data = (T[]) new Object[0]; }
/** A Collection consisting of the elements of C */
public ArrayCollection (Collection<? extends T> C) {
data = C.toArray((T[]) new Object[C.size ()]);
}
/** A Collection consisting of a view of the elements of A. */
public ArrayCollection (T[] A) { data = T; }
public int size () { return data.length; }
public Iterator<T> iterator () {
return new Iterator<T> () {
private int k = 0;
public boolean hasNext () { return k < size (); }
public T next () {
if (! hasNext ()) throw new NoSuchElementException ();
k += 1;
return data[k-1];
}
public void remove () {
throw new UnsupportedOperationException ();
}
};
}
public boolean isEmpty () { return size () == 0; }
public boolean contains (Object x) {
for (T y : this) {
if (x == null && y == null
|| x != null && x.equals (y))
return true;
}
return false;
}
```
代码3.1: “从零开始”实现一种新的只读集合(Collection)
```java
public boolean containsAll (Collection<?> c) {
for (Object x : c)
if (! contains (x))
return false;
return true;
}
public Object[] toArray () { return toArray (new Object[size ()]); }
public <E> E[] toArray (E[] anArray) {
if (anArray.length < size ()) {
Class<?> typeOfElement = anArray.getClass ().getComponentType ();
anArray = (E[]) Array.newInstance (typeOfElement, size ());
}
System.arraycopy (anArray, 0, data, 0, size ());
return anArray;
}
private boolean UNSUPPORTED () {
throw new UnsupportedOperationException ();
}
public boolean add (T x) { return UNSUPPORTED (); }
public boolean addAll (Collection<? extends T> c) { return UNSUPPORTED (); }
public void clear () { UNSUPPORTED (); }
public boolean remove (Object x) { return UNSUPPORTED (); }
public boolean removeAll (Collection<?> c) { return UNSUPPORTED (); }
public boolean retainAll (Collection<?> c) { return UNSUPPORTED (); }
}
```
代码3.1续:由于这是只读集合,所有企图对集合的修改操作,都将会抛出UnsupportedOperationExceptio异常,这是一种提示不支持当前操作的常规做法。
在接下来的部分,我们将看到如何去使用这些类,以及它们内部是如何使用Java类库功能的。不过,我们首先得快速地看下可行的方法。
## 3.1 从零开始
为了作比较,我们可以假设,我们想引入一个简单的实现,这个实现允许我们以只读集合(Collection)的形式处理对象(Objects)数组。一个直接的方式正如代码3.1所示。接下来,解释下Collection,前两个构造函数,一个是用来生成一个空的collection(不是很有用,当然是因为你无法再添加它),另一个是通过给定的collection拷贝出一份。第三个构造函数是针对新的类,并提供一个Collection数组(array)视图,——也就是说,Collection中的每项成员对应着数组中的元素,也对应着Collection接口相应的操作。接下来是一些必要的方法。通过**iterator**返回得到的**Iterator**有一个匿名类型,调用**ArrayCollection**的用户无法直接创建这种类型的对象。由于这是只读的收集器,所以不允许使用optional方法(会修改收集器)。
**反射(Reflection)概览**。 第二个**toArray**方法相当有意思,这个方法里使用了Java语言中一个特别的功能:*反射*。它是一种语言特性,允许调用者借助语言本身可以操控编程语言的构造。在英语中,当我们在说诸如“单词'hit'是一个动词”的时候,我们就在使用反射。**toArray**方法产生了一个数组,这个数组是由一组相同动态类型参数组成的。我们是这样做的,首先调用**getClass**,这个方法定义了所有的**Objects**,这样我们得到了内建类型**java.lang.Class**,它用来表示**anArray**参数的动态类型。**Class**类型其中的一个操作是**getComponentType**,这是一个数组类型,可以获取反射元素类型的**Class**。最后,newInstance方法(定义在java.lang.reflect.Array类中)创建了新的对象,需要我们给定它的大小以及表示component类型的**类**
## 3.2 AbstractCollection类
AbstractCollection的实现中有个有意思的功能:**isEmpty**开始的几个方法中,都没有用到ArrayCollection的私有数据,而是依赖其它几个(公有)方法。因此**Collection**类中任何一个实现都可以调用他们。Java标准库中的AbstractCollection类利用了这种观察方式(如代码3.2所示)。这是一个已部分实现的抽象类,新的**Collection**可以得到扩展。至少来说,一个实现部分可以重写**iterator****size**的定义,以便得到一个只读方式收集的类。比如,在代码3.3中展示了一个更加简易的**ArrayCollection**重写形式。此外,如果开发者重写了**add**方法,那么**ArrayCollection**还将自动提供**addAll**。最后,如果**iterator**方法返回了一个支持**remove**方法的**Iterator**,那么**AbstractCollection**将自动提供**clear****remove****removeAll**以及**retainAll**等方法。
在程序中,使用**AbstractCollection**的仅仅是一个**扩展**项。那也就是说,它是一个简单的工具类,对实现部分中创建新的**Collection**带来方便。你不应该把它用来识别正式参数类型、局部变量或者作用域。在这里顺便提一下,在声明**AbstractCollection**时候应该设置为**protected**,这个关键字强调了只有**AbstractClass**的继承部分可以调用它。
在代码3.1中,你已经看到了展示**AbstractClass**是如何运行的五个例子:**isEmpty****contains****containsAll**方法以及两个**toArray**方法。你只要理解了这种常见的思路,就能相当轻松地写出类似的方法。在练习部分中,你将会写更多的方法。
## 3.3 实现List接口
AbstractList和AbstractSequentialList这两个抽象类是由AbstractCollection专门扩展的,AbstractCollection是由Java标准库提供的,它用来帮助开发者定义那些可以实现**List**接口的类。你选择哪种,取决于成员使用具体实现哪种list类型。
### 3.3.1 AbstractList类
在代码3.4中,给出了**List****AbstractList**的实现,为成员提供快速(通常是常数时间)*随机访问*各自元素的功能,即类成员提供了**get****remove**(如果有提供的话)的快速实现方式。代码3.5展示了**listIterator**的运行原理,作为演示的一部分。此外,这个类中还体现了很多有意思的技巧。
**Protected方法****removeRange**方法不在public接口部分。由于它是声明了**protected**,所以只能在其它**java.util**包中进行调用,并且必须得在**AbstractList**扩展体范围之内。这样的方法是给本类及其扩展部分来使用的*实现工具(implementation utilities)*。在**AbstractList**标准实现中,**removeRange**用来实现**clear**(你只要还记得**L.subList(k0,k1).clear()**是一个删除**List**中任意元素,你就会觉得这个方法很重要)。
```java
package java.util;
public abstract class AbstractCollection<T> implements Collection<T> {
/** The empty Collection. */
protected AbstractCollection<T> () { }
/** Unimplemented methods that must be overridden in any
* non-abstract class that extends AbstractCollection */
/** The number of values in THIS. */
public abstract int size ();
/** An iterator that yields all the elements of THIS, in some
* order. If the remove operation is supported on this iterator,
* then remove, removeAll, clear, and retainAll on THIS will work. */
public abstract Iterator<T> iterator ();
/** Override this default implementation to support adding */
public boolean add (T x) {
throw new UnsupportedOperationException ();
}
Default, general-purpose implementations of
contains (Object x), containsAll (Collection c), isEmpty (),
toArray (), toArray (Object[] A),
addAll (Collection c), clear (), remove (Object x),
removeAll (Collection c), and retainAll (Collection c)
/** A String representing THIS, consisting of a comma-separated
* list of the values in THIS, as returned by its iterator,
* surrounded by square brackets ([]). The elements are
* converted to Strings by String.valueOf (which returns "null"
* for the null pointer and otherwise calls the .toString() method). */
public String toString () { ... }
}
```
代码3.2:这个抽象类java.util.AbstractCollection,可以用来帮助实现新的**Collection**。所有的方法都定义在**Collection**的规范说明中。实现部分必须完成对**iterator****size**的定义,并且还要重写其他方法,或者使用它们的默认实现部分(本例中没有给出)。
```java
import java.util.*;
/** A read-only Collection whose elements are those of an array. */
public class ArrayCollection<T> extends AbstractCollection<T> {
private T[] data;
/** An empty Collection */
public ArrayCollection () {
data = (T[]) new Object[0];
}
/** A Collection consisting of the elements of C */
public ArrayCollection (Collection<? extends T> C) {
data = C.toArray(new Object[C.size ()]);
}
/** A Collection consisting of a view of the elements of A. */
public ArrayCollection (Object[] A) {
data = A;
}
public int size () { return data.length; }
public Iterator<T> iterator () {
return new Iterator<T> () {
private int k = 0;
public boolean hasNext () { return k < size (); }
public T next () {
if (! hasNext ()) throw new NoSuchElementException ();
k += 1;
return data[k-1];
}
public void remove () {
throw new UnsupportedOperationException ();
}
};
}
```
代码3.3:重新实现ArrayCollection,使用**java.util.AbstractCollection**中的默认实现。
**removeRange**的默认实现简单调重复地用了remove(k),所以这个运行速度不会很快。但如果一个特殊的**List**成员有更好的方法,那么程序员可以重写**removeRange**,使用**clear**可以有更好的性能(这就是为什么这个方法的默认实现没有在**最后**声明,即使**List**中的任何成员都可以使用)。
**检查有效性**。 正如我们在2.2.3章节中讨论过的,**List**接口的**iterator****listIterator****subList**方法给我们产生了一种观点,即如果改变了list的结构那么它会**变得无效**。那些无视规则的程序员,不会知道**List**的实现做了些什么。使用一个无效的视图可能会导致不可预估的结果或异常。即便如此,**AbstractList**类在显式检查错误时候会遇到麻烦,并且会立即抛出一个特定的异常**ConcurrentModificationException****modCount**(声明为**protected**,以此代表它是用于**AbstractList**,而不是其它地方)会持续追踪**AbstractList**结构修改的数量。每次调用**add**或者**remove**(直接在List中或者通过视图)应该会增加它。个人视图可以持续记录最后一个值,它们在List的**modCount**成员变量中能**监视**这个值。如果这个值在这期间被改变了,那么会有异常抛出。我们将在代码3.5中看到这个范例。
**Helper类****AbstractList**(至少是在Sun的实现中)的**subList**使用了一个非公有的工具类型**java.util.SubList**来得出它的结果。由于它是非公有的,所以**java.util.SubList****java.util**包来说是私有的,并且这不是由官方给出部分。然而,在同一个包中,你可以用它来访问非公有成员变量和**removeRang**的工具方法(**removeRang**)。
### 3.3.2 AbstractSequentialList类
第二个抽象**List**的实现,**AbstractSequentialList**(代码3.6),适用于随机访问相对较慢的成员,当时list迭代器的**next**操作的速度依然很快。
当你想对**get**和迭代器的**next**进行实现时,为这个例子设计一个有显著区别的类,这样的理由已经很充分了。如果我们假设有一个执行很快的**get**方法,那很容易实现一个带有**next**的迭代器,如代码3.5所示。如果**get**执行较慢,也就是说,如果查找list中的元素k,必须对在它前面的k项元素进行排序,那么要实现像范例代码中的**next**将会很糟糕。这个算法将花费O(n<sup>2</sup>)复杂度来迭代一个N个元素的list。所以,使用**get**来实现迭代并不一定是个好主意。
```java
package java.util;
public abstract class AbstractList<T>
extends AbstractCollection<T> implements List<T> {
/** Construct an empty list. */
protected AbstractList () { modCount = 0; }
abstract T get (int index);
abstract int size ();
T set (int k, T x) { return UNSUPPORTED (); }
void add (int k, T x) { UNSUPPORTED (); }
T remove (int k) { return UNSUPPORTED (); }
Default, general-purpose implementations of
add (x), addAll, clear, equals, hashCode, indexOf, iterator,
lastIndexOf, listIterator, set, and subList
/** The number of times THIS has had elements added or removed. */
protected int modCount;
/** Remove from THIS all elements with indices in the
range K0 .. K1-1. */
protected void removeRange (int k0, int k1) {
ListIterator<T> i = listIterator (k0);
for (int k = k0; k < k1 && i.hasNext (); k += 1) {
i.next (); i.remove ();
}
}
private Object UNSUPPORTED ()
{ throw new UnsupportedOperationException (); }
}
```
代码3.4:抽象类AbstractList,用作帮助编写能随机读取的**List**的实现。内部类**ListIteratorImpl**可详见代码3.5。
```java
public ListIterator<T> listIterator (int k0) {
return new ListIteratorImpl (k0);
}
private class ListIteratorImpl<T> implements ListIterator<T> {
ListIteratorImpl (int k0)
{ lastMod = modCount; k = k0; lastIndex = -1; }
public boolean hasNext () { return k < size (); }
public hasPrevious () { return k > 0; }
public T next () {
check (0, size ());
lastIndex = k; k += 1; return get (lastIndex);
}
public T previous () {
check (1, size ()+1);
k -= 1; lastIndex = k; return get (k);
}
public int nextIndex () { return k; }
public int previousIndex () { return k-1; }
public void add (T x) {
check (); lastIndex = -1;
k += 1; AbstractList.this.add (k-1, x);
lastMod = modCount;
}
public void remove () {
checkLast (); AbstractList.this.remove (lastIndex);
lastIndex = -1; lastMod = modCount;
}
public void set (T x) {
checkLast (); AbstractList.this.remove (lastIndex, x);
lastIndex = -1; lastMod = modCount;
}
```
代码3.5:**AbstractList**一部分可能的实现,展示了内部类提供**listIterator**的值。
```java
// Class AbstractList.ListIteratorImpl, continued.
/* Private definitions */
/** modCount value expected for underlying list. */
private int lastMod;
/** Current position. */
private int k;
/** Index of last result returned by next or previous. */
private int lastIndex;
/** Check that there has been no concurrent modification. Throws
* appropriate exception if there has. */
private void check () {
if (modCount != lastMod) throw new ConcurrentModificationException();
}
/** Check that there has been no concurrent modification and that
* the current position, k, is in the range K0 <= k < K1. Throws
* appropriate exception if either test fails. */
private void check (int k0, int k1) {
check ();
if (k < k0 || k >= k1)
throw new NoSuchElementException ();
}
/** Check that there has been no concurrent modification and that
* there is a valid ‘‘last element returned by next or previous’’.
* Throws appropriate exception if either test fails. */
private checkLast () {
check ();
if (lastIndex == -1) throw new IllegalStateException ();
}
```
代码3.5续:**ListIterator**的私有成员
```java
public abstract class AbstractSequentialList<T> extends AbstractList<T> {
/** An empty list */
protected AbstractSequentialList () { }
abstract int size ();
abstract ListIterator<T> listIterator (int k);
Default implementations of
add(k,x), addAll(k,c), get, iterator, remove(k), set
From AbstractList, inherited implementations of
add(x), clear, equals, hashCode, indexOf, lastIndexOf,
listIterator(), removeRange, subList
From AbstractCollection, inherited implementations of
addAll(), contains, containsAll, isEmpty, remove(), removeAll,
retainAll, toArray, toString
}
```
代码3.6:**AbstractSequentialList**
另一方面,如果我们总想通过迭代所有k项元素来实现**get(k)**(也就是说,使用**Iterator**的方法来实现**get**,而不是反转(reverse)),很显然,我们将不可能很快地来执行**get**。我们显然会失去快速获取的表示
## 3.4 AbstractMap类
如代码3.7所示,**AbstractMap****Map**接口提供了模板实现。重写了**entrySet**来提供一个只读的**Set**。另外,重写了**put**方法,给出了一个扩展的**Map**,并且实现了**remove**方法,**entrySet().iterator()**提供一个完全可修改的**Map**
## 3.5 性能预测
在第二章的开始部分,我曾说过对于给定的接口有几种典型实现。在有几种可能的情况下会用到不止一种。首先,要么是因为有特殊的存储项目、关键字、或者需要特殊操作的值,要么是为了运行速度,或者是对这些特殊类型要做额外操作。其次,一些特定的**Collections**或是**Maps**可能需要一种特殊的实现,因为他们是其它的一部分,比如说**subList****entrySet**视图。再次,一种实现可能在某些情况下的性能好,但在其它情况下可能就不好。最后,在不同的实现之间可能有时间和空间的权衡,并且一些应用程序可能需要比较紧凑(节省空间)的成员结构。
```java
package java.util;
public abstract class AbstractMap<Key,Val> implements Map<Key,Val> {
/** An empty map. */
protected AbstractMap () { }
/** A view of THIS as the set of all its (key,value) pairs.
* If the resulting Set’s iterator supports remove, then THIS
* map will support the remove and clear operations. */
public abstract Set<Entry<Key,Val>> entrySet ();
/** Cause get(KEY) to yield VAL, without disturbing other values. */
public Val put (Key key, Val val) {
throw new UnsupportedOperationException ();
}
Default implementations of
clear, containsKey, containsValue, equals, get, hashCode,
isEmpty, keySet, putAll, remove, size, values
/** Print a String representation of THIS, in the form
* {KEY0=VALUE0, KEY1=VALUE1, ...}
* where keys and values are converted using String.valueOf(...). */
public String toString () { ... }
}
```
代码3.7:**AbstractMap**
我们不能对这里的**Abstract...**类成员有具体的性能要求,因为他们是模板而不是完整的实现。不过,我们可以把他们作为一个待开发者补充的函数来描述其性能。这里,让我们考虑两个例子:**List**接口的实现模板。
**AbstractList**。 在AbstractList背后的策略是使用了**size****get(k)****add(k,x)****set(k,x)**以及**remove(k)**这些方法,它们是由扩展类型提供的,这些扩展类型还用来实现了其它部分。**listIterator**方法返回了一个**listIterator**,这个**listIterator**使用了**get**来实现**next****previous**,使用**add**(在AbstractList中的)实现了迭代器的**add**,使用**remove**(在AbstractList中的)实现了迭代器的**remove**。由增加或减少的整型变量构成迭代器实现了记账簿(bookkeeping)的功能,因此它的花费是一个较小的常数时间。这样看来,我们可以轻松地将迭代函数的耗费直接和那些方法(下表中所展示的)关联上。为了简化问题复杂度,对于个体而言的**size****equals**操作,我们将它们所耗费的时间看做是个常数。我们将"plugged-in"方法的数值以C<sub>α</sub>的形式给出;**this**(这里的**List**)的大小是N,其它收集器参数(表示为c,这里我们假设是相同类型的**List**,只是为了可以讲更多的内容)的大小是M。
```math
```
**AbstractList**实现的耗费
**AbstractSequentialList**。 现在让我们比较一下AbstractList和AbstractSequentialList的实现,后者未使用低效的**get**操作,但它仍然使用了低效的迭代器。在这个例子中,**get(k)**操作是由新创建的**AbstractSequentialList**,同时由一个耗费**k**倍的时间性能**next**操作一起来实现的。让我们看下这张表格:
```math
```
#练习
3.1. 为**AbstractCollection**写出一个**addAll**的实现。在它做**增加**操作时,如果新增了收集器不支持的元素,则会抛出**AbstractCollectio**,如果支持的话,就新增一个元素。
3.2. 为**AbstractCollection**写出一个**removeAll**的实现。如果支持当前移除操作,使用**iterator**来进行**remove**的操作。
3.3. 为**java.util.SubList**写出一个实现。这个工具类实现了**List**,并且有这样的一个构造函数:
```java
/** A view of items K0 throught K1-1 of THELIST. Subsequent
* modifications to THIS also modify THELIST. Any structural
* modification to THELIST other than through THIS and any
* iterators or sublists derived from it renders THIS invalid.
* Operations on an invalid SubList throw
* ConcurrentModificationException */
SubList (AbstractList theList, int k0, int k1) { ... }
```
3.4. 为一个**AbstractSequentialList**类写出**add(k,x)****get**的实现。它在列表的任何一端或附近,都可以很快地获取一个元素。
3.5. 扩展**AbstractMap**类,编写更多**Map**的实现。试着尽可能地不要依赖**AbstractMap**,仅仅实现需要的部分。比如说要写一个成员,写出**Map.Entry**的实现,要使用到Java库提供的现有的**Set**,即**HashSet**。把最终实现的类叫做**SimpleMap**
3.6. 在3.5章节,我们没有谈论到**subList**方法返回**Lists**操作的性能。请估算出**AbstractMap****AbstractSequentialList**的性能。对于**AbstractSequentialList****get**方法所需要的时间必须依赖**subList**(起点位置)取决于第一个参数。为什么是这样呢?在**ListIterator**定义中的哪些更改,不会让子列表上的**get**(和其他操作)的性能受到子列表的原始列表中的位置的影响?
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册