最近在研究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. javaWeb学习总结(10)- EL表达式

    一.EL表达式简介 EL 全名为Expression Language.EL主要作用: 1.获取数据 EL表达式主要用于替换JSP页面中的脚本表达式,以从各种类型的web域 中检索java对象.获取数 ...

  2. java虚拟机学习-JVM调优总结-垃圾回收面临的问题(8)

    如何区分垃圾 上面说到的“引用计数”法,通过统计控制生成对象和删除对象时的引用数来判断.垃圾回收程序收集计数为0的对象即可.但是这种方法无法解决循环引用.所以,后来实现的垃圾判断算法中,都是从程序运行 ...

  3. 三、 添加视图View(ASP.NET MVC5 系列)

    在这一章节我们可以修改HelloWorldController类,通过使用视图模板来封装处理产生给客户端的HTML响应. 我们将使用Razor View engine来创建视图文件.基于Razor的视 ...

  4. 抱歉,您必须拥有一个终端来执行 sudo

    Linux ssh执行远端服务器sudo命令时有如下报错: sudo: sorry, you must have a tty to run sudo sudo:抱歉,您必须拥有一个终端来执行 sudo ...

  5. MySQL主从同步报错故障处理集锦

    前言 在发生故障切换后,经常遇到的问题就是同步报错,下面是最近收集的报错信息. 记录删除失败 在master上删除一条记录,而slave上找不到 Last_SQL_Error: Could not e ...

  6. ios runtime 打印内 内部调用的属性

    unsigned int count = 0; // 拷贝出所有的成员变量列表 Ivar *ivars = class_copyIvarList([UITextField class], &c ...

  7. ArrayList源码解析(四)

    这篇文章主要看ArrayList的Iterator和ListIterator的实现. 1.Iterator和类Itr 当我们调用iterator方法时返回一个Iterator. /** * Retur ...

  8. spring和hibernate的整合

    阅读目录 一.概述 二.整合步骤 1.大致步骤 2.具体分析 一.概述 Spring整合Hibernate有什么好处? 1.由IOC容器来管理Hibernate的SessionFactory 2.让H ...

  9. 微信JS-SDK开发 入门指南

    目录 前言 1. 过程 1.1 代码 1.2 代理 1.3 下载 1.4 解压 1.5 运行 1.6 查看 2. 微信接口测试 2.1 申请测试帐号 2.1.1 测试号信息 2.1.2 接口配置信息 ...

  10. Ajax&jQuery教案总结

    Ajax&jQuery教程总结 目录 第一章 Ajax入门 6 第1讲 传统表单提交存在的问题 6 课程内容 6 1. 问题的引入 6 2. 问题的解决 6 参考进度(0.5课时) 7 第2讲 ...