Java泛型支持通配符(Wildcard),可以单独使用一个"?"表示任意类,也可以使用extends关键字标识某一类(接口)的子类型,还可以使用super关键字标识某一类(接口)的父类型,但问题是什么时候该用extends,什么时候该用super?

(1)泛型结构只参与"读"操作则限定上界(extends 关键字)

于都如下代码,看业务逻辑操作是否还能继续?

 import java.util.Arrays;
import java.util.List; public class Client {
public static void main(String[] args) {
read(Arrays.asList("A")); } public static <E> void read(List<? super E> list){ for(Object obj:list){ //业务逻辑操作
}
}
}

从List列表中读取元素操作(比如一个数字列表中的求和计算),你觉得方法read能继续写下去吗?

答案是不能,我们不知道list到底存放的是什么元素,只能推断出是E类型的父类,当然也可以是E类型,单问题是E类型的父类是什么呢?

无法再推断,只有运行时才知道,那么编码期就完全无法操作了,当然,你可以把它当作是Object类型来处理,需要时再转换成E类型,但是这样完全违背了泛型的初衷.

在这种情况下,"读"操作如果期望从List集合中读取数据就需要使用extends关键字了,也就是要界定泛型的上界,代码如下:

 import java.util.Arrays;
import java.util.List; public class Client {
public static void main(String[] args) {
read(Arrays.asList("A"));
} public static <E> void read(List<? extends E> list){
for(E e:list){
System.out.println(e.getClass());
//业务逻辑处理
}
}
}

此时已经推断出List集合中取出的是E类型的元素,具体是什么类型的元素就要等到运行时才能确定了,但是它一定是一个确定的类型,比如read(Arrays.asList("A"))调用该方法时,可以推断出List中的元素类型是String,之后就可以对List中的元素进行操作了,如加入到另外的List<E>集合中,或者作为Map<E,V>的键等.

(2)泛型结构只参与"写"操作则限定下界(使用super关键字)

先看如下代码是否可以正常编译:

 import java.util.List;

 public class Client {
public static void main(String[] args) { } public static void write(List<? extends Number> list){
list.add(null);
list.add(123);
//The method add(int, capture#2-of ? extends Number)
//in the type List<capture#2-of ? extends Number> is not applicable for the arguments (int)
}
}

编译失败,失败的原因是list中的元素类型不确定,也就是编译期无法推断出泛型类型到底是什么,是Integer类型?是Double?还是Byte?这些都符合extends关键字的定义,由于无法确定实际的泛型类型,所以编译器聚聚了此类操作.

在此种情况下只有一个元素是可以add进去的:null值,这是因为null是一个万用类型,它可以是所有类的实例对象,所以可以加入到任何列表中.

Object是否也可以?不可以,因为它不是Number的子类,而且即使把list变量修改为List<? extends Object>类型也不能加入,就是因为编译器无法推断出泛型类型,加什么元素都是无效的.

在这种"写"操作的情况下,使用super关键字限定泛型类型的下界才可以.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; public class Client {
public static void main(String[] args) { } public static void write(List<? super Number> list) {
list.add(123);
list.add(3.14);
} }

不用管是Integer类型还是Float类型,都可以加入到list列表中,因为它们都是Number累心g,zhejiu保证了泛型类的可靠性.

对于要限定上界还是下界,JDK的Collecctions.copy方法是一个非常好的例子,它实现了把源列表中的所有元素拷贝到目标列表中对应的索引位置上.代码如下:

     public static <T> void copy(List<? super T> dest, List<? extends T> src) {
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest"); if (srcSize < COPY_THRESHOLD ||
(src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i<srcSize; i++)
dest.set(i, src.get(i));
} else {
ListIterator<? super T> di=dest.listIterator();
ListIterator<? extends T> si=src.listIterator();
for (int i=0; i<srcSize; i++) {
di.next();
di.set(si.next());
}
}
}

源列表是用来提供数据的,所以src变量需要限定上界,带有extends关键字,目标列表是用来写入数据的,所以dest变量需要界定上界,带有super关键字.

如果一个泛型结构即用作"读"操作又用作"写"操作,那该如何进行限定呢?

不限定,使用确定的泛型类型即可,如List<E>.

[改善Java代码]不同的场景使用不同的泛型通配符的更多相关文章

  1. [改善Java代码]避开基本类型数组转换列表陷阱

    开发中经常用到Arrays和Collections这两个工具类. 在数组和列表之间进行切换.非常方便.但是也会遇到一些问题. 看代码: import java.util.Arrays; import ...

  2. [改善Java代码]警惕泛型是不能协变和逆变的

    什么叫做协变(covariance)和逆变(contravariance)? 在变成语言的类型框架中,协变和逆变是指宽类型和窄类型在某种情况下(如参数,泛型,返回值)替换或交换的特性,简单的说,协变是 ...

  3. [改善Java代码]易变业务使用脚本语言编写

    建议16: 易变业务使用脚本语言编写 Java世界一直在遭受着异种语言的入侵,比如PHP.Ruby.Groovy.JavaScript等,这些“入侵者”都有一个共同特征:全是同一类语言—脚本语言,它们 ...

  4. [改善Java代码]在明确的场景下,为集合指定初始容量

    我们经常使用ArrayList,Vector,Hashmap等集合,一般都是直接用new跟上类名声明出一个集合来,然后使用add,remove,等方法进行操作,而且因为它们是自动管理长度的,所以不用我 ...

  5. [改善Java代码]推荐在复杂字符串操作中使用正则表达式

    一.分析  字符串的操作,诸如追加.合并.替换.倒序.分隔等,都是在编码过程中经常用到的,而且Java也提供了append.replace.reverse.split等方法来完成这些操作,它们使用起来 ...

  6. [改善Java代码]非稳定排序推荐使用List

    我们知道Set与List的最大区别就是Set中的元素不可以重复(这个重复指的equals方法的返回值相等),其他方面则没有太大的区别了,在Set的实现类中有一个比较常用的类需要了解一下:TreeSet ...

  7. [改善Java代码]不同的列表选择不同的遍历方法

    一.场景: 我们来看一个场景,统计一个省的各科高考科目考试的平均分. 当然使用数据库中的一个SQL语句就能求出平均值,不过这个不再我们的考虑之列,这里只考虑使用纯Java的方式来解决.(由于我的机器配 ...

  8. [改善Java代码]枚举和注解结合使用威力更大

    注解的写法和接口很类似,都采用了关键字interface,而且都不能有实现代码,常量定义默认都是pulbic static final类型的. 他们的主要不同点是:注解在interface前加上@字符 ...

  9. [改善Java代码]优先选择线程池

    在Java1.5之前,实现多线程编程比较麻烦,需要自己启动线程,并关注同步资源,防止线程死锁等问题,在1.5版本之后引入了并行计算框架,大大简化了多线程开发. 我们知道线程有5个状态:新建状态(New ...

随机推荐

  1. hive UDTF函数

    之前说过HIVE,UDF(User-Defined-Function)函数的编写和使用,现在来看看UDTF的编写和使用. 1. UDTF介绍 UDTF(User-Defined Table-Gener ...

  2. 通用FASTREPORT打印模块及接口方法

    untFastReport.dfm文件: object frmFastReport: TfrmFastReport OldCreateOrder = False Height = 405 Width ...

  3. TypeScript学习笔记(三):类

    类 在TypeScript中,类似于C#的结构,即一个文件中可以存在多个类,且文件名可以任意取,我们先看一个简单的类的示例. class Person { private name: string; ...

  4. SQL Delta实用案例介绍 (对应软件)

    http://blog.csdn.net/hongdi/article/details/5363209

  5. nm命令详解

    nm在linux中列出目标文件的符号清单,常用来查看动态链接库中的函数 nm支持的选项如下 -a   按照man手册,仅列出调试信息,实际上却是调试信息+正常信息 -A   增加一列显示目标文件,没有 ...

  6. Myeclipse 10.x 安装Aptana3.2 插件

    安装步骤: 1.下载aptana3.2 Eclipse Plugin插件. 下载地址:http://update1.aptana.org/studio/3.2/024747/index.html 2. ...

  7. Javascript继承实现

    S1:js中一切皆对象,想想如果要实现对父对象属性和方法的继承,最初我们会怎样子来实现呢,考虑到原型的概念,最初我是这样来实现继承的 function Parent(){ this.name='123 ...

  8. Spring Hibernate4 整合配置文档

    1 applicationContext.xml配置文档 <?xml version="1.0" encoding="UTF-8"?><bea ...

  9. Volley使用指南第四回(来自developer.android)

    Volley网络请求的第四篇,废话不多说,开始. 这一篇文章将会教你怎样在Volley支持的范围内定制一个请求. 第一步:写一个通用请求: 大多数请求都有已经写好的接口供你调用,如果你的请求是Stri ...

  10. 【不积跬步,无以致千里】vim复制

    用vim这么久 了,始终也不知道怎么在vim中使用系统粘贴板,通常要在网上复制一段代码都是先gedit打开文件,中键粘贴后关闭,然后再用vim打开编辑,真的不 爽:上次论坛上有人问到了怎么在vim中使 ...