最近在研究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
*/
数组支持协变, 比如 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[10]; number[0] = 1; System.out.println(number[0]); }
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的例子。