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. Tensorflow (1)

    'tf.placeholder' or 'tf.Variable' The difference is that with tf.Variable you have to provide an ini ...

  2. Shell终端收听音乐--网易云音乐命令行版

    Musicbox:网易云音乐命令行版本 高品质网易云音乐命令行版本,简洁优雅,丝般顺滑,基于Python编写. 这款命令行的客户端使用 Python 构建,以 mpg123 作为播放后端: Vim 式 ...

  3. for for-in foreach 循环

    var a = [1, 2, 3, 4, 5] //for用来便利数组元素非常简单 for (var i = 0; i < a.length; i++) { console.log(a[i]) ...

  4. CentOS网络接口配置文件ifcfg-eth详解

    ======CentOS网络接口配置文件ifcfg-eth详解====== 文件 /etc/sysconfig/network-scripts/ifcfg-eth0在/etc/sysconfig/ne ...

  5. WPF Application

    Application类作为启动的入口,在VS中,通常自动代码为我们继承了Application类,这样做的有点,我还没有理解到,但是我们先学到这个知识点. 为了能够更好的控制整个启动过程,包括得到A ...

  6. DB2导入导出 学习笔记

    db2pd -osinfodb2mtrk -i -d (for aix)db2 get dbm cfg show detaildb2 get db cfg show detaildb2 get sna ...

  7. RandomAccessFile类初次使用

    RandomAccessFile : java提供的对文件内容的访问 既可以读文件 也可以写文件 支持随机访问文件 可以访问文件的任意位置 (1)java文件模型 : 在硬盘上的文件是byte byt ...

  8. jquery+ajax 实现text框模糊搜索并可利用listbox实时显示模糊搜索列表结果

    功能描述: text框中输入,text框下面的listbox中实时显示依据输入的内容进行模糊搜索的结果 js代码 $j(function() { $j("input[id='txtCos'] ...

  9. 窗口、easyui-window、easyui-panel、easyui-linkbutton

    //窗口 <script type="text/javascript" src="js/jquery.min.js"></script> ...

  10. redis - 主从复制与主从切换

    redis2.8之前本身是不支持分布式管理的,一般建议使用redis3.0及以后版本 redis主从切换的方法 keepalive  或者 使用sentinel线程管理 说明如何使用sentinel实 ...