Java 泛型 协变性、逆变性

@author ixenos

摘要:协变性、协变通配符、协变数组、协变返回值

协变性、逆变性和无关性


在面向对象的计算机程序语言中,经常涉及到类型之间的转换,例如从具体类小猫到动物之间的类型转换(上行转换),或者从形状向三角形之间的转换(下行转换)。

协变性(covariance)、逆变性(contravariance)和无关性(invariant)。他们都是用来描述类型转换的性质的术语,形式化的描述如下:

如果A和B是类型,f表示类型转换,≤表示子类型关系,(例如A≤B,表示A是B的子类)那么:

如果A≤B 则f(A) ≤ f(B),小推大,那么f是协变的;如果A≤B 则f(B) ≤ f(A),大推小,那么f是逆变的;如果上面两种都不成立,那么f是无关的。

Java语言有一些语法可以用协变性和逆变性来理解


  • Java泛型
class DataHolder<T>{

}

    假如类型 A ≤ B, 但是直接使用DataHolder<A> 和DataHolder<B>是不可协变的,之前我们已经叙述过。

    但是利用Java提供的通配符语法,却可以提供一个协变的类型转换
       DataHolder<A> ≤DataHolder<? Extends B>  // ?是通配符,但这样协变是类型不安全的

    例如:

static boolean find(Iterable<? extends Object> where, Object what){ //通配符?赋给泛型变量,可以有一个协变的类型转换
return false;
}

    协变的类型转换:可使用Iterable<String> 来调用find函数。

协变数组


  协变数组是有漏洞的,具体分析请看:数组协变带来的静态类型漏洞 rednaxelafx.iteye.com/blog/379703

  注意:
例如 A =
String, B = Object 我们这里有A≤B
f(A) = String[], f(B) = Object[]. 因为Object[] objects =
new String[n].(赋值) 所以我们可以认为数组具有协变性

协变返回值(在子类重写方法的时候)


在面向对象语言中,一个协变返回值方法是一个在子类覆盖(Override)该方法的时候,方法的返回值可以被一个“更窄”的类型所替代。(Java JDK5.0)
例如:

public static class Super {
Object getSomething() {
return null;
}
} public static class SubClass extends Super {
@Override
String getSomething() {
return null;
}
}

这段话主要的思想就是协同返回值使用在子类需要一个不同于父类覆盖方法的返回值,但是返回值却有继承关系的情况下。
一个可能更好的例子如下所示:

class Collection {
Iterator iterator() { ... }
} class List extends Collection {
@Override
ListIterator iterator() { ... }
}

Iterator函数获得到当前集合的迭代器,在子类中,迭代器有着更确切的表示,所以使用了Iterator的子类ListIterator作为新的返回值。

逆变参数(形参)


  继承的一个法则:子类不能比父类有更严格的限制
先举个例子,可以一下子明白逆变参数的例子,其实逆变参数使用环境依然是在继承体系下的函数覆盖

class Super {
void doSomething(String parameter) {
}
} class Sub extends Super {
void doSomething(Object parameter) { //逆变了
}
}

为了更好的理解这个问题,首先引入Liskov Substitution Principle的概念。这个原则的内容就是说,子类的对象可以在任何需要父类对象的地方使用
这个原则和参数以及返回值有什么关系呢?
假如我们定义类如下:

class Super {
ReturnType doSomething(ParameterType a) {
return null;
}
} class Sub extends Super {
ReturnType doSomething(ParameterType a) {
return null;
}
}

假如我有Super sup;
首先考虑参数,现在要调用sup.doSomething(o);
如果两个函数需要的参数类型一致,没有什么值得讨论的。假如在一些情况下,sub需要的参数类型变成了和ParameterType有继承关系的另一个类NewParameterType。如下:

class Sub extends Super {
ReturnType doSomething(NewParameterType a) {
return null;
}
}

如果NewParameterType是ParameterType的子类,那么sup.doSomething(o)调用中,如果o是指向NewParameterType的对象,则不会被通过。只有NewParameterType是ParameterType的父类的时候,才不会发生任何问题(形参要变只能逆变)。所以在提出逆变参数的概念。
同样地,利用这个原则也可以用来理解。
假如在某使用环境中有如下语句:
ReturnType rt = sup.doSomething(o);
假如设计变更,子类需要返回一个与ReturnType有继承关系的类NewReturnType,如果返回的是ReturnType的父类,那么则上面语句则不能通过编译,只有NewReturnType是ReturnType的子类的时候,才不会违反Liskov子类原则。

Java 泛型 协变性、逆变性的更多相关文章

  1. Java泛型的逆变

    在上篇<Java泛型的协变>这篇文章中遗留以下问题——协变不能解决将子类型添加到父类型的泛型列表中.本篇将用逆变来解决这个问题. 实验准备 我们首先增加以下方法,见代码清单1所示. 代码清 ...

  2. JAVA泛型——逆变

    在上篇<JAVA泛型——协变>这篇文章中遗留以下问题——协变不能解决将子类型添加到父类型的泛型列表中.本篇将用逆变来解决这个问题. 实验准备 我们首先增加以下方法,见代码清单1所示. 代码 ...

  3. Java泛型的协变

    在上篇<Java泛型的基本使用>这篇文章中遗留以下问题,即将子类型也能添加到父类型的泛型中,要实现这种功能必须借助于协变. 实验准备 现在在上篇文章展示的Decorator类型的基础上,增 ...

  4. JQuery选择器大全 前端面试送命题:面试题篇 对IOC和DI的通俗理解 c#中关于协变性和逆变性(又叫抗变)帮助理解

    JQuery选择器大全   jQuery 的选择器可谓之强大无比,这里简单地总结一下常用的元素查找方法 $("#myELement")    选择id值等于myElement的元素 ...

  5. 不变性、协变性和逆变性(Invariance, Covariance & Contravariance)

    源码下载 一.里氏替换原则(Liskov Substitution Principle LSP) 我们要讲的不是协变性和逆变性(Covariance & Contravariance)吗?是的 ...

  6. oc 的 协变性与逆变性

    ?协变性与逆变性是类型关系在范畴论的定义.是类型的继承关系在高阶类型中的定义? __kindof只是在统一继承体系下方便了类型转化,提供了使用时语法上的便捷:但是对于类型转换是否正确不做判定: kin ...

  7. Java泛型中的协变和逆变

    Java泛型中的协变和逆变 一般我们看Java泛型好像是不支持协变或逆变的,比如前面提到的List<Object>和List<String>之间是不可变的.但当我们在Java泛 ...

  8. Java泛型的协变与逆变

    泛型擦除 Java的泛型本质上不是真正的泛型,而是利用了类型擦除(type erasure),比如下面的代码就会出现错误: 报的错误是:both methods  have same erasure ...

  9. Java中的逆变与协变

    看下面一段代码 Number num = new Integer(1); ArrayList<Number> list = new ArrayList<Integer>(); ...

随机推荐

  1. 2014蓝桥杯问题 C: 神奇算式

    没做完,先搞答题了 #include <stdio.h> #include<string.h> #include<stdlib.h> int comp(const ...

  2. 9.mybatis动态SQL标签的用法

    mybatis动态SQL标签的用法   动态 SQL MyBatis 的强大特性之一便是它的动态 SQL.如果你有使用 JDBC 或其他类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句有多么 ...

  3. ManualResetEvent和AutoResetEvent的区别

    在讨论这个问题之前,我们先了解这样一种观点,线程之间的通信是通过发信号来进行沟通的.(这不是废话) 先来讨论ManualResetEvent,讨论过程中我会穿插一些AutoResetEvent的内容, ...

  4. Salesforce自主学习(一)

    Salesforce学习--接触Apex: 学习目标: 1.描述出Apex程序语言的关键特点: 2.保存一个Apex类并用另一个Apex类来调用它的方法: 3.使用Developer Console检 ...

  5. redis的持久化 rdb和aof

    1.rdb(Redis DataBase) 当满足条件时,redis单独会fork(创建)一个新的线程,会先将内存中的数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次已经持久化 ...

  6. CUDA开发存储器运用(包括存储器之间的转存)

    主机端内存(host memory) 主机端叶锁定内存(pinned memory) 显存 寄存器(register) 局部存储器(local memory) 共享存储器(shared memory) ...

  7. HTML5扩展之微数据与丰富网页摘要itemscope, itemtype, itemprop

    HTML5扩展之微数据与丰富网页摘要 by zhangxinxu from http://www.zhangxinxu.com本文地址:http://www.zhangxinxu.com/wordpr ...

  8. 【NOIP2006提高组】能量项链

    说好的好好写人话的题解 嗯很多题解都说过这是一个石子合并的模型它也确实就是一个石子合并的模型.然而就算这样我也不会写最后仍然写了个记忆化搜索 首先我们不论环状,就直接一条链型,当只剩下两个珠子的时候, ...

  9. MFC多线程各种线程用法 .

    http://blog.csdn.net/qq61394323/article/details/9328301 一.问题的提出 编写一个耗时的单线程程序: 新建一个基于对话框的应用程序SingleTh ...

  10. jquery 使用attr() 函数对复选框无效的原因

     复选框是网站开发的时候经常用到的网页标签之一,常见的在页面上对复选框的操作包括取值和修改复选框的状态.在jquery中,常见的操作标签的值得函数为attr,然而在操作复选框的时候,通常采用的却是pr ...