协变逆变
B是A的子类,A是B的父类。 当我们定义一个协变类型List[+A]时,List[Child]可以是List[Parent]的子类型。 当我们定义一个逆变类型List[-A]时,List[Child]可以是List[Parent]的父类型。
// java中数组是支持协变的 Number num1 = new Integer(0); Number[] num2 = new Integer[10];而java泛型既不支持协变也不支持逆变
List<Object> list = null; //new ArrayList<String>();//编译错误 list = new ArrayList<Object>();
但可以通过通配符?实现
List<? extends Object> list2 = new ArrayList<String>();
下面给出一个具体的例子
List<? extends Object> covariantList = aList; List<? super String> contravariantList = aList; // covariantList.add("b"); //wrong Object a = covariantList.get(0); contravariantList.add("a"); //OK // String b = contravariantList.get(1); //wrong Object c = contravariantList.get(0);
你可以调用covariantList
所有的不需要泛型参数的方法,因为泛型参数必须 extends Object, 但是编译时你不知道它确切的类型。但是你可以调用getter方法,因为返回类型总是符合Object类型。
contravariantList
正好相反,你可以调用所有的带泛型参数的方法,因为你明确的可以传入一个String的父类。但是getter方法却不行。
综上,java对协变,逆变的支持还不够强大
scala 较java最大的优势在于其函数式编程
为我们定义了强大的trait FunctionN
如
trait Function1[ -T1, +R] extends AnyRef
特质声明中参数T1是逆变的,返回值R是协变的
为了加深对协变逆变的理解 ,构造3个类
class CSuper {} class C extends CSuper {} class Csub extends C {}
定义函数
// trait Function1[ -T1, +R] extends AnyRef var f: C => C = (c: C) => new C
参数C对比于-T1,返回值C 对比于+R
所以参数c应该可以看做C的父类,即对于
C => C
的接口声明参数只能是C或Csuper
而返回值C是协变的
只能是C或Csub
var f: C => C = (c: C) => new C f = (c: CSuper) => new Csub f=(c:C)=>new CSuper //error f=(c:Csub)=>new C //error