淘宝店铺网站策划书,网站建设哪种语言好,wordpress建立cms,外国网站接单做翻译参数化类型是不变的#xff08; invariant #xff09; 。 换句话说#xff0c;对于任何两个截然不同的类型 Typel 和 Type2 而言#xff0c; ListType1 #xff1e;既不是 ListType 2 #xff1e; 的子类型#xff0c;也不是它的超类型 。虽然 L istString… 参数化类型是不变的 invariant 。 换句话说对于任何两个截然不同的类型 Typel 和 Type2 而言 ListType1 既不是 ListType 2 的子类型也不是它的超类型 。虽然 L istString不是 ListObject的子类型这与直觉相悖但是实际上很有意义 。你可以将任何对象放进一个ListObject中却只能将字符串放进 ListString中 。由于 ListString不能像 ListO句ect 能做任何事情它不是一个子类型 。 有时候我们需要的灵活性要比不变类型所能提供的更多 。比如第 29 条中的堆楼 。 提醒一下下面就是它的公共 API
public class StackE {public Stack();public void push(E e);public E pop();public boolean isEmpty();
}假设我们想要增加一个方法让它按顺序将一系列的元素全部放到堆枝中 。 第一次尝试如下
public void pushAll(IterableE src) {for(E e : src)push(e);
}这个方法编译时正确无误但是并非尽如人意 。 如果 Iterable 的 src 元素类型与堆栈的完全匹配就没有问题 。 但是假如有一个 StackNumber并且调用了 push (intVal),这里的工ntVal 就是 Integer 类型 。 这是可以的因为 Integer 是 Number 的一个子类型 。 因此从逻辑上来说下面这个方法应该可行
Stack Number numberStack new Stack() ;
IterableInteger integers ...;
numberStack. pushAll(integers);
但是如果尝试这么做就会得到下面的错误消息因为参数化类型是不可变的 幸运的是有一种解决办法 。Java 提供了一种特殊的参数化类型称作有限制的通配符类型bounded wildcard type 它可以处理类似的情况 。pushAll 的输入参数类型不应该为“ E 的 Iterable 接口”而应该为“ E 的某个子类型的 Iterable 接口”通配符类型Iterable?extends E 正是这个意思 。 使用关键字 ex ten也有些误导 回忆一下第29 条中的说法确定了子类型 subtype 后每个类型便都是自身的子类型即使它没有将自身扩展 。我们修改一下 pushAll 来使用这个类型
public void pushAll(Iterable? extends E src) {for(E e : src)push(e);
} 修改之后不仅 Stack 可以正确无误地编译没有通过初始的 pushAll 声明进行编译的客户端代码也一样可以 。 因为 Stack 及其客户端正确无误地进行了编译你就知道一切都是类型安全的了 。 现在假设想要编写一个 pushAll 方法使之与 popAll 方法相呼应 。popAll 方法从堆校中弹出每个元素并将这些元素添加到指定的集合中 。 初次尝试编写的 popAll 方法可能像下面这样
public void popAll(Col1ectionE dst) {while (!isEmpty())dst.add(pop());
} 此外如果目标集合的元素类型与堆栈的完全匹配这段代码编译时还是会正确无误并且运行良好 。 但是也并不意味着尽如人意 。 假设你有一个 StackNumber 和 Object 类型的变量 。 如果从堆校中弹出 一个元素并将它保存在该变量中它的编译和运行都不会出错那你为何不能也这么做呢
StackNumber numberStack new StackNumber() ;
CollectionObject objects ...;
numberStack.popAll(objects) ; 如果试着用上述 的 popAll 版本编译这段客户端代码就会得到一个非常类似于第一次用 pushAll 时所得到的错误CollectionObject 不是 CollectionNumber的子类型 。 这一次通配符类型同样提供了一种解决办法 。popAll 的输入参数类型不应该为“ E 的集合”而应该为“ E 的某种超类的集合”这里的超类是确定的因此 E 是它自身的一个超类型。 仍有一个通配符类型正符合此意Collection? super E 。 让我们修改 popAll 来使用它
public void popAll(Collection? super E dst) {while (!isEmpty())dst.add(pop();
}做了 这个变动之后Stack 和客户端代码就都可以正确无误地编译了 。 结论很明显为了获得最大限度的灵活性要在表示生产者或者消费者的输入参数上使用通配符类型 。 如果某个输入参数既是生产者又是消费者那么通配符类型对你就没有什么好处了因为你需要的是严格的类型匹配这是不用任何通配符而得到的 。 下面的助记符便于让你记住要使用哪种通配符类型 PECS 表示 producer-extendsconsumersuper 。 换句话说如果参数化类型表示一个生产者 T 就使用 extends T 如果它表示一个消 费者 T 就使用 super T 。 在我们的 Stack 示例中pushAll 的 src 参数产生 E 实 例供 Stack 使用 因 此 src 相 应的类型为 Iterable? extends E ; popAll的 dst 参数通过 Stack 消费 E 实例因此 dst 相应的类型为 Collection? s uper E 。PECS 这个助记符突 出了使用通配符类型的基本原则 。Naftalin 和 Wadler 称之为 Get αnd Put Principle。 如果使用得当通配符类型对于类的用户来说几乎是无形的 。 它们使方法能够接受它们应该接受的参数并拒绝那些应该拒绝的参数 。 如果类的 用 户必须考虑通配符类型类的API 或许就会出错 。 一般来说 如果类型参数只在方法声明中出现一次就可以用通配符取代它 。 如果是无限制的类型参数就用无限制的通配符取代它如果是有限制的类型参数就用有限制的通配符取代它。 总而言之在 API 中使用通配符类型虽然比较需要技巧但是会使 API 变得灵活得多 。 如果编写 的是将被广泛使用的类库 则一定要适当地利用通配符类型 。 记住基本的原则producer-extends,consumer-super(PECS 。 还要记住所有的 comparable 和comparator 都是消费者 。