<? 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);
       }
}
 
 

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

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

    参数化类型是不可变的.对两个不同类型T1和T2而言,List<T1>与List<T2>没有父子类型关系. 考虑: public class Stack<E> { p ...

  2. 第二十八条:利用有限制通配符来提升API的灵活性

    如第二十五条所述,参数化类型是不可变的.类型Type1和Type2而言,不管Type1与Type2的关系,List<Type1>既不是List<Type2>的子类型,也不是也不 ...

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

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  4. EffectiveJava(28)怎么利用有限制的通配符类型来提升API的灵活性

    有时候,我们需要的灵活性要比不可变类型所能提供的更多.所以针对一些通用性的方法,通常用泛型代替固定的数据类型,特别是当你要将某个方法打包成Jar的时候. 结合之前的例子,我们增加尝试用有限制的通配符类 ...

  5. effective-java学习笔记---使用限定通配符来增加 API 的灵活性31

    在你的 API 中使用通配符类型,虽然棘手,但使得 API 更加灵活. 如果编写一个将被广泛使用的类库,正确使用通配符类型应该被认为是强制性的. 记住基本规则: producer-extends, c ...

  6. 利用 Django REST framework 编写 RESTful API

    利用 Django REST framework 编写 RESTful API Updateat 2015/12/3: 增加 filter 最近在玩 Django,不得不说 rest_framewor ...

  7. Atitit.提升api兼容性的方法 v3 q326

    Atitit.提升api兼容性的方法 v3 q326 1. Atitit.兼容性的“一加三”策略1 2. 2. 扩展表模式1 3. 3. 同时运行模式1 3.1. 3.1. 完美的后向兼容性2 3.2 ...

  8. 利用Vert.x构建简单的API 服务、分布式服务

    目前已经使用Vertx已经一年多了,虽然没有太多的造诣,但也已在项目中推广了下:从最初的vertx搭建web服务,到项目上线运营,还算比较稳定.再到后来尝试搭建基于vertx的分布式服务,一路下来也积 ...

  9. Effective c++ Item 28 不要返回对象内部数据(internals)的句柄(handles)

    假设你正在操作一个Rectangle类.每个矩形可以通过左上角的点和右下角的点来表示.为了保证一个Rectangle对象尽可能小,你可能决定不把定义矩形范围的点存储在Rectangle类中,而是把它放 ...

随机推荐

  1. 全排列 next_permutation() 函数的用法

    在头文件<algorithm>里面有如下代码: int a[]; do { } while(next_permutation(a,a+n)); 可产生1~n的全排列有如下代码: #incl ...

  2. JDK源码分析 – LinkedList

    LinkedList类的申明 public class LinkedList<E> extends AbstractSequentialList<E> implements L ...

  3. 解决chrome css本地映射不成功&&附带映射方法

    解决办法:把本地文件夹名改成英文的(不要有中文) 顺便写一下怎么把在chrome调试的本地项目中的css映射到本地: 1.F12(option+command+i)启动chrome调试工具 2.打开s ...

  4. ifstat查看网络流量的原理

    ifstat查看网卡流量的原理:读的是哪个/proc/ 接口啊 同diskIO一样,网络的IO也同样有统计计数的,是/proc/net/dev一个典型的输出就是这个样子的: root@station6 ...

  5. poj3074-Sodoku

    解数独. 分析 考虑如何把数独解合法的条件转化为经典的01精确覆盖: 每个格子只能填一个数,1-9 每一列刚好填了1-9 每一行刚好填了1-9 每个九宫格刚好填了1-9 也就是说,每个格子,列,行,九 ...

  6. bzoj 4568 [SCOI 2016] 幸运数字

    题目大意 给定一棵\(n\)个点的树,每个点有权值 \(q\)次询问树上路径中 每个点权值可选可不选的最大异或和 \(n\le 2*10^4,q\le 2*10^5,val[i]\le 2^{60}\ ...

  7. 《转》vue-cli的webpack模板项目配置文件注释

    一.文件结构 本文主要分析开发(dev)和构建(build)两个过程涉及到的文件,故下面文件结构仅列出相应的内容. ├─build │ ├─build.js │ ├─check-versions.js ...

  8. Devc++编译系统分配给int多少字节

    我看的是<C语言程序设计>..谭浩强的PDF版 里面只讲了VC和TC 的,没有Devc++的..(我的是5.10版) 还有这是什么意思? 经过查阅我进行了这样的测试: 得到了这样的结果: ...

  9. NetScaler ‘Counters’ Grab-Bag!

    NetScaler ‘Counters’ Grab-Bag! https://www.citrix.com/blogs/author/andrewre/ https://www.citrix.com/ ...

  10. NetScaler + Wireshark = A Perfect Combination!

    NetScaler + Wireshark = A Perfect Combination! https://www.citrix.com/blogs/2014/05/03/netscaler-wir ...