Java泛型之通配符

原文点此链接

使用通配符的原因:Java中的数组是协变的,但是泛型不支持协变。

数组的协变

首先了解下什么是数组的协变,看下面的例子:

Number[] nums = new Integer[10]; // OK

因为Integer是Number的子类,一个Integer对象也是一个Number对象,所以一个Integer的数组也是一个Number的数组,这就是数组的协变。

Java把数组设计成协变的,在一定程度上是有缺陷的。因为尽管把Integer[]赋值给Number[],Integer[]可以向上转型为Number[],但是数据元素的实际类型是Integer,只能向数组中放入Integer或者Integer的子类。如果向数组中放入Number对象或者Number其他子类的对象,对于编译器来说也是可以通过编译的。但是运行时JVM能够知道数组元素的实际类型是Integer,当其它对象加入数组是就会抛出异常(java.lang.ArrayStoreException)。

泛型的设计目的之一就是保证了类型安全,让这种运行时期的错误在编译期就能发现,所以泛型是不支持协变的。例如:

List<Number> nums = new ArrayList<Integer>(); // incompatible types

当确实需要建立这种向上转型的类型关系的时候,就需要用到泛型的通配符特性了。例如:

List<? extends Number> nums = new ArrayList<Integer>(); // OK

无边界通配符(Unbounded Wildcards)

语法:

class-name<?> var-name

例子:

public static void print(List<?> list) {
    for (Object obj : list) {
        System.out.println(o);
    }
}

List<?> list和List list的区别:

  • List<?> list是表示持有某种特定类型对象的List,但是不知道是哪种类型;List list是表示持有Object类型对象的List。
  • List<?> list因为不知道持有的实际类型,所以不能add任何类型的对象,但是List list因为持有的是Object类型对象,所以可以add任何类型的对象。
    注意:List<?> list可以add(null),因为null是任何引用数据类型都具有的元素。

Pair<?> 和 Pair 的区别

  • Pair<?>的  ? getFirst()方法,返回值只能赋值给一个Object对象,它的void setFirst(? )方法不能被调用,甚至不能用Object调用。
  • 为什么要使用这样脆弱的类型?它对于许多简单的操作非常有用。例如 ,下面这个方法将用来测试一个 pair 是否包含一个 mill 引用,它不需要实际的类型。
    public static boolean hasNulls (Pair<?> p)
    {
        return p.getFirstO = null | | p.getSecondO = null ;
    }
    通过将 hasNulls 转换成泛型方法,可以避免使用通配符类型:
    public static <T> boolean hasNulls (Pair<T> p)
    但是,带有通配符的版本可读性更强。

上边界限定的通配符(Upper Bounded Wildcards)

语法:

class-name<? extends superclass> var-name

例子:

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

    return s;
}

List<? extends Number> list = new ArrayList<Integer>(); // OK
List<? extends Number> list = new ArrayList<Object>(); // error

特性:

  • List<? extends Number> list表示某种特定类型(Number或者Number的子类)对象的List。跟无边界通配符一样,因为无法确定持有的实际类型,所以这个List也不能add除null外的任何类型的对象
list.add(new Integer(1)); // error
list.add(null); // OK
  • 从list中获取对象是是可以的(比如get(0)),因为在这个List中,不管实际类型是什么,但肯定都能转型为Number。
Number n = list.get(0); // OK
Integer i = list.get(0); // error
  • 事实上,只要是形式参数有使用类型参数的方法,在使用无边界或者上边界限定的通配符的情况下,都不能调用。比如以java.util.ArrayList为例:
public E get(int index) // 可以调用
public int indexOf(Object o) // 可以调用
public boolean add(E e) // 不能调用

下边界限定的通配符(Lower Bounded wildcards)

语法:

class-name<? super subclass> var-name

例子:

public static void writeTo(List<? super Integer> list) {
    // ...
}

List<? super Number> list = new ArrayList<Number>(); // OK
List<? super Number> list = new ArrayList<Object>(); // OK
List<? super Number> list = new ArrayList<Integer>(); // error

特性:

  • List<? super Integer> list表示某种特定类型(Integer或者Integer的父类)对象的List。可以确定这个List持有的对象类型肯定是Integer或者其父类,所以往list里面add一个Integer或者其子类的对象是安全的,因为Integer或者其子类的对象都可以向上转型为Integer的父类对象。但是因为无法确定实际类型,所以往list里面add一个Integer的父类对象是不安全的。
list.add(new Integer(1)); // OK
list.add(new Object()); // error
  • 当从List<? super Integer> list获取具体的数据的时候,JVM在编译的时候知道实际类型可以是任何Integer的父类,所以为了安全起见,要用一个最顶层的父类对象来指向取出的数据,这样就可以避免发生强制类型转换异常了。
Object obj = list.get(0); // OK
Integer i = list.get(0); // error

PECS原则(Producer Extends Consumer Super)

从上面上边界限定的通配符和下边界限定的通配符的特性,可以知道:

  • 对于上边界限定的通配符,无法向其中加入任何对象,但是可以从中正常取出对象。
  • 对于下边界限定的通配符,可以存入subclass对象或者subclass的子类对象,但是取出时只能用Object类型变量指向取出的对象。

简而言之,上边界限定(extends)的通配符适合于内容的获取,而下边界限定(super)的通配符更适合于内容的存入。所以就有了一个PECS原则来很好的解释这两种通配符的使用原则。

  • 当一个数据结构作为producer对外提供数据的时候,应该只能取数据而不能存数据,所以适合使用上边界限定(extends)的通配符。
  • 当一个数据结构作为consumer获取并存入数据的时候,应该只能存数据而不能取数据,所以适合使用下边界限定(super)的通配符。
  • 如果既需要取数据也需要存数据,就不适合使用泛型的通配符。
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    for (int i = 0; i < src.size(); i++) {
        dest.set(i, src.get(i));
    }
}

原文地址:https://www.cnblogs.com/everest33Tong/p/11588373.html

时间: 2024-07-31 20:30:12

Java泛型之通配符的相关文章

JAVA泛型-通配符

黑马程序员:Java培训.Android培训.iOS培训..Net培训 JAVA泛型-通配符 一.泛型与继承 有如下的继承关系和以它们为类型参数的泛型: public class Holder<T>{ T t; public Holder(){} public Holder(T at){ t = at;} public void set(T at){ t = at;} public T get(){return t; } } //有如下代码: Holder<Apple> apple

Java 泛型、通配符? 解惑

Java 泛型通配符?解惑 分类: JAVA 2014-05-05 15:53 2799人阅读 评论(4) 收藏 举报 泛型通配符上界下界无界 目录(?)[+] 转自:http://www.linuxidc.com/Linux/2013-10/90928.htm T 有类型 ? 未知类型 一.通配符的上界 既然知道List<Cat>并不是List<Anilmal>的子类型,那就需要去寻找替他解决的办法, 是AnimalTrianer.act()方法变得更为通用(既可以接受List&

Java泛型和通配符那点事

泛型(Generic type 或者generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样. 可以在集合框架(Collection framework)中看到泛型的动机.例如,Map类允许您向一个Map添加任意类的对象,即使最常见的情况是在给定映射(map)中保存某个特定类型(比如String)的对象. 因为Map.get()被定义为返回Object

JAVA 泛型与通配符的使用

泛型的本质是参数化类型.即所操作的数据类型被指定为一个参数. 1.jdk 1.5/1.6 必须显式的写出泛型的类型. 2.jdk 1.7/1.8 不必显式的写出泛型的类型. 一.泛型声明 可以用<T>.<K,V>.<T  extends  Number>等进行泛型的声明.其中,<T  extends  Number>的声明方式限定了T的范围,T只能为 Number的子类. 1.参数类型用在类的创建中,泛型类. 2.参数类型用在接口的创建中,泛型接口. 3.参

Java泛型中通配符的使用

学习目标 掌握通配符"?" 的使用 掌握受限泛型的设置 掌握泛型与子类继承的限制 匹配任意类型的通配符 在开发中对象的引用传递是最常见的,但是如果在泛型类的操作中,在进行传递的时候泛型类型必须匹配才可以传递.否则是无法传递的. class Info<T>{ private T var ; // 定义泛型变量 public void setVar(T var){ this.var = var ; } public T getVar(){ return this.var ; }

JAVA泛型通配符T,E,K,V区别,网友回复:一文秒懂

先解释下泛型概念泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型接口.泛型方法.Java语言引入泛型的好处是安全简单. 在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的"任意化","任意化"带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的.对于强制类型转换错误的

JAVA泛型通配符T,E,K,V区别,T以及Class&lt;T&gt;,Class&lt;?&gt;的区别

1. 先解释下泛型概念 泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型接口.泛型方法.Java语言引入泛型的好处是安全简单.在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的.对于强制类型转换错误的情况,编译器可能不提示错

Java的泛型和通配符

泛型:1.泛型类    class A<T>{    }2.在创建实例时,需要为其类型变量赋值 3.泛型方法    class A<T>{        public T fun1(){}        public void fun2(T t){}        //以上两个都不是泛型方法,他们是泛型类里面的一个方法        //发现方法要求需要在方法上有泛型的定义        public <T> T fun3(){}//此为泛型方法    } class

java 泛型详解(普通泛型、 通配符、 泛型接口,泛型数组,泛型方法,泛型嵌套)

JDK1.5 令我们期待很久,可是当他发布的时候却更换版本号为5.0.这说明Java已经有大幅度的变化.本文将讲解JDK5.0支持的新功能-----Java的泛型. 1.Java泛型  其实Java的泛型就是创建一个用类型作为参数的类.就象我们写类的方法一样,方法是这样的method(String str1,String str2 ),方法中参数str1.str2的值是可变的.而泛型也是一样的,这样写class Java_Generics<K,V>,这里边的K和V就象方法中的参数str1和st