Java泛型之协变与逆变及extends与super选择

目录
  • 什么是不变
  • 什么是协变
  • 什么是逆变
  • extends 和 super
  • 使用extends还是super呢

要了解协变与逆变,首先要引入:

根据 Liskov替换原则,如果C是P的子类,则P可以代替C,即 P p = new C();
C继承于P,记做为 C < P

什么是不变

如果F是不变,当 C <= P 时,那么 F(C) 和 F(P) 没有任何继承关系

除例如 Integer是 Number的子类,根据 Liskov替换原则

Number number = new Integer(1);  //correct

但是如果这样写就会报错

List<Number> list = new ArrayList<Integer>(1);  //error

虽然 Number和 Integer存在继承关系:Integer < Number, 但在Java里,泛型默认是不变的, 因此也可以看作为 List<Number> List<Integer> 不存在任何继承关系

什么是协变

如果F是协变的,当 C <= P 时,那么 F(C) <= F(P)

Java 提供了一个extends来将不变转为协变,例如:

List<? extends Number> list = new ArrayList<Integer>(1);  //corrent

此时的List<? extends Number>可以看作为ArrayList<Integer>的父类

? extend Number可以看作为一个类型范围,表示Number的某一个子类

数组默认是协变的

Number[] numbers = new Integer[3]; 

什么是逆变

如果F是逆变的,当 C <= P 时,那么 F(C) >= F(P)

Java 提供了一个super来将不变转为协变,例如:

List<? super Number> list = new ArrayList<Object>(1);  //corrent

此时的 List<? super Number>可以看作为 ArrayList<Object>的父类

extends 和 super

首先,我们看看Collection.add的实现:

public interface List<E> extends Collection<E> { boolean add(E e); }

下面代码将会报错?? extends NumberInteger类型不匹配

List<? extends Number> list = new ArrayList<Integer>(); // correct
list.add(Integer.valueOf(1));  //error

首先在调用add方法时,泛型E自动变成了<? extends Number>

第二行报错,也就是说? extends Number不是Integer的父类。这里要将 List<? extends Number>ArrayList<Integer>的父类区分开。

? extends Number可以看作为一个类型范围中某一个类型,表示Number的某一个子类,但又没明确是哪个子类,可能是Float,可能是Short,也可能是Integer的子类(Integer被final修饰,不可能有子类,这里只是一种假设情况),它只确定了它的上界为 Number,并没有确定下界(有可能存在? extends NumberInteger),因此 ? extends Number不是Integer的父类

将上面代码稍做修改就正确了:

List<? super Number> list = new ArrayList<Object>(); // correct
list.add(Integer.valueOf(1));  //correct

首先因为逆变,List<? super Number>ArrayList<Object>的父类,第一行正确。

第二行: ? super NumberInteger的父类,原因是:? super Number表示Number的某一个父类,可能是Serializable也可能是 Object 但不管是哪个,Number的父类一定是Integer的父类,因此第二行也正确

使用extends还是super呢

java.util.Collections的copy方法(JDK1.7)给了我们答案:

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());
        }
    }
}
  • 要从泛型类取数据时,用extends;
  • 要往泛型类写数据时,用super;
  • 既要取又要写,就不用通配符(即extends与super都不用)
private static <E> E getFirst(List<? extends E> list){
    return list.get(0);
}

private static <E> void setFirst(List<? super E> list, E firstElement){
    list.add(firstElement);
}

public static void main(String[] args) {
    List<Integer> list = new ArrayList<Integer>();
    setFirst(list, 1);
    Number number = getFirst(list);
}

到此这篇关于Java泛型之协变与逆变及extends与super选择的文章就介绍到这了,更多相关Java泛型 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2022-05-11

一篇文章带你了解Java泛型的super和extends

目录 概念简单理解 代码样例解读 关于List<? super T> add方面 返回值方面 关于List<? extendsT> add方面 返回值方面 总结 概念简单理解 List<? extends T>表示该集合中存在的都是类型T的子类,包括T自己 List<? super T>表示该集合中存的都是类型T的父类,包括T自己 代码样例解读 父子类代码: /** * 生物 */ static class Biological{ } /** * 动物 */

Java泛型extends及super区别实例解析

<? extends T>和<? super T>是Java泛型中的"通配符(Wildcards)"和"边界(Bounds)"的概念. <? extends T>:是指"上界通配符(Upper Bounds Wildcards)" <? super T>:是指"下界通配符(Lower Bounds Wildcards)" 为什么要用通配符和边界? 使用泛型的过程中,经常出现一种很

java 泛型的详解及实例

java 泛型的详解及实例 Java在1.5版本中增加了泛型,在没有泛型之前,从集合中读取每一个对象都需要进行强转,如果一不小心插入了类型错误的对象,在运行时就会报错,给日常开发带来了很多不必要的麻烦,比如以下代码: public class TestGeneric { public static void main(String[] args) { List list = new ArrayList(); list.add(" name:"); list.add(" zer

Java Socket编程心跳包创建实例解析

1.什么是心跳包? 心跳包就是在客户端和服务器间定时通知对方自己状态的一个自己定义的命令字,按照一定的时间间隔发送,类似于心跳,所以叫做心跳包. 用来判断对方(设备,进程或其它网元)是否正常运行,采用定时发送简单的通讯包,如果在指定时间段内未收到对方响应,则判断对方已经离线.用于检测TCP的异常断开.基本原因是服务器端不能有效的判断客户端是否在线,也就是说,服务器无法区分客户端是长时间在空闲,还是已经掉线的情况.所谓的心跳包就是客户端定时发送简单的信息给服务器端告诉它我还在而已.代码就是每隔几分

Java内存模型原子性原理及实例解析

这篇文章主要介绍了Java内存模型原子性原理及实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 本文就具体来讲讲JMM是如何保证共享变量访问的原子性的. 原子性问题 原子性是指:一个或多个操作,要么全部执行且在执行过程中不被任何因素打断,要么全部不执行. 下面就是一段会出现原子性问题的代码: public class AtomicProblem { private static Logger logger = LoggerFactory.

python数据类型判断type与isinstance的区别实例解析

在项目中,我们会在每个接口验证客户端传过来的参数类型,如果验证不通过,返回给客户端"参数错误"错误码. 这样做不但便于调试,而且增加健壮性.因为客户端是可以作弊的,不要轻易相信客户端传过来的参数. 验证类型用type函数,非常好用,比如 >>type('foo') == str True >>type(2.3) in (int,float) True 既然有了type()来判断类型,为什么还有isinstance()呢? 一个明显的区别是在判断子类. type(

Java编程通过匹配合并数据实例解析(数据预处理)

本文研究的主要是Java编程通过匹配合并数据(数据预处理)的相关内容,具体如下. 数据描述 以下程序是对如下格式的数据进行合并处理. 这个表的每一行表示用户id及用户的特征.其中,一个用户只有一个特征向量,即第一列不会重复. 这张表的第一列,表示用户的id,第二列表示用户所看的电影,第三列表示用户对电影的打分(1-13分),第四列表示用户对电影的打分,但分值范围是1-5分. 问题描述 在做数据预处理时,如何将第二张表添加上用户特征呢?其实,方法很简单,将第二张表的用户id与第一张表的用户id进行

JavaScript常用截取字符串的三种方式用法区别实例解析

stringObject.substring(start,stop) 用于提取字符串中介于两个指定下标之间的字符. start必需.一个非负的整数,规定要提取的子串的第一个字符在 stringObject 中的位置. stop可选.一个非负的整数,比要提取的子串的最后一个字符在 stringObject 中的位置多 1.如果省略该参数,那么返回的子串会一直到字符串的结尾. start从0开始 到stop(不包含stop)结束 不接受负的参数. stringObject.substr(start,

java阻塞队列实现原理及实例解析

这篇文章主要介绍了java阻塞队列实现原理及实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 阻塞队列与普通队列的不同在于.当队列是空的时候,从队列中获取元素的操作将会被阻塞,或者当队列满时,往队列里面添加元素将会被阻塞.试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素.同样,试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程使队列重新变得空闲起来,如从队列中移除一个或者多个元素,或者完

Java定义形式及可变参数实例解析

这篇文章主要介绍了Java定义形式及可变参数实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 Java中的方法类似于面向过程程序设计中的函数,但与其不同的是,Java中的方法不能独立存在,它属于类或对象.既然方法属于类或对象,那么,方法的调用者就必须是类或对象.(当然,之后将会提到的同一个类中方法互相调用,实际上也是类或对象在作为调用者) 还是先上一段代码: package com.my.pac05; /** * @author Summ

Java原子变量类原理及实例解析

这篇文章主要介绍了Java原子变量类原理及实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.原子变量类简介 为何需要原子变量类 保证线程安全是 Java 并发编程必须要解决的重要问题.Java 从原子性.可见性.有序性这三大特性入手,确保多线程的数据一致性. 确保线程安全最常见的做法是利用锁机制(Lock.sychronized)来对共享数据做互斥同步,这样在同一个时刻,只有一个线程可以执行某个方法或者某个代码块,那么操作必然是原子性