型变(协变与逆变)
今天理清了一下关于java与scala中的型变这个概念, 比较一下这两个JVM语言是如何对待这个问题的
Java中的协变与逆变
static class Person {
public void hi() {
System.out.println("hi");
}
}
static class Student extends Person {
public void hello() {
System.out.println("hello");
}
}
数组中的协变
Student[] students = new Student[2];
Person[] peoples = students;//发生协变
peoples[0] = new Student();
peoples[1] = new Person();//运行异常 ArrayStoreException
使用通配符处理协变逆变
//首先强调一下这个编译异常,泛型是不支持型变,如下会出现type不相容
//List<Person> list3 = new ArrayList<Student>();//incompatible error
//逆变
List<? super Student> list = new ArrayList<Person>();
list.add(new Student());
//list.get(0).hi(); compile error
//协变
List<? extends Person> list2 = new ArrayList<Student>(){{
add(new Student());
}};
//list2.get(0).hello();//compile error
list2.get(0).hi();
从上面例子可以看出来: 方法的形参是逆变的、返回值是协变的
- 要从泛型类取数据时,用extends, 发生协变
- 要往泛型类写数据时,用super, 发生逆变
- 既要取又要写,就不用通配符
我们可以看出java在这一方面做不是很好,在看看scala中的使用
scala中的协变与逆变
class Person {
def hi(): Unit = {
println("hi person")
}
override def toString: String = "Person\n"
}
class Student extends Person {
def hello(): Unit = {
println("hello")
}
override def hi(): Unit = {
println("hi student")
}
override def toString: String = "Student\n"
}
协变点在返回值,Person
是Student
的父类,则Function1[Student, Person]
是Function1[Student, Student]
的父类,这就是协变
// Person> Student
//Function1[Student, Person] > Function1[Student, Student]
val fun = new Function1[Student, Student] {
def apply(x: Student): Student = {
//return 发生协变, 实际return 更具体
x
}
}
def test(fun: Function1[Student, Person], stu: Student) {
print(fun.apply(stu)) //Student
}
test(fun, new Student)
//简写如下:
def test2(fun: Student => Person, stu: Student) {
print(fun(stu))
}
test2(x => x, new Student)
逆变点在形参,Person
是Student
的父类,而Function1[Student, Person]
是Function1[Person, Person]
的父类,这就是逆变
//Function1[Student, Person] > Function1[Person, Person]
val fun1 = new Function1[Person, Person] {
def apply(x: Person): Person = { //x 发生逆变, 实际传入只会更具体
x
}
}
def test1(fun: Function1[Student, Person], stu: Student) {
print(fun.apply(stu))//Student
}
// def test1(fun: Student => Person, stu: Student) {
// print(fun(stu))
// }
test1(fun1, new Student)
Function1[-T1,+R]
是scala中的trait,-
表示逆变,+
表示协变,可以看出scala在这方面做得非常优秀
为什么需要协变与逆变?
里氏代换原则: 任何使用基类的地方,都能够使用子类替换,而且在替换子类后,系统能够正常工作
,这里就是要遵循这个原则,协变比较容易理解,逆变有点拗,Function1[Student, Person]
是父类,Function1[Person, Person]
的子类,所有父类都可以被子类替代,如方法:test1(fun: Function1[Student, Person], stu: Student)
或者test1(fun: Function1[Teacher, Person], t: Teacher)
,巧妙的泛化了参数,使方法更抽象,减少重复代码
scala中型变点
如何确定型变点,笼统的说方法的形参是逆变的、返回值是协变的
,但是如果形参中是一个方法呢?看下面的例子的注释:
trait Fun[+T1, -T2, +R] {
def apply(fun: Function1[T1, T2], param: T2): R
}
val f = new Fun[Person, Student, Student] {
// 协变点确定
//( - ) => (+) default
//( - , - ) => (+)
//(-( (-) => (+) ), - ) => (+)
//( (+) => (-) , - ) => (+)
override def apply(fun: Person => Student, stu: Student): Student = {
println("fun1")
fun(stu)
}
}
println("fun return => " + f((stu: Person) => {
println("fun2")
stu.hi()
stu.asInstanceOf[Student]
}, new Student))
执行结果
fun1
fun2
hi student
fun return => Student
参考
- Java中的逆变与协变
- 快学scala