最近在研究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 逆变与协变的名词说明的更多相关文章

  1. Java 逆变与协变

    最近一直忙于学习模电.数电,搞得头晕脑胀,难得今天晚上挤出一些时间来分析一下Java中的逆变.协变.Java早于C#引入逆变.协变,两者在与C#稍有不同,Java中的逆变.协变引入早于C#,故在形式没 ...

  2. Java中的逆变与协变

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

  3. Java中的逆变与协变(转)

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

  4. Java中的逆变与协变 很直接不饶弯的讲出来了

    ```java 协变 extends只能new 辈分比自己低的家伙 List<? extends Number> list001 = new ArrayList<Integer> ...

  5. Java中的逆变与协变 专题

    结论先行: PECS总结: 要从泛型类取数据时,用extends: 协变 要往泛型类写数据时,用super: 逆变 既要取又要写,就不用通配符(即extends与super都不用) 不变 List&l ...

  6. Java逆变(Covariant)和协变(Contravariant)

    1. 定义 逆变和协变描述的经过类型变换后的类型之间的关系.假如A和B表示类型,f表示类型变换,A ≤B表示A是B的子类型,那么 如果A ≤B,f(A) ≤f(B),那么f是协变 如果A ≤B,f(B ...

  7. C# 逆变与协变

    该文章中使用了较多的 委托delegate和Lambda表达式,如果你并不熟悉这些,请查看我的文章<委托与匿名委托>.<匿名委托与Lambda表达式>以便帮你建立完整的知识体系 ...

  8. .NET 4.0中的泛型逆变和协变

    转载自:http://www.cnblogs.com/Ninputer/archive/2008/11/22/generic_covariant.html:自己加了一些理解 随Visual Studi ...

  9. .NET Core CSharp初级篇 1-8泛型、逆变与协变

    .NET Core CSharp初级篇 1-8 本节内容为泛型 为什么需要泛型 泛型是一个非常有趣的东西,他的出现对于减少代码复用率有了很大的帮助.比如说遇到两个模块的功能非常相似,只是一个是处理in ...

随机推荐

  1. PC端网页的基本构成

    首先,一个前端最基本的就是排网页,有人会看不起拍页面,认为不就是排一个页面嘛,有啥的,分分钟的事,可是他不知道的是,一个网页中也包含了很多内容,像我们如果不理解margin,padding,会经常对我 ...

  2. VueJs生产环境部署

    VueJs为客户端语言,所以部署的时候是不需要基于nodejs或其他服务器运行环境,只需要像其他静态站点的方式发布就可以了,下面介绍一下VueJs具体发布的流程还有需要注意的点. 先来看VueJs最终 ...

  3. CentOS下SparkR安装部署:hadoop2.7.3+spark2.0.0+scale2.11.8+hive2.1.0

    注:之前本人写了一篇SparkR的安装部署文章:SparkR安装部署及数据分析实例,当时SparkR项目还没正式入主Spark,需要自己下载SparkR安装包,但现在spark已经支持R接口,so更新 ...

  4. iOS11和机器学习CoreML库

    随着iOS11的发布,苹果公司也正式加入了机器学习的战场.在新的iOS11中内置了CoreML,虽然还是Beta版本,但是功能已经非常强大了. 在这个CoreML库里面,已经集成了一些训练好的模型,可 ...

  5. linux切换g++

    sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.4 50(优先级) sudo update-alternative ...

  6. linux-ubuntu下fastQC的安装

    1.fastqc是在Java环境下运行的:所以在安装fastqc之前,Linux下要有相应的Java运行环境(JRE).且java的版本应该在1.8.0版以上 2.java的安装:下载最新版本的Jav ...

  7. 自己整理的openresty安装步骤

    这几天一直在研究对webapi的限流和名单的问题,于是看了开涛博客的方案,于是就用到了openresty,一个把Nginx和lua集成的东西. 下面就是整理的安装方案(简单使用基本可以这么安装) 下载 ...

  8. lvs之 lvs原理架构介绍

    一. 概念 lvs的术语: Router:GWIP vs:virtual server,director rs:real server CIP:client IP VIP:virtual server ...

  9. Error creating bean with name 'signController': Injection of autowired dependencies failed

    出现了一大串错误,Error creating bean with name 'signController': Injection of autowired dependencies failed. ...

  10. 解决arcgis javascript textsymbol不支持多行文本标注的问题

    首先,下载这段js文件,命名为esri.symbol.MultiLineTextSymbol.js require(["esri/layers/LabelLayer"], func ...