Java 泛型中的通配符详解

这篇具有很好参考价值的文章主要介绍了Java 泛型中的通配符详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

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> 之间的关系是什么呢?

Java 泛型中的通配符详解

        虽然 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 类之间的关系。// 使用通配符可以定义两个类型之间的关系

Java 泛型中的通配符详解

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#1
  found: int,Object
  reason: actual argument Object cannot be converted to CAP#1 by method invocation conversion
  where E is a type-variable:
    E extends Object declared in interface List
  where 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> 并不是只读的,但是却可以认为它是只读的,因为不能在列表中存储新的元素或更改现有元素文章来源地址https://www.toymoban.com/news/detail-495326.html

到了这里,关于Java 泛型中的通配符详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 了解java中的通配符“?“

    目录 通配符的作用         先看一段代码         用通配符\\\"?\\\"后,代码变化         结论 通配符上界  通配符下界 对通配符上下界的注释理解及其练习代码   简记: ?  用于在泛型的使用,即为通配符. 在Java中,通配符(wildcard)主要用于泛型编程,用于表示一个

    2024年02月10日
    浏览(55)
  • Linux详解:通配符

    Linux是一款开源操作系统,其灵活性和可定制性一直受到开发者的喜爱和追捧。而且,Linux在文件管理方面提供了丰富的功能,例如通配符,它是一种用于匹配文件名的特殊字符。通配符在Linux中可以帮助我们更加方便和快捷地查找和操作文件。本文将介绍Linux中常用的通配符

    2024年02月09日
    浏览(61)
  • office办公技能|word中的常见通配符使用

    操作方法: 1、快捷键 Ctrl+H,打开Word的查找替换窗口,单击【更多】按钮,勾选“使用通配符”。 2、在查找内容处,输入“替换内容*^13”,替换为处什么都不填。 3、单击【全部替换】。 (1)点击【开始】-【编辑】-【替换】按钮或按【Ctrl+H】组合键,打开“查找与替换”

    2024年01月20日
    浏览(62)
  • SHELL 基础 入门(三) Bash 快捷键 命令执行顺序,详解通配符

    目录 Bash 常用快捷键  输入输出重定向  用法  输出重定向  命令执行顺序  ;  分号 ||  通配符  传统通配符  ? \\\'  *  \\\'  [  ]   [ - ]   [ ^ ] 常用字符  强调 : {  }   生成序列  Ctrl + A    把光标移动到命令行开头 Ctrl + E    把光标移动到命令行尾  Ctrl + D    退出当前

    2024年02月11日
    浏览(59)
  • 数据结构(Java实现)-字符串常量池与通配符

    字符串常量池 在Java程序中,类似于:1, 2, 3,3.14,“hello”等字面类型的常量经常频繁使用,为了使程序的运行速度更快、更节省内存,Java为8种基本数据类型和String类都提供了常量池。 “池” 是编程中的一种常见的, 重要的提升效率的方式, 我们会在未来的学习中遇到各

    2024年02月10日
    浏览(50)
  • Elasticsearch 通配符查询

    通配符查询(wildcard query) 匹配字段被通配符表达式(没有被分析)匹配的文档。支持的通配符为*(匹配任意字符序列,包括空字符序列)以及?(匹配任意单字符)。注意,此查询可能会很慢,它需要迭代许多字段值。为了防止极慢的通配符匹配,通配符字段值不能以一个

    2024年02月11日
    浏览(87)
  • 【类型通配符】

    为了表示各种泛型List的父类,可以使用类型通配符 类型通配符:? List?:表示元素类型未知的List,它的元素可以匹配任何的类型 这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素添加到其中 如果不想让List?是任何泛型的父类,只想让它代表某一类泛型List的父

    2024年02月17日
    浏览(49)
  • 活用 命令行通配符

    本文是对 阮一峰老师 命令行通配符教程 [1] 的学习与记录 通配符早于正则表达式出现,可以看作是原始的正则表达式. 其功能没有正则那么强大灵活,而胜在简单和方便. - 字符 切回上一个路径/分支 如图: !! 代表上一个命令, 如图: [Linux中“!\\\"的神奇用法](https://www.cnblogs.com/bian

    2024年02月10日
    浏览(58)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包