Java与Scala的协变与逆变
来源:互联网 发布:后盾网 mysql视频教程 编辑:程序博客网 时间:2024/05/29 17:23
Java与Scala的协变与逆变
一、 概念介绍
在Java与Scala中都支持协变、逆变与非转化。考虑一种场景,一个方法的参数类型为List[AnyVal],那我传入List[Int]是否符合要求呢?即List[Int]是否为List[AnyVal]的了类呢?如果是,这种转化则称为协变,如果List[Int]是List[AnyVal]的父类,则这种转化称为逆变。协变与逆变是里氏替换原则的一种表现,该规则适用于子类行为,而非超类行为,说白了,协变/逆变是为了在不同的场景更好的描述类的继承关系, 如下图:
为了清楚的说明,看如下例子:
object CovAndContra { def main(args : Array[String]): Unit ={ var leo1 : CovAndContra[A] = new CovAndContra[A](new A) var leo2 : CovAndContra[B] = new CovAndContra[B](new B) var leo3 : CovAndContra[C] = new CovAndContra[C](new C) leo1 = leo2 leo3 = leo2 //编译错误 }}class A {}class B extends A{}class C extends B{}class CovAndContra[+A]( c : A){}
类A<-B<-C依次为继承关系,如果定义CovAndContra 是协变的,则leo*的继承关系为leo1<-leo2<-leo3。当CovAndContra是逆变的,即CovAndContra[-A]则leo*的继承关系为leo3<-leo2<-leo1。注:在Java中参数化类型在定义时并不支持继承转化行为,说白了,就是Java不能在定义一个类的时候使用协变/逆变的特性。类型的转化标记说明:
协变相对好理解,下面以一个Java的例子来说明在什么情况下使用逆变。
public class JavaCovAndContra<T> { public static void main(String[] args){ List<JA> leo1 = new ArrayList<>(); List<JB> leo2 = new ArrayList<>(); List<JC> leo3 = new ArrayList<>(); JavaCovAndContra<JB> leo = new JavaCovAndContra<>(); leo.pull(leo1); leo.pull(leo2); leo.push(leo2); leo.push(leo3); leo.pull(leo3); //报错 leo.push(leo1); //报错 } private List<T> arr = new ArrayList<>(); public void push(List<? extends T> list){ for(T item : list){ arr.add(item); } } public void pull(List<? super T> list){ for(T item : arr){ list.add(item); } }}class JA {}class JB extends JA {}class JC extends JB {}
二、 协变/逆变与函数的参数
在《Programming in Scala》中有这样两句话,“1、变异标记只有在类型声明中的类型参数里才有意义,对参数化的方法没有意义,因为该标记影响的是子类继承行为,而方法没有了类。2、函数的参数必须是逆变的,而返回值必须是协变的。”小编感觉,把这两句话理解透彻就可以很好的掌握协变/逆变的特性了。先说第一句,如第一个小节里所说,协变/逆变只使用在类型参数中,因此在方法在使用标记是错误的。如下的方法声明是不允许的。
Scala : def test(a : +A) : Unit = ???Java : public void test(? extends T list)
而一个疑问是Java的代码中public void push(List
class A {}class B extends A{}class C extends B{}//类一class CovAndContra[+T]( c : T){ def getValue : T = ??? def setValue(value : T) : Unit = ??? //编译报错}//类二class CotraAndCov[-T]( c : T){ def getValue : T = ??? //编译报错 def setValue(value : T) : Unit = ???}
如上,两个例子,类一与类二分别为协变与逆变的,两个类分别实现了两个方法,并分别有一个方法在编译时是报错的。我们假设两个类都可以通过编译,下面来看看,这会导致怎样的问题。声明一个对象为x= CovAndContra[B] 这时T=B,再声明一个对象为y= CovAndContra[C] 这时T=B,由于CovAndContra是协变的,因此可以做x=y这样的赋值,这时x的类型是不变的为CovAndContra[B],而其实际指向的类型为CovAndContra[C],这时我们调用getValue类型,返回B类型,实际返回的是C类型,由于B<-C,因此是符合逻辑的,而当我们调用setValue类型时,我们想处理B类型,而实现调用的方法只能处理C类型,所以这时就会有问题。再详细一下,B类型为一个方法为funB,而C类型除了实现funB外,又添加了funC方法,对于y= CovAndContra[C]这个对象,我们调用y.setValue时,传入的是类型C,存在funC方法,而当我们把y赋值给x时,由于我们调用x.setValue时传入B类型就可以,而setValue的实现处理方法是CovAndContra[C]这个类实现的,如果setValue方法中调用了funC方法,这时就会出问题了。
三、 类型的上界,下界
在Java中,类型的上下界通过extends与super来实现,可以通过如下方法定义类型。
class JA<T extends String> {}
而scala更灵活,可以对方法指定参数的输入类型范围(java是否可以,这个不太了解,应该是不可以的),方法定义如下:
def setValue[X <: String](value : X) : Unit = ???
对于协变/逆变的+-与<: >:符号的区别,个人认为协逆变的-+是继承的标识,以在编译时告诉编译器该类型的协逆变的,当然也可以确定类型的边界,而>: <:只是用于确定范围的。即如果我们这样定义类型:
class CovAndContra[T <: A]( c : T)var leo1 : CovAndContra[A] = new CovAndContra[A](new A)var leo2 : CovAndContra[B] = new CovAndContra[B](new B)leo1 = leo2 //报错
当做leo2到leo1的赋值时,会有报错.
- Java与Scala的协变与逆变
- scala的协变与逆变、上界与下界
- scala入门:逆变与协变
- scala协变与逆变
- Scala教程(十五)Scala的特性逆变与协变
- scala类型系统:15) 协变与逆变
- scala类型系统:15) 协变与逆变
- Scala语法中的协变与逆变
- scala类型系统:协变与逆变
- Java中的逆变与协变
- Java中的逆变与协变
- Java中的逆变与协变
- Java中的逆变与协变
- Java中的逆变与协变
- Java中的逆变与协变
- Java 逆变与协变
- java中的协变与逆变
- scala中协变与逆变的理解
- 用数据可视化直观理解数据--iris数据集为例
- 数据库学习记录
- 查看MySQL数据库大小
- webstorm安装
- ATM取款项目
- Java与Scala的协变与逆变
- js获取页面中的鼠标滚轮事件
- SDL 2 游戏编程(四)按键处理
- Spring 之 Bean
- js 交换赋值我认为最简单的方法 (ES6解构)
- 查看mysql语句运行时间
- 斐波纳契数列问题
- Linux平台Java调用so库-JNI使用例子
- 蓝桥杯历届 煤球数目