目录

1、如何定义和使用上界通配符?

2、如何定义和使用无界通配符?

3、如何定义和使用下界通配符?

4、如何使用通配符定义泛型类或接口之间的子类型关系?

5、通配符的捕获和辅助方法

6、通配符使用指南


在泛型代码中,问号(?)称为通配符,用来表示未知类型。通配符可以在多种情况下使用:如作为参数、字段或局部变量的类型;有时也可以作为返回类型。另外,通配符永远不会用作调用泛型方法、创建泛型类或超类型实例的类型参数。

为什么使用通配符?

在 Java 中,类和数组之间的对象关系是可以继承的,比如 Dog extends Animal,那么 Animal[] 与 Dog[] 就是兼容的。但是集合之间却不存在这种关系,也就是说 List<Animal> 不是List<Dog> 的父类,他们之间没有任何关系。那么为了建立两个集合之间的联系,就需要用到通配符。// 只是其中一部分原因

1、如何定义和使用上界通配符?

可以使用上界通配符来放宽对变量的限制。例如,你想编写一个方法,该方法适用于List<Integer>, List<Double> 和 List<Number>;就可以通过使用上界通配符来实现这一点。

声明一个上界通配符,需要使用通配符 ('?'),跟上 extends 关键字,然后再跟它的上界。比如,编写用于 Number 列表和 Number 子类型(如 Integer、Double 和 Float)的方法,可以指定 List<? extends Number>。List<? extends Number> 要比 List<Number> 对类型的限制更加宽松,因为 List<Number> 只匹配类型为 Number 的列表,而 List<? extends Number> 匹配类型为 Number 或其任何子类的列表。

例如下边的程序代码:

public static void process(List<? extends Foo> list) { /* ... */ }

上界通配符 <? extends Foo> 中的 Foo 匹配 Foo 类型和 Foo 的任何子类型。process() 方法可以访问类型为 Foo 的列表元素:

public static void process(List<? extends Foo> list) {for (Foo elem : list) {// ...}
}

在 foreach 子句中,elem 变量迭代列表中的每个元素。在 elem 元素上可以使用 Foo 类中定义的任何方法。

如下,sumOfList() 方法返回列表中数字的和:

public static double sumOfList(List<? extends Number> list) {double s = 0.0;for (Number n : list)s += n.doubleValue();return s;
}

下面的代码,使用一个 Integer 对象列表,输出 sum = 6.0:

List<Integer> li = Arrays.asList(1, 2, 3);
System.out.println("sum = " + sumOfList(li));

Double 值列表可以使用相同的 sumOfList() 方法。下面的代码输出 sum = 7.0:

List<Double> ld = Arrays.asList(1.2, 2.3, 3.5);
System.out.println("sum = " + sumOfList(ld));

2、如何定义和使用无界通配符?

无界通配符类型使用通配符(?)指定,例如 List<?>。在下边两种情况下,无界通配符是一种有用的方法:

  1. 编写一个方法,该方法可以使用 Object 类中提供的功能函数。
  2. 代码在泛型类中使用不依赖于类型参数的方法。例如,List.size 或 List.clear。事实上,Class<?> 之所以如此常用,是因为 Class<T> 中的大多数方法都不依赖于 T。

例如以下 printList() 方法:

public static void printList(List<Object> list) {for (Object elem : list)System.out.println(elem + " ");System.out.println();
}

printList() 的目标是用来打印任何类型的列表,但上述代码中它只能打印 Object 实例的列表;并不能打印 List<Integer>, List<String>, List<Double> 等,因为这些列表不是 List<Object> 的子类型。所以如果要编写一个通用的 printList() 方法,需要使用到 List<?>:

public static void printList(List<?> list) {for (Object elem: list)System.out.print(elem + " ");System.out.println();
}

对于任何具体的类型 A,List<A> 都是 List<?> 的子类,可以使用 printList() 打印任何类型的列表:// 解决了之前 List<A> 和 List<B> 无任何关系的问题

List<Integer> li = Arrays.asList(1, 2, 3);
List<String>  ls = Arrays.asList("one", "two", "three");
printList(li);
printList(ls);

注意:List<Object> 和 List<?> 是不一样的。可以将 Object 或 Object 的任何子类型插入到 List<Object> 中,但是只能将 null 插入到 List<?> 中。

3、如何定义和使用下界通配符?

上界通配符将未知类型限制为特定类型的子类型,使用 extends 关键字表示。类似地,下界通配符将未知类型限制为特定类型的超类型,使用 super 关键字表示。

下界通配符使用通配符('?')表示,后面跟着 super 关键字,然后再跟着它的下界:<? super A>。

注意:可以为通配符指定上界,也可以指定下界,但不能同时都指定。

例如,编写一个将 Integer 对象放入列表的方法。为了最大限度地提高灵活性,该方法需要适用于 List<Integer>、List<Number> 和 List<Object> 等任何可以保存 Integer 值的对象。那么要编写处理 Integer 列表和 Integer 超类型列表的方法,就可以使用 List<? super Integer> 来指定。如以下代码将数字 1 到 10 添加到列表的末尾:

public static void addNumbers(List<? super Integer> list) {for (int i = 1; i <= 10; i++) {list.add(i);}
}

4、如何使用通配符定义泛型类或接口之间的子类型关系?

泛型类或接口之间的关联并不取决于它们的类型之间是否存在关联。不过,我们可以使用通配符来创建泛型类或接口之间的关联关系。

给定以下两个非泛型类:

class A { /* ... */ }
class B extends A { /* ... */ }

编写以下代码是合理的:

B b = new B();
A a = b;

上边这个例子表明常规类的继承遵循子类型的规则:如果 B 继承 A,那么 B.calss 就是 A.calss 的子类型。但是这个规则并不适用于泛型类型:

List<B> lb = new ArrayList<>();
List<A> la = lb;   // compile-time error

假设 Integer 是 Number 的子类型,那么 List<Integer> 和 List<Number> 之间的关系是什么呢?

虽然 Integer 是 Number 的子类型,但 List<Integer> 并不是 List<Number> 的子类型,所以,这两种类型并不相关。事实上,List<Number> 和 List<Integer> 的公共父类是 List<?>。

为了在这些类之间创建关系,让代码可以通过 List<Integer> 的元素访问 Number 的方法,可以使用一个上界通配符:

List<? extends Integer> intList = new ArrayList<>();
List<? extends Number>  numList = intList;  // OK. List<? extends Integer> is a subtype of List<? extends Number>

因为 Integer 是 Number 的子类型,而 numList 是 Number 对象的列表,所以 intList (Integer对象的列表)和 numList 之间存在关联关系。下图显示了用上下界通配符声明的几个 List 类之间的关系。// 使用通配符可以定义两个类型之间的关系

5、通配符的捕获和辅助方法

在某些情况下,编译器会自动推断通配符的类型。例如,列表可以定义为 List<?>,但是,当计算表达式时,编译器会从代码中推断出特定的类型,这种场景称为通配符捕获。

大多数情况下,都不需要担心通配符的捕获,除非看到包含短语 “capture of” 的错误消息。

如下,WildcardError 示例会在编译时会产生一个捕获错误:

import java.util.List;public class WildcardError {void foo(List<?> i) {i.set(0, i.get(0));}
}

在本例中,编译器将 i 输入形参处理为 Object 类型。当 foo 方法调用 List.set(int, E) 时,编译器无法确认插入到列表中的对象类型,所以会产生错误。当发生这种类型的错误时,通常意味着编译器认为给变量分配了错误的类型。将泛型添加到 Java 语言中就是出于这个原因——在编译时加强类型安全。

当使用 Oracle 的 JDK 7 javac 实现编译时,WildcardError 示例会生成以下错误:

WildcardError.java:6: error: method set in interface List<E> cannot be applied to given types;i.set(0, i.get(0));^required: int,CAP#1found: int,Objectreason: actual argument Object cannot be converted to CAP#1 by method invocation conversionwhere E is a type-variable:E extends Object declared in interface Listwhere CAP#1 is a fresh type-variable:CAP#1 extends Object from capture of ?
1 error

在本例中,代码试图执行安全操作,那么如何解决编译器错误呢?可以通过编写一个捕获通配符的私有 helper 方法来修复它。如 WildcardFixed 所示,通过创建私有辅助方法 fooHelper 来解决这个问题:

public class WildcardFixed {void foo(List<?> i) {fooHelper(i);}// 创建辅助方法,以便可以通过类型推断捕获通配符private <T> void fooHelper(List<T> l) {l.set(0, l.get(0));}
}

由于使用了 helper 方法,编译器使用推断来确定调用中的 T 是 CAP#1,即捕获变量。示例现在编译成功。

6、通配符使用指南

在使用泛型进行编程时,让人比较困惑的是需要确定何时使用上界通配符,何时使用下界通配符。本节提供了一些设计代码时要遵循的指导原则。// 以下是来自官方的指导原则

为了方便理解,可以将变量看作以下两种形式:

  • “in” 变量:“in” 变量为代码提供数据。比如一个复制方法有两个参数:copy(src, dest)。src 参数提供了需要复制的数据,因此它是 “in” 形参。
  • “out” 变量:“out” 变量用来保存输出数据,便于该数据在其他地方使用。在 copy(src, dest) 方法中, dest 参数用来接收数据,因此它是 "out" 形参。

当然,有些变量同时用于 “输入” 和 “输出” 目的,下边指南中也提到了这种情况。

在决定是否使用通配符以及使用什么类型的通配符时,可以使用 “in” 和 “out” 原则。以下列表提供了需要遵循的指导方针:

  1. 对于 “in” 变量,可以定义一个上界通配符,使用 extends 关键字。
  2. 对于“out”变量,可以定义一个下界通配符,使用 super 关键字。
  3. 如果 “in” 变量可以使用 Object.calss 中定义的方法对其进行访问,可以使用无界通配符。
  4. 如果变量同时用于 “输入” 和 “输出”的情况下,不要使用通配符。// 限定唯一的类型

以上这些准则不适用于方法的返回类型。应该避免使用通配符作为方法的返回类型,因为它会迫使使用代码的程序员去处理通配符。

List<? extends ...> 可以认为它是只读的,但并不能进行严格的保证,假设有以下两个类:

class NaturalNumber {private int i;public NaturalNumber(int i) { this.i = i; }// ...
}class EvenNumber extends NaturalNumber {public EvenNumber(int i) { super(i); }// ...
}

考虑下面的代码:

List<EvenNumber> le = new ArrayList<>();
List<? extends NaturalNumber> ln = le;
ln.add(new NaturalNumber(35));  // 编译错误

因为 List<EvenNumber> 是 List<? extends NaturalNumber> 的子类型,所以可以将 le 赋值给 ln。但是却不能用 ln 把一个自然数加到一个偶数列表中。在该列表中只可以进行以下操作:

  1. 添加 null 元素。
  2. 调用 clear() 方法。
  3. 获取迭代器并调用 remove() 方法。
  4. 捕获通配符并写入从列表中读取的元素。

所以,从严格意义上来讲,List<? extends NaturalNumber> 并不是只读的,但是却可以认为它是只读的,因为不能在列表中存储新的元素或更改现有元素。

Java 泛型中的通配符详解相关推荐

  1. Java泛型三:通配符详解extends super

    在java泛型中,? 表示通配符,代表未知类型,< ? extends Object>表示上边界限定通配符,< ? super Object>表示下边界限定通配符. 通配符 与 ...

  2. Java 泛型(generics)详解及代码示例、Java 类型通配符详解及代码示例

    Java 泛型(generics)详解及代码示例.Java 类型通配符详解及代码示例 - 概念 Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制 ...

  3. 一文读懂Java泛型中的通配符 ?

    之前不太明白泛型中通配符"?"的含义,直到我在网上发现了Jakob Jenkov的一篇文章,觉得很不错,所以翻译过来,大家也可以点击文末左下角的阅读原文看英文版的原文. 下面是我的 ...

  4. Java虚拟机中类加载机制详解

    Java虚拟机中类加载机制详解 1,什么是java类加载机制 **首先在java中,是通过编译来生成.class文件(可能在本地,或者网页下载),java的类加载机制就是 将这些.class文件加载到 ...

  5. 聊一聊Java 泛型中的通配符 T,E,K,V,?

    点击上方"方志朋",选择"设为星标" 回复"1024"获取独家整理的学习资料 作者:glmapper juejin.im/post/5d57 ...

  6. 聊一聊-JAVA 泛型中的通配符 T,E,K,V,?

    前言 Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许开发者在编译时检测到非法的类型. 泛型的本质是参数化类型,也就是说所操作的数据 ...

  7. JAVA 泛型中的通配符 T,E,K,V,?

    点击上方"朱小厮的博客",选择"设为星标" 后台回复"加群",加入新技术 来源:8rr.co/2Xqx 前言 Java 泛型(generic ...

  8. 原神一面:Java 泛型中的通配符 T,E,K,V,?,你确定都了解吗?

    点击上方 "编程技术圈"关注, 星标或置顶一起成长 后台回复"大礼包"有惊喜礼包! 每日英文 Sometimes, the same thing, we can ...

  9. Java泛型专题之2、聊一聊-JAVA 泛型中的通配符 T,E,K,V,?

    目录 1. 前言 Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许开发者在编译时检测到非法的类型. 泛型的本质是参数化类型,也就是说 ...

最新文章

  1. 美国互联网瘫痪了,你的密码怎么办?
  2. 从源码分析DEARGUI之文件选择
  3. 2.7 HBase架构深入剖析
  4. iOSCoreAnimation动画系列教程(一):CABasicAnimation【包会】
  5. Caffe抽取图像特征
  6. Django的model查询操作 与 查询性能优化
  7. 基于Docker搭建RabbitMQ(多图)
  8. 流媒体技术的应用与发展前景
  9. 4. linux调用文件计算阶乘前5项和_嵌入式Linux系统编程——文件读写访问、属性、描述符、API
  10. 计算机期末考试知识,干货|计算机期末复习宝典
  11. ocp认证考试报名_2019年OCP认证在线考试网_OCP题库
  12. 非线性动力学 nonlinear dynamics
  13. [Camera Drv]Factory mode下camera图像rotate了180度 - MTK物联网在线解答 - 技术论坛
  14. ora11g 安装报错ins_emagent.mk
  15. cad怎么将图层后置_Auto CAD2014图层后置快捷键是什么啊?
  16. 中国十大无线耳机排行榜,音质好配置高的蓝牙耳机分享
  17. 数据科学家必须知道的10个深度学习架构
  18. linux系统写一个计划任务并执行,Linux系统计划任务
  19. pycharm验证码
  20. 【Chrome必备插件,一键提升10倍效率】新用户永久免广告,好用!

热门文章

  1. MyBatis Plus之逻辑删除和分页插件使用
  2. 爱奇艺《中国新歌声》第二季破6亿 强平台助推综艺影响力全面爆发
  3. 多人种人脸识别(一)
  4. 键盘录入两个整数,求他们的最大公约数
  5. 国庆中秋已过,双十一店铺布局你做好了吗?
  6. PTA字符串压缩 (10 分)
  7. 解决 Public registration is not allowed 问题
  8. 计算机科学的实验报告,计算机实验报告范文
  9. 怎么把pdf压缩的小一点 这3种方式都很简单
  10. 高德地图自定义创建地图