kotlin语言中的out和in

在 kotlin 语言中,out 表示协变,in 表示逆变;协变和逆变并不是 kotlin 独有的概念,像 Java、C#都有这样的概念;为了能够理解 kotlin 语言中的 out 和 in,我们先用 Java 的泛型来举例,我们需要用泛型,是因为它的好处就是在编译的时候能够检查类型安全,并且所有的强制转换都是自动和隐式的。

1、Java 中的 ? extends T 和 ? super T

1、1 ? extends T

ps:代码是在 AndroidStudio 工具上写的

建立一个 Java 文件鸟类 Birds;

public class Birds {
private String name;
public Birds(String name) {
this.name = name;
}
public void flight() {
System.out.println("我是" + name + ",属于鸟类,我能飞行");
}
}

建立一个 Java 文件乌鸦类 Crow 并继承 Birds;

public class Crow extends Birds {
public Crow(String name) {
super(name);
}
}

新建一个 Java 文件的泛型类 TestBirds 并限制泛型 T 是 Birds 的子类;

public class TestBirds<T extends Birds> {
public void actionBirds(List<T> birds) {
for (T bird : birds) {
bird.flight();
}
}
}

在程序入口尝试使用 List<Crow> list 作为参数传递给 TestBirds 的 actionBirds 方法;

 List<Crow> list = new ArrayList<>();
TestBirds<Birds> testBirds = new TestBirds<>();
Crow crow = new Crow("乌鸦");
list.add(crow);
/**
* 这里 list 地方会编译报红线
*/
testBirds.actionBirds(list);

这时候传入 list 发现编译不通过,我们这里分析一下:TestBirds 是泛型类,没有使用的之前,T 是不确定的,使用之后 T 是确定的,它是 Birds;把 list 作为参数传入 actionBirds 方法中,等同于 List<Birds> birds = list,但是 List<Birds> birds = list 是不成立的,虽然 Crow 继承于 Birds,birds 只保存的是 Birds 类型的对象,list 只保存 Crow 类型的对象,birds 和 list 是没有任何关系的。

TestBirds 类中新添加一个 actionBirds2 方法,该方法是在 actionBirds 方法的基础上修改的;

 public void actionBirds2(List<? extends T> birds) {
for (T bird : birds) {
bird.flight();
}
}

在程序入口使用 List<Crow> list 作为参数传递给 TestBirds 的 actionBirds2 方法;

 List<Crow> list = new ArrayList<>();
TestBirds<Birds> testBirds = new TestBirds<>();
Crow crow = new Crow("乌鸦");
list.add(crow);
/**
* 这里 list 地方会编译报红线
*/
// testBirds.actionBirds(list);
testBirds.actionBirds2(list);

这时候发现 testBirds.actionBirds2(list) 这行代码编译通过了,是不是感觉很神奇?这里分析一下:把 list 作为参数传入 actionBirds2 方法相等于 List<? extends Birds> birds = list,它是成立的,List<? extends Birds> birds ,表示集合存储的是 Birds 和 Birds 的子类对象,限定了上届,而 list 存储的是 Birds 子类的对象,所以代码编译通过,它是成立的;上界通配符 < ? extends T>,用 extends 关键字声明,表示参数可能是T,或者是T的子类。

1、2 ? super T

在上面原有代码的基础上,在 TestBirds 类中添加一个 actionBirds3 方法;

 public void actionBirds3(List<T> birds,List<T> crows) {
for (T crow : crows) {
birds.add(crow);
}
}

在程序入口尝试调用 TestBirds 的 actionBirds3 方法;

 List<Crow> list = new ArrayList<>();
TestBirds<Crow> testBirds = new TestBirds<>();
Crow crow = new Crow("乌鸦");
list.add(crow);
List<Birds> birdsList = new ArrayList<>();
testBirds.actionBirds3(birdsList,list);

到这里后,发现 actionBirds3 方法的第一个参数就报红色编译错误了,原因是实例化 TestBirds 类对象的时候,T 被 Crow 替换了,birdsList 只存储 Birds 类型的数据,而 actionBirds3 方法的第一个参数只存储 Crow 类型的数据,所以2者没有任何关系,所以语法编译错误。

我们在 TestBirds 中新添加一个 actionBirds4 方法,在 actionBirds3 的基础上改动一下第一个参数;

 public void actionBirds4(List<? super T> birds,List<T> crows) {
for (T crow : crows) {
birds.add(crow);
}
}

在实参不变的情况下,在程序入口调用 TestBirds 的 actionBirds4 方法;

 List<Crow> list = new ArrayList<>();
TestBirds<Crow> testBirds = new TestBirds<>();
Crow crow = new Crow("乌鸦");
list.add(crow);
List<Birds> birdsList = new ArrayList<>();
/**
* 这里 birds 地方会编译报红线
*/
// testBirds.actionBirds3(birds,list);
testBirds.actionBirds4(birdsList,list);

这时候发现调用 TestBirds 中的 actionBirds4 方法编译通过,分析一下:actionBirds4 方法的第一个参数为 List<? super T> birds,? super T 是下限通配符,它表示在使用中限定参数类型为 T 或者是 T 的父类,birds 存储的是 T 类型和 T 父类的对象;在实例化 TestBirds 的过程中,把 Crow 替换成了 T,Birds 刚好是 Crow 的父类,调用 TestBirds 的 actionBirds4 方法传递第一个参数时相当于 List<? super Crow> birds = list,所以编译通过。

2、kotlin 中的 out 和 in

2、1 out

在上面 ? extends T 的代码案例中,TestBirds 类中的 actionBirds2 方法的第一个参数 birds 集合加了 ? extends T 进行限制,然后用 for 循环遍历 T 的元素进行取出来,这样的操作是读取;? extends T 限定了通配符类型的上界,所以我们可以安全地从其中读取却不可以修改数据;我们可以把那些只能从中读取的对象称为生产者;List<? extends T> 这样的类型不进行消费的生产者,以保证类型运行的安全,这就是协变;在 kotlin 中用 out 表示,kotlin 中的 “out T” 等同于 Java 的 “?extends T”;下面用 kotlin 的 out 关键字举个例子:

新建一个 kotlin 类 TestBirds2 并写一个和 TestBirds 类 actionBirds2 效果一样的函数;

class TestBirds2<T: Birds> {
fun actionBirds2(birds: MutableList<out T>) {
for (bird: T in birds) {
bird.flight()
}
}
}

在程序入口调用 TestBirds2 中的 actionBirds2 函数;

 var testBirds2: TestBirds2<Birds> = TestBirds2<Birds>()
var crow: Crow = Crow("乌鸦")
var crowList: MutableList<Crow> = mutableListOf(crow)
testBirds2.actionBirds2(crowList)

2、2 in

在上面 ? super T 的代码案例中,TestBirds 类中的 actionBirds4 方法的第一个参数 birds 集合加了 ? super T 进行限制,然后用 for 循环遍历第二个参数crows 的 T 元素进行取出来,再将 T 元素放入到 birds 集合中;? super T 限定了通配符类型的下界,所以我们可以安全地从其中修改数据,也就是将 T 元素放入到 birds 集合中;我们可以把那些只能从中修改的对象称为消费者;List<? super T> 这样的类型获取出来的数据类型是 Object,没有意义,可认为不进行生产的消费者,以保证类型运行的安全,这就是逆变;在 kotlin 中用 in 表示,kotlin 中的 “in T” 等同于 Java 的 “?super T”;下面用 kotlin 的 in 关键字举个例子:

在 TestBirds2 类中写一个和 TestBirds 类中 actionBirds4 方法效果一样的函数;

 fun actionBirds4(birds: MutableList<in T>,crow: MutableList<T>) {
for (t: T in crow) {
birds.add(t)
}
}

在程序入口调用 TestBirds2 中的 actionBirds4函数;

 var testBirds2: TestBirds2<Crow> = TestBirds2<Crow>()
var crow: Crow = Crow("乌鸦")
var crowList: MutableList<Crow> = mutableListOf(crow)
var birdsList: MutableList<Birds> = mutableListOf()
testBirds2.actionBirds4(birdsList,crowList)

2、3 类型投影

上面的 out 和 in 的例子使用起来还是有限制,因为有 T 继承于 Birds 的局限;这里讲一下类型投影,在讲类型投影之前先说一下 Any,Any 是 kotlin 语言的祖宗类,类似于 Java 中的 Object,但是又不是等于 Object,因为 Any 只有 equals、hashCode 和 toString 这3个函数;将一个类声明为泛型类,泛型类型可以出现在 out 位置,也可以出现在 in 位置,我们就可以在使用处将其声明成协变或者逆变,就等于把这个类型投影出某一面进行使用,就属于类型投影;就拿泛型类 MutableList<T> 来说,真正要实例化 MutableList 的时候,T 的位置可以多添加 in 或者 out, 把这个类型投影出某一面进行使用,也就是 MutableList 的读取数据方法 get 或者写入数据方法 add;下面就拿 MutableList 写代码举例一下:

 var mutableList: MutableList<out Any> = mutableListOf("公众号小二玩编程",2,3,4,5)
var size: Int = mutableList.size - 1
var any: Any? = null
for (i: Int in 0 .. size) {
any = mutableList.get(i)
println("第" + (i + 1) + "any是--" + any)
}
var mutableList2: MutableList<in String> = mutableListOf()
mutableList2.add("公众号小二玩编程")
/**
* 这里 Int 类型,编译会报错,因为 mutableList2 做了 in String 限制
*/
mutableList2.add(2)

本篇文章写到这里就结束了,由于技术水平有限,文章中难免会有错误,欢迎大家批评指正。

猜你喜欢

转载自blog.csdn.net/liujun3512159/article/details/127815821