关于kotlin:kotlin语言中的out和in

6次阅读

共计 5026 个字符,预计需要花费 13 分钟才能阅读完成。

ps:浏览原文,能够获取源码

在 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)

本篇文章写到这里就完结了,因为技术水平无限,文章中难免会有谬误,欢送大家批评指正。

正文完
 0