Item 28 利用有限制通配符来提升API的灵活性

<? extends E> 和<? super E>的使用

 

<? extends E>的使用

Item 26

这是堆栈的公有API:

public class Stack<E>{

public Stack();

public void push(E e);

public E pop();

public boolean isEmpty();

public void pushAll(Iterable<E> src);

}

在使用方法pushAll时,添加src,其中的数据类型要跟当前的Stack完全相同,这样才可以使用。比如,Stack<Number>,那么src的类型就只能是Iterable<Number>,不能是Iterable<Integer>。因为,Iterable<Number>与Iterable<Integer>并不是同一个类型。

那么,如果要实现一种效果,可以添加的类型是Stack中元素类型的子类型,而不一定使用完全相同的类型。

可以通过有限制的通配符来实现:

public class Stack<E>{

public Stack();

public void push(E e);

public E pop();

public boolean isEmpty();

public void pushAll(Iterable<? extends E> src);

}

------------------------------------------------------------------------

<? super E>的使用

 

public class Stack<E>{

public Stack();

public void push(E e);

public E pop();

public boolean isEmpty();

public void pushAll(Iterable<? extends E> src);

public void popAll(Collection<E> dst);

}

要实现一个功能,将堆栈中的元素弹出来,保存到一个容器中。也就是实现popAll这个API。使用这个API是有要求的,那就是容器的类型要完全跟当前的Stack的类型一致。那么,假设,我们想堆栈中的元素可以存放在容器Collection<Object>中,那么,该如何重新定义popAll方法?

使用super关键字。

public class Stack<E>{

public Stack();

public void push(E e);

public E pop();

public boolean isEmpty();

public void pushAll(Iterable<? extends E> src);

public void popAll(Collection<? super E> dst);

}

修改后的popAll方法,可以保存堆栈弹出的元素的容器类型是这样的,容器类型是Stack元素类型的父类。

好处:使用了上述的通配符,会提高API的灵活性,让它可以接受更多的类型。

pushAll 是数据的生产者;对生产者的进参数使用 <? extends E>,可以接受更多的类型,而不是只是E这种类型,可以接受E及其子类的类型。

popAll是数据的消费者;对消费者出参数使用<? super E>,可以让堆栈的数据保存在多种类型的容器中,而不只是保存在Collection<E>。它可以保存在类型是E的父类的容器中。

-----------------------------------------------------------------------------------------------------------------------------------------------

类型推导失效的情况:

 

调用方法Set<Number> numbers = Union.union(integers, doubles);Java编译器在进行类型推导时,不能确定类型参数究竟是使用哪个(Integer,Double),在这种情况下要显示指定类型参数。Set<Number> numbers = Union.<Number> union(integers, doubles);

public class Union {

public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2) {

Set<E> result = new HashSet<E>(s1);

result.addAll(s2);

return result;

}

// Simple program to exercise flexible generic method

public static void main(String[] args) {

Set<Integer> integers = new HashSet<Integer>();

integers.add(1);

integers.add(3);

integers.add(5);

Set<Double> doubles = new HashSet<Double>();

doubles.add(2.0);

doubles.add(4.0);

doubles.add(6.0);

// Won‘t compile; see page 137

// Set<Number> numbers = union(integers, doubles);

// Explicit type parameter is necessary here

Set<Number> numbers = Union.<Number> union(integers, doubles);

System. out.println(numbers);

}

}

------------------------------------------------------------------------------

使用有限制的通配符,让比较操作不只是限定在一种具体类型上:

版本一:

class Parent implements Comparable<Parent> {

@Override

public int compareTo(Parent o) {

// TODO Auto-generated method stub

//实现比较留空

return 0;

}

}

class ChildOne extends Parent {

}

class ChildTwo extends Parent {

}

public class RecursiveTypeBound {

// Returns the maximum value in a list - uses recursive type bound

public static <T extends Comparable<T>> T max(List<T> list) {

Iterator<T> i = list.iterator();

T result = i.next();

while (i.hasNext()) {

T t = i.next();

if (t.compareTo(result) > 0)

result = t;

}

return result;

}

public static void main(String[] args) {

List<String> argList = Arrays. asList(args);

System.out.println( max(argList));

List<ChildOne> oneList = new ArrayList<ChildOne>();

//    max(oneList);

//    Bound mismatch: The generic method max(List<T>) of type RecursiveTypeBound is

//    not applicable for

//    the arguments (List<ChildOne>). The inferred type ChildOne is not

//    a valid substitute for the bounded parameter <T extends Comparable<T>>

}

}

分析:<T extends Comparable<T>> ,该类型参数有如下限定:针对可以与自身进行比较的每个类型T。

但是,使用ChildOne类型的容器oneList,作为参数调用max方法,执行是失败的。

失败的原因是,ChildOne实例可以与其它继承了Parent的类的实例进行比较,比如,ChildTwo实例。这样就违反了约束条件:只能与自身进行比较(Comparable<T>)。

版本二:使用有限制的通配符来解决版本一的问题

class Parent implements Comparable<Parent> {

@Override

public int compareTo(Parent o) {

// TODO Auto-generated method stub

return 0;

}

}

class ChildOne extends Parent {

}

class ChildTwo extends Parent {

}

public class RecursiveTypeBound {

public static <T extends Comparable<? super T>> T max(List<? extends T> list) {

Iterator<? extends T> i = list.iterator();

T result = i.next();

while (i.hasNext()) {

T t = i.next();

if (t.compareTo(result) > 0)

result = t;

}

return result;

}

public static void main(String[] args) {

List<String> argList = Arrays. asList(args);

System.out.println( max(argList));

List<ChildOne> oneList = new ArrayList<ChildOne>();

max(oneList);

}

}

分析:新定义的泛型方法,首先,它使用的类型参数是:可以与自身进行比较的类型;可以与与该类型本身是类型兼容的类型相比较的类型。这是将Comparable<T>转换为Comparable<? super T>。

比如,ChildOne可以与ChildOne进行比较;ChildOne可以与ChildTwo进行比较。

从版本一转换为版本二,List<T>转换为List<? extends T>,转换的规则是,按照生产者消费者规则,List是生产者,使用<? extends T>。含义变化为,max不再接受单一一种类型,它可以接受某一类的类型。

比如:

List<Parent> oneList = new ArrayList<Parent>();

oneList.add(new ChildOne());

oneList.add(new ChildTwo());

oneList.add(new Parent());

max(oneList);

由接受一种具体的类型,变为接受一个种类的类型。

Comparable<T>是消费者,它是使用参数List的数据来进行操作的。转换它的时候,使用Comparable<? super T>。

----------------------------------------------------------------------------------------------------------------------------------

类型参数和通配符

List<E>和List<?>

很多方法可以同时使用这两种方式来实现。

比如,交换列表中的元素:

版本1:

public class SwapE {

public static <E> void swap(List<E> list, int i, int j) {

list.set(i, list.set(j, list.get(i)));

}

public static void main(String[] args) {

// Swap the first and last argument and print the resulting list

List<String> argList = Arrays. asList(args);

swap(argList, 0, argList.size() - 1);

System. out.println(argList);

}

}

版本2:

public class Swap {

public static void swap(List<?> list, int i, int j) {

swapHelper(list, i, j);

//list.set(i, list.set(j, list.get(i)));,因为只能往List<?>放null。

}

// Private helper method for wildcard capture

private static <E> void swapHelper(List<E> list, int i, int j) {

list.set(i, list.set(j, list.get(i)));

}

public static void main(String[] args) {

// Swap the first and last argument and print the resulting list

List<String> argList = Arrays. asList(args);

swap(argList, 0, argList.size() - 1);

System. out .println(argList);

}

}

时间: 2024-09-30 18:10:07

Item 28 利用有限制通配符来提升API的灵活性的相关文章

第28条:利用有限制通配符来提升API的灵活性

参数化类型是不可变的.对两个不同类型T1和T2而言,List<T1>与List<T2>没有父子类型关系. 考虑: public class Stack<E> { public Stack(); public void push(E e); public E pop(); public boolean isEmpty(); } 假设增加一个方法,按顺序将一系列的元素放到堆栈中: public void pushAll(Iterable<E> src) { fo

Effective Java 第三版——31.使用限定通配符来增加API的灵活性

Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深刻的变化. 在这里第一时间翻译成中文版.供大家学习分享之用. 31. 使用限定通配符来增加API的灵活性 如条目 28所述,参数化类型是不变的.换句话说,对于任何两个不同类型的Type1和Type,List <Type1>

利用 Django REST framework 编写 RESTful API

利用 Django REST framework 编写 RESTful API Updateat 2015/12/3: 增加 filter 最近在玩 Django,不得不说 rest_framework 真乃一大神器,可以轻易的甚至自动化的搞定很多事情,比如: 自动生成符合 RESTful 规范的 API 支持 OPTION.HEAD.POST.GET.PATCH.PUT.DELETE 根据 Content-Type 来动态的返回数据类型(如 text.json) 生成 browserable

利用高德地图官方提供的API实现模拟定位的功能

使用高德地图API需要申请一个开发者ID号以及我所用的相关资源:  http://pan.baidu.com/disk/home?fr=ibaidu 该文件夹中有一个官方文档,AMap_IOS_API_Guid,提供了使用方法供开发者参阅. 利用高德地图官方提供的API实现模拟定位的功能,布布扣,bubuko.com

利用Swagger Maven Plugin生成Rest API文档

利用Swagger Maven Plugin生成Rest API文档 Swagger Maven Plugin This plugin enables your Swagger-annotated project to generate Swagger specs and customizable, templated static documents during the maven build phase. Unlike swagger-core, swagger-maven-plugin

利用 AWK 的数值计算功能提升工作效率(转载)

Awk 是一种优秀的文本样式扫描和处理工具.转文侧重介绍了 awk 在数值计算方面的运用,并通过几个实际工作中的例子,阐述了如何利用 awk 的计算功能来提高我们的工作效率.转文源自IBM Bluemix,链接:http://www.ibm.com/developerworks/cn/linux/l-cn-awkinwork/ Awk 是一种优秀的文本样式扫描和处理工具. Awk 与 sed 和 grep 有些相似, 但功能比后者强不少. awk 提供的功能包括样式载入, 流控制,数学运算符,进

使用.NET Core搭建分布式音频效果处理服务(五)利用消息队列提升水平扩展灵活性

消息队列 神马是消息队列,看看某度的原话“在项目中,将一些无需即时返回且耗时的操作提取出来,进行了异步处理,而这种异步处理的方式大大的节省了服务器的请求响应时间,从而提高了系统的吞吐量”. 其实消息队列还可以用于解耦,在多层项目模型或中型项目以上,都会用到消息队列,减少层与层之间的耦合:还可以做跨进程间的通讯(传输率显然比不上RPC). 上一节说道最终需要采用消息队列来进行分离前级和后级,并且采用异步方式,用于提高业务服务器的吞吐率,不过,虽然分离了,如果后级服务器的处理能力达不到请求数或接近平

Effective C++ Item 28 避免返回对象内部数据的引用或指针

本文为senlie原创.转载请保留此地址:http://blog.csdn.net/zhengsenlie Item 31 经验:避免返回handles(包含 references.指针.迭代器)指向对象内部.遵守这个条款可添加封装性, 帮助 const 成员函数的行为像个 const,并将发生"虚吊号码牌"(dangling handles)的可能性降至最低. 演示样例: class Point{ public: Point(int x, int y); //... void set

Effective JavaScript Item 28 不要依赖函数的toString方法

本系列作为Effective JavaScript的读书笔记. 在JavaScript中,函数对象上存在一个toString方法,它能够方便地将函数的源代码转换返回成一个字符串对象. (function(x) { return x + 1; }).toString(); // "function (x) {\n return x + 1;\n}" toString方法不仅仅会让一些黑客找到攻击的方法,而且该方法也存在严重的限制. 首先,toString方法的实现方式并没有被ECMASc