Java 逆变与协变的名词说明
最近在研究Thinking in Java的时候,感觉逆变与协变有点绕,特意整理一下,方便后人。我参考于Java中的逆变与协变,但是该作者整理的稍微有点过于概念化,我在这里简单的说一下
我对于协变于逆变的理解
一:协变
协变返回类型指的是子类中的成员函数的返回值类型不必严格等同于父类中被重写的成员函数的返回值类型,而可以是更 "狭窄" 的类型。当然协变也会出现在数据,泛型等地方。
1:协变的简单实例
参考于 “理解Java中的协变返回类型”。 下边代码中,子类方法的返回值类型是父类方法返回值类型的子类型,这就是简单的协变示意。
import java.io.ByteArrayInputStream;
import java.io.InputStream; class Base
{
//子类Derive将重写此方法,将返回类型设置为InputStream的子类
public InputStream getInput()
{
return System.in;
}
}
public class Derive extends Base
{ @Override
public ByteArrayInputStream getInput()
{ return new ByteArrayInputStream(new byte[1024]);
}
public static void main(String[] args)
{
Derive d=new Derive();
System.out.println(d.getInput().getClass());
}
}
/*程序输出:
class java.io.ByteArrayInputStream
*/
2:数组使用协变
数组支持协变, 比如 Parent [] pets =new Son[10] 。如果 son是parent的子类,那么这种定义形式在Java编译期是允许的。
但是,java中 数组协变 很容易导致错误:
出错的例子:
public static void main(String[] args) {
//编译期不报错
Object[] number = new Integer[10];
number[0] = "123";
System.out.println(number[0]);
}
/**
结果:
Exception in thread "main" java.lang.ArrayStoreException: java.lang.String
at com.generic.TestN.main(TestN.java:8)
**/
正常的例子:
public static void main(String[] args) {
//下边就是数组类型的协变,感觉有点像是上转型
Number[] number = new Integer[];
number[] = ;
System.out.println(number[]);
}
3:数组不支持泛型,作为对比,容器支持泛型但不支持协变(不包括通配符)
在这里 说一下 Java数组的特殊性,也是Java数组为什么敢使用协变的原因:
数组记得它内部元素的具体类型,并且会在运行时做类型检查。虽然向上转型以后,编译期类型检查放松了,但因为数组运行时对内部元素类型看得紧,不匹配的类型还是插不进去的.
这也是为什么容器Collection不能设计成协变的原因。Collection不做运行时类型检查,比较耿直。所以容器是不支持协变的(当然,引入通配符之后这可以解决这一问题,我们待会在说)
java数组在创建的时候必须知道内部元素的类型,而且会一直记得类型信息。每次往数组内添加元素都会做类型检查
Java泛型是用擦除(Erasure)实现的,运行时类型参数会被擦掉。
List<String> l = new ArrayList<String>();
l.add("hello");
String str=l.get(0)
//这里简单说一下擦除,上边是编译器的代码,运行时,泛型会被擦除
而且 ,java中数组明确规定
Java Language Specification明确规定:数组内的元素必须是“物化”的,对“物化”的第一条定义就是不能是泛型。
在这里,我从知乎上找到了一个反编译Array的例子。
String[] s=new String[]{"hello"};
int[] i=new int[]{1,2,3};
//Array的具体实现是在虚拟机层面,嵌地非常深,也查不到源码
//只能用javap反编译看看具体初始化数组的字节码
下面是具体的反编译的字节码: 看注释说明,创建int数组和String数组的指令都不一样,换句话说,数组是Java中的特例,它嵌在虚拟机层面,从底层就决定了不支持泛型
Code:
0: iconst_1
1: anewarray #2 // class java/lang/String
4: dup
5: iconst_0
6: ldc #3 // String hello
8: aastore
9: astore_1
10: iconst_3
11: newarray int
13: dup
14: iconst_0
15: iconst_1
... ...
4:泛型中的协变(带通配符的泛型)
看来协变的概念就应该很清楚的知道,泛型是不支持协变的。List<integer> 并不是 List<Number>的儿子。编译期就会报错,如下截图

于是,java设计者引入了通配符的概念,用于在泛型中提供协变这一功能。比如 我们希望有一个方法,它即可以接受宠物狗列表,也可以接受田园犬(是宠物狗的子类)列表,
于是我们引入协变。
package com.generic; import java.util.ArrayList;
import java.util.List; public class PetShow { public void run(List<? extends ChongWuGou> dogs){
System.out.println("running");
}
public static void main(String [] args){
List<ChongWuGou> cDogs = new ArrayList<ChongWuGou>();
List<TianYuanQuan> tDogs = new ArrayList<TianYuanQuan>();
new PetShow().run(tDogs);//该方法可以正确运行
}
}
//宠物狗
class ChongWuGou{ }
class TianYuanQuan extends ChongWuGou{ }
二:逆变
在Java中不允许将父类变量赋值给子类变量。泛型自然也不支持逆变。但是在泛型中可以通过通配符进行模拟(第六节:协变和逆变 )
public class Test
{
public static void main(String[] args)
{
List<? super Integer> list = new ArrayList<Number>();
}
}
? super Integer的含义是:支持Integer的父类,也包括Integer类,作为泛型的参数。
Java 逆变与协变的名词说明的更多相关文章
- Java 逆变与协变
最近一直忙于学习模电.数电,搞得头晕脑胀,难得今天晚上挤出一些时间来分析一下Java中的逆变.协变.Java早于C#引入逆变.协变,两者在与C#稍有不同,Java中的逆变.协变引入早于C#,故在形式没 ...
- Java中的逆变与协变
看下面一段代码 Number num = new Integer(1); ArrayList<Number> list = new ArrayList<Integer>(); ...
- Java中的逆变与协变(转)
看下面一段代码 Number num = new Integer(1); ArrayList<Number> list = new ArrayList<Integer>(); ...
- Java中的逆变与协变 很直接不饶弯的讲出来了
```java 协变 extends只能new 辈分比自己低的家伙 List<? extends Number> list001 = new ArrayList<Integer> ...
- Java中的逆变与协变 专题
结论先行: PECS总结: 要从泛型类取数据时,用extends: 协变 要往泛型类写数据时,用super: 逆变 既要取又要写,就不用通配符(即extends与super都不用) 不变 List&l ...
- Java逆变(Covariant)和协变(Contravariant)
1. 定义 逆变和协变描述的经过类型变换后的类型之间的关系.假如A和B表示类型,f表示类型变换,A ≤B表示A是B的子类型,那么 如果A ≤B,f(A) ≤f(B),那么f是协变 如果A ≤B,f(B ...
- C# 逆变与协变
该文章中使用了较多的 委托delegate和Lambda表达式,如果你并不熟悉这些,请查看我的文章<委托与匿名委托>.<匿名委托与Lambda表达式>以便帮你建立完整的知识体系 ...
- .NET 4.0中的泛型逆变和协变
转载自:http://www.cnblogs.com/Ninputer/archive/2008/11/22/generic_covariant.html:自己加了一些理解 随Visual Studi ...
- .NET Core CSharp初级篇 1-8泛型、逆变与协变
.NET Core CSharp初级篇 1-8 本节内容为泛型 为什么需要泛型 泛型是一个非常有趣的东西,他的出现对于减少代码复用率有了很大的帮助.比如说遇到两个模块的功能非常相似,只是一个是处理in ...
随机推荐
- php实现题目抢答、商品秒杀等类型的需求
最近和其他部门合作项目,当然我是负责php接口方面的工作,get到一些东西,所以来分享记录一下. 项目需求: 题目将通过主持人ipad投射至大屏幕,选手按'抢答'按钮进行抢答.抢答成功,选手所在组,以 ...
- 移动端web解决方案
范畴 移动端web浏览器.至少需要适配的,UC,QQ,各手机内置浏览器,chrome,safari. 是不是觉得和PC端差不多?错了!每款同一版本ios的内置浏览器一样.但每款同一版本android的 ...
- 使用HAL库函数建立STM32F2工程
一.建立一个新的文件夹,新建三个文件夹User.Driver.Project,还可以加一个Doc存放工程的相关说明等等. 1.将STM32F2xx的库函数(HAL函数库)中的Drivers文件夹中的所 ...
- 与我们息息相关的internet服务(1)---域名服务
在起步一个公司,从组建的技术上,可能要准备很多东西,其中一个就是我们熟悉的域名-----域名可以用在邮箱中.网站中等等 域名,听起来很怪的一个词(他类似于电话本的作用),但理解起来很简单,想法也很简单 ...
- Docker化tomcat 并且使用maven热部署
今天没事干 就把自己的tomcat docker化并且使用maven实现热部署 特别记录一下~ 1.首先进行centos7的安装 就不在记录了 注意的是最好使用centos7 因为docker 依赖 ...
- 创建单页web app, 如何在chrome中隐藏工具栏 地址栏 标签栏?
问题描述: 为使用更大的屏幕空间,在访问web应用的使用,如何隐藏地址栏.工具栏? 解决办法: 1. chrome的application mode 选项--->更多工具---->添加到桌 ...
- 关于jQuery表单下拉selected设置无效的解决方案
表单下拉选项使用selected设置,发现第一次默认选中成功,在页面不刷新的情况下,再次下拉,selected属性设置了,默认选中不生效 研究发现是浏览器兼容的问题 一,解决浏览器缓存问题,在HTML ...
- v9手机版文章内容不显示
方法一: 打开PHPCMS v9的/phpcms/templates/default/wap/show.html页面, 将网页中的{$content}替换为:{$rs['content']} 这样wa ...
- 解决运行pytorch程序多线程问题
当我使用pycharm运行 (https://github.com/Joyce94/cnn-text-classification-pytorch ) pytorch程序的时候,在Linux服务器 ...
- 7.如何发布vue项目到服务器
1.确保程序是可运行的,即npm run dev可以运行 2.把index.js修改 3.运行npm命令npm run build 4.生成的dist文件为 直接点击index.html就能运行,部署 ...