[改善Java代码]警惕泛型是不能协变和逆变的
什么叫做协变(covariance)和逆变(contravariance)?
在变成语言的类型框架中,协变和逆变是指宽类型和窄类型在某种情况下(如参数,泛型,返回值)替换或交换的特性,简单的说,协变是用一个窄类型替换宽类型,而逆变则是用宽类型覆盖窄类型.
协变:宽类型------>窄类型
逆变:窄类型------>宽类型
class Base{
public Number doStuff(){
return 0;
}
}
class Sub extends Base{
@Override
public Integer doStuff(){
return 0;
}
}
子类的 doStuff 方法返回值的类型比父类方法要窄(Integer extend Number),此时 doStuff 方法就是一个协变方法,同时根据 Java 的覆写定义来看,这又属于覆写。那什么是逆变呢?代码如下:
class Base{
public void doStuff(Integer i){
}
}
class Sub extends Base{
public void doStuff(Number n){
}
}
子类的 doStuff 方法的参数类型比父类要宽,此时就是一个逆变方法,子类扩大了父类方法的输入参数,但是根据覆写定义来看,doStuff 不属于覆写,只是重载而已。
由于此时的doStuff方法已经与父类没有任何关系了,只是子类独立扩展出的 一个行为,所以是否声明为doStuff方法名意义不大,逆变已经不具有特别的意义了.我们来重点关注一下协变.
public class Client {
public static void main(String[] args) {
Base base = new Sub();
}
}
base变量是否发生了协变?是的....发生了协变,base变量是Base类型,它是父类,而其赋值却是子类实例,也就是用窄类型覆盖了宽类型,这也叫做多态(Polymorphism),两者相同含义,在Java世界里,"重复发明"轮子的事情多了去了.
泛型是既不支持协变也不支持逆变.为什么?
(1)泛型不支持协变
数组和泛型很相似,一个是中括号,一个是尖括号,那我们就以数组为参照对象,看代码:
public class Client {
public static void main(String[] args) {
//数组支持协变
Number[] n = new Integer[10];
//编译不通过,泛型不支持协变
//List<Number> ln = new ArrayList<Integer>();//报错
//Type mismatch: cannot convert from ArrayList<Integer> to List<Number>
}
}
ArrayList是List的子类型,Integer是Number的子类型,里氏替换原则在此行不通了,原因就是Java为了保证运行期的安全性,必须保证泛型参数类型是固定的.
所以它不允许一个泛型参数可以同时包含两种类型,即使是父子类关系也不行.
泛型不支持协变,但可以使用通配符(Wildcard)模拟协变.代码如下:
//Number的子类型都可以是泛型参数类型
List<? extends Number> ln = new ArrayList<Integer>();
"? extends Number" 表示的意思是,允许Number所有的子类(包括自身)作为泛型参数类型,但在运行期只能是一个具体类型,或者是Integer或者是Double类型,
或者是Number类型,也就是说通配符只是在编码期有效,运行期必须是一个确定类型.
(2)泛型不支持逆变
Java虽然可以允许逆变存在,单在对类型赋值上是不允许逆变的,你不能把一个父类实例对象赋值给一个子类类型变量,泛型自然也不允许此种情况发生了,
但是它可以使用super关键字来模拟实现.代码如下:
//Integer的父类型(包括Integer)都可以是泛型参数类型
List<? super Integer> li = new ArrayList<Number>();
"
"? super Integer" 的意思是可以把所有Integer父类型(自身,父类或接口)作为泛型参数,这里看着就像是把一个Number类型的ArrayList赋值给了Integer类型的List,
其外观类似于使用一个宽类型覆盖一个窄类型,它模拟了逆变的实现.
泛型既不支持协变也不支持逆变,带有泛型参数的子类型定义与我们经常使用的类类型也不相同,其基本的类型关系如下: 泛型通配符的Q&A
1.Integer是Number的子类型 √
2.ArrayList<Integer>是List<Integer>的子类型 √
3.Integer[] 是Number[] 的子类型 √
4.List<Integer> 是List<Number>的子类型 ×
5.List<Integer> 是List<? extends Integer>的子类型 ×
6.
List<Integer> 是List<? super Integer>的子类型 ×
Java的泛型不支持逆变和协变,只是能够实现逆变和协变.
[改善Java代码]警惕泛型是不能协变和逆变的的更多相关文章
- Java泛型中的协变和逆变
Java泛型中的协变和逆变 一般我们看Java泛型好像是不支持协变或逆变的,比如前面提到的List<Object>和List<String>之间是不可变的.但当我们在Java泛 ...
- C#4.0新增功能03 泛型中的协变和逆变
连载目录 [已更新最新开发文章,点击查看详细] 协变和逆变都是术语,前者指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型,后者指能够使用比原始指定的派生类型的派生程度更小(不太具体 ...
- C#高级编程之泛型三(协变与逆变)
为何引入协变.逆变 我们知道一个子类对象可以赋值给一个基类对象 Animal animal = new Animal(); Animal cat = new Cat(); 那如果是用在泛型里面能行嘛? ...
- 编写高质量代码改善C#程序的157个建议[协变和逆变]
前言 本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html .本文主要学习记录以下内容: 建议42.使用泛型参数兼容泛型接口的不可变性 建议43.让接口 ...
- .NET泛型中的协变与逆变
泛型的可变性:协变性和逆变性 实质上,可变性是以一种类型安全的方式,将一个对象作为另一个对象来使用. 我们已经习惯了普通继承中的可变性:例如,若某方法声明返回类型为Stream,在实现时可以返回一个M ...
- [改善Java代码]警惕自增的陷阱
建议7: 警惕自增的陷阱 老师就说:自增有两种形式,分别是i++和++i,i++表示的是先赋值后加1,++i是先加1后赋值,这样理解了很多年也没出现问题,直到遇到如下代码,我才怀疑我的理解是不是错了: ...
- [改善Java代码]警惕数组的浅拷贝
建议62:警惕数组的浅拷贝 一.分析 在日常工作中,我们会遇见很多数组的拷贝和复制的问题,但是在你使用系统提供的API进行编码的时候,无形中会留下浅拷贝的隐患. 二.场景 有这样一个例子,第一个箱 ...
- C# 泛型的协变和逆变
1. 可变性的类型:协变性和逆变性 可变性是以一种类型安全的方式,将一个对象当做另一个对象来使用.如果不能将一个类型替换为另一个类型,那么这个类型就称之为:不变量.协变和逆变是两个相互对立的概念: 如 ...
- C# 泛型的协变和逆变 (转载)
1. 可变性的类型:协变性和逆变性 可变性是以一种类型安全的方式,将一个对象当做另一个对象来使用.如果不能将一个类型替换为另一个类型,那么这个类型就称之为:不变量. 协变和逆变是两个相互对立的概念: ...
随机推荐
- 第二百五十天 how can I 坚持
html排版,好烦心. 我以为我会哭,但是我没有.---<领悟> 确实是搞不懂自己,到底想要什么?弟弟最近也不知道咋的了,感觉有点不对劲呢. 最近雾霾好严重,希望我们的后代不会知道雾霾是什 ...
- Python模块(Module)
一个Python Module(模块),是一个文件,包含了Python对象定义和Python语句(definitions and statements).文件名就是模块名加上后缀.py,在模块内部,模 ...
- HDU 4432 Sum of divisors (水题,进制转换)
题意:给定 n,m,把 n 的所有因数转 m 进制,再把各都平方,求和. 析:按它的要求做就好,注意的是,是因数,不可能有重复的...比如4的因数只有一个2,还有就是输出10进制以上的,要用AB.. ...
- linux 查看当前所在目录的全路径
有时候,使用linux的shell的时候需要查看当前位置的全路径,可以使用 pwd命令 当然,知道了该命令就可以通过man pwd来查看该命令的全部帮助手册.
- Connection 连接字符串释义
本文将详细介绍如何使用Connection对象连接数据库.对于不同的.NET数据提供者,ADO.NET采用不同的Connection对象连接数据库.这些Connection对象为我们屏蔽了具体的实现细 ...
- 剑指OFFER之变态跳台阶(九度OJ1389)
题目描述: 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级.求该青蛙跳上一个n级的台阶总共有多少种跳法. 输入: 输入可能包含多个测试样例,对于每个测试案例, 输入包括一个整数n(1 ...
- 使用Github遇到的问题及解决办法
问题一: 当push代码上去仓库时,出现 ! [rejected] master -> master (fetch first) error: failed to push som ...
- Arduino 4X3键盘 AD模拟分量实现
//读分量测试 ---------------- ; //模拟口0 ; // 取得键AD模拟分量 void setup() { //通信波特率 Serial.begin(); } void loop( ...
- C++转义字符使用
编码过程中字符串可能过长,这通常须要换行,对于换行转义字符\ ,使用时要保证\后无空格,否则会出现"error C2017:非法的转义字符 "错误 如 // ''\"后 ...
- Java模拟网站登录
web登陆无非就是网页获取,cookie 的管理,post和get方式的模拟. 1.网页内容获取 java.io.InputStream in; java.net.URL url = new java ...