Java 都是协变的原因

93 阅读2分钟

缘起

今天有同事问以下代码的使用

List<String> list=new ArrayList<>();  
setData(list);  //异常报错:类型错误
setNewData("LLL");//正常使用
public void setData(List<Object> data){  
  
}  
public void setNewData(Object data){  
  
}

思想建立

 String extends Object
  • 里氏替换原则(继承): String 和 Object 单独使用没问题
  • 协变: 换一个使用环境依然遵守继承关系: java 数组是协变的,
  • 逆变:兼容方向发生了转变,传入继承者,可兼容父类
  • 不变:在新的环境关系断绝: list 列表是不变的,泛型是不变的
  • 通配符:<?>,它代表任意类型通配符

原因

 public void setData(List<Object> data)

在方法中为list 泛型 失去了继承关系 为不变,所以在开头的 使用中编译器是不能通过的

java 为什么会这样设计: 保证类型的安全性 List<Object> 有很多子类,防止后续类型取出发生类型转变错误,比如数组未知的情况下就会出现类型转换失败,形成运行时错误;

Object[] objects=new String[3];  
objects[0]=1;  
objects[1]="2";  
int num= (int) objects[1];//运行错误

使用

但在开发过程中我们很多场景是需要类似继承的扩展,那不变的list 和泛型是不是隔绝了协变和逆变? Java 为了满足部分场景提供了? extends? super

  • 消费者(TextView extends View): List<? extends View>List<TextView> 的父类以提供协变的能力,函数的拿到的数据只能取不可修改,增加类型安全
List<TextView> list =new LinkedList<TextView>();  
list.add(new TextView(this));  
setData(list);// 正常使用 

public void setData(List<? extends View> list){  
// list.add(new TextView(this)); // 禁止添加 保证类型安全  
TextView textView= (TextView) list.get(0);  
}
  • 生产者: List<? super TextView>List<View> 的子类提供逆变能力,并可以添加数据
List<View> list1 =new LinkedList<>();  
list1.add(new TextView(this));  
setData1(list1);

public void setData1(List<? super TextView> list){  
list.add(new TextView(this));  
TextView textView= (TextView) list.get(0);  
}