前言

以一个java老鸟的角度,如何去看 kotlin。Java源代码应该如何用Kotlin重构。如何正确学习kotlin并且应用到实际开发中。本文将会探究。

本文分两大块,重难点和潜规则。

重难点:Kotlin中可以独立出来讲解的大块知识点。提供单独Demo。这部分大多数是Kotlin开创的新概念(相比于Java)。

潜规则:Kotlin是谷歌用来替换Java的,它和java百分百完全兼容,但是实际上java转成kotlin之后,需要我们手动修改很多东西,甚至某些部分必须打散重构来达到最优编码。其中,kotlin的某些特性和java不同,甚至完全反转。这部分知识点比较零碎,单独Demo不方便提供,就以小例子的形式来写。

正文大纲

  • 重难点

    • lambda以及操作符

    • 高阶函数以及操作符

    • Kotlin泛型

    • 集合操作

    • 协程

    • 操作符重载

  • 潜规则

    • Kotlin文件和类不存在一对一关系

    • 共生体

    • 继承

    • 修饰符

    • 空指针问题

正文

重难点

Kotlin泛型

类型擦除

我们在编码java的时候,写一个泛型类,可能是这样的

class Plate<T>{    T t;Plate(T t){this.t = t;}    T get(){return t;}void set(T t){this.t =            t;}}

以上是java代码。

Java泛型是伪泛型,在编译之后,所有的泛型写法都会被移除,而会用实际的类型去替换。

mian函数运行的时候, 被移除。而原来的T,就变成了Object。

所以,Plate的字节码反编译过来就应该是

class Plate{    Object t;Plate(Object t){this.t = t;}    Object get(){return t;}void set(Object t){this.t = t;}}

那么既然运行的时候,泛型限制全都没有了。那么怎么保证 泛型的作用呢?

答案:编码的时候,编译器帮我们进行校验。

strList.add(123);//报错

PECS 法则 和 上下边界的问题

public class Panzi<T> {    T mT;public Panzi(T t) { mT = t;}public T get() { return mT; }public void set(T t) {  mT = t; }}class Fruit {}class Banana extends Fruit {}class Apple extends Fruit {}
 Panzi<Apple> applePanzi = new Panzi<>(new Apple()); Panzi<Fruit> fruitPanzi = new Panzi<>(new Fruit()); fruitPanzi = applePanzi;

虽然 Apple和Fruit是父子继承关系。但是Panzi 和 Panzi是半毛钱关系都没有,如果你想fruitPanzi = applePanzi ,把后者赋值给前者,是会报编译错误的。

如果想让装水果的盘子和 装 苹果的盘子发生一点关系,能够后者赋值给前者。

Panzi<Apple> applePanzi = new Panzi<>(new Apple());Panzi extends Fruit> fruitPanzi = new Panzi<>(new Fruit());fruitPanzi = applePanzi;

那就必须使用到上下边界的关键字 extends

extends 在泛型中表示指定上界,也就是说,实际类型都必须在Fruit之下(包括Fruit自己)。那么既然apple也是Fruit的子类,那么赋值就可以做到。

  • PECS法则

刚才说到了上边界 extends。而下边界是 super关键字

Panzi extends Fruit> extendsFruit = new Panzi<>(new Apple());Panzi super Fruit> superFruit = new Panzi<>(new Fruit());

super关键字,在泛型中表示定义下界,实际类型必须在Fruit之上,同时也在Object之下(包括Fruit和Object)

所以会出现这么一个情况:

Panzi extends Fruit> extendsFruit = new Panzi<>(new Apple());Panzi super Fruit> superFruit = new Panzi<>(new Object());

我们有这么两个Panzi,前者是 Fruit作为泛型上界,一个是Fruit作为下界。

现在,我们从Panzi中去调用get/set方法。会发现。

PE:

extendsFruit.set(new Apple()); // 编译报错! Fruit fruit2 = extendsFruit.get();// 编译正常

为何?因为 Fruit作为上界,我get出来的类型可以确定一定是Fruit类型的。但是我set进去的时候,JVM无法判定实际类型(因为泛型被擦除,JVM只人为set(Object t) 的参数是一个Object),JVM要求是Object,但是你却给了一个Apple,编译器无法处理。所以干脆 java的泛型,? extends 定义了上界,只允许get,不允许set。这就是PECS中的PE,意思就是 Pruducer Extends ,生产者 Extends,只取不存。

相对应:

CS:则是 Cunsumer super 消费者只存不取。

Object object = superFruit.get(); //get,get出来虽然不报错,但是没有任何意义。因为不能确定类型,只知道是一个Object,无法调用APIsuperFruit.set(new Fruit());// 但是set进去的时候,可以确定一定是一个Fruit的

这就是java泛型的 PECS法则.

kotlin泛型使用实例

java泛型里面比较纠结的难点就是类型擦除和PECS法则了。

那么kotlin泛型,原理上和java泛型和没有区别。只是写法上有了区别。

open class Fruitclass Apple : Fruit()class Banana : Fruit()class Panzi<T>(t: T) {var t: T = tfun get(): T {return t}fun set(t: T) {this.t = t}}
    fun test1() {// 试试能不能相互赋值var fruitPanzi: Panzi<Fruit> = Panzi(Fruit()) //一个水果盘子var applePanzi: Panzi<Apple> = Panzi(Apple()) //一个苹果盘子//试试相互赋值//        fruitPanzi = applePanzi  // 编译报错//        applePanzi = fruitPanzi  // 编译报错//双方完全是不相干的类,不能相互赋值 ,}/**     * 加边界之后再赋值     */fun test2() {//如果你非要认为苹果盘子归属于水果盘子,那么可以这样        var fruitPanzi2: Panzi<out Fruit> = Panzi(Fruit()) //一个水果盘子var applePanzi2: Panzi<Apple> = Panzi(Apple()) //一个苹果盘子        fruitPanzi2 = applePanzi2  //那么这就是out决定泛型上边界的案例}/**     * PECS法则,OUT表示 ? extends 决定上界,上界生产者只取不存     */fun test3() {//看一下get set方法// 决定上界之后的泛型,只取不存var fruitPanzi2: Panzi<out Fruit> = Panzi(Fruit())        fruitPanzi2.get()//        fruitPanzi2.set(Apple())  // 这里编译报错,和java泛型的表现一样}/**     * PECS法则,IN表示 ? super 决定下界,下界消费者,只存不取     */fun test4() {//试试泛型下界 invar fruitPanzi: Panzi<in Fruit> = Panzi(Fruit())        fruitPanzi.set(Fruit())//可以set,但是看看getval get = fruitPanzi.get()//不会报错,get出来的类型就完全不能确定了,只知道是 顶级类Any? 的子类,获得它也没有意义}

集合操作

Kotlin的集合,并没有重新开创一套规则,它的底层依然是java的Collection。Kotlin提供了可变集合和不可变集合的接口。

  • 不可变集合:List,Set,Map (内部元素不可以 增减 或者 修改,在定义的时候就已经将容量和内部元素定死)

  • 不可变集合: MutableList ,MutableSet,MutableMap(声明的时候可以随意指定初始值,后续可以随意增删和修改内部元素)

集合操作分为:对象的创建和 api的调用

对象的创建

方式有多种,以不可变集合 List 为例,kotlin的List底层和Java的List一致,底层数据结构是数组。

静态指定元素值

fun main() {val listOf = listOf<String>("str1", "str2", "str3")    listOf.forEach { print("$it ") }}

执行结果:

str1 str2 str3

通过动态创建过程来指定元素值

fun main() {val list = List(3) {"str$it"}    list.forEach { print("$it ") }}

执行结果:

str0 str1 str2

api的调用

对象已经创建,我们要利用kotlin提供的方法来完成业务代码。

一级难度api(all,any,count,find,groupBy)
fun testCollectionFun() {val ages = listOf<Int>(1, 2, 3, 4, 5, 6, 7, 100, 200)//那么是不是所有的元素都大于10    ages.apply { print("all:") }.all { it > 10 }.apply { println(this) } //结果是false//是不是存在任意一个元素大于10    ages.apply { print("any:") }.any { it > 10 }.apply { println(this) }// 符合指定条件的元素个数    ages.apply { print("count:") }.count { it < 10 }.apply { println(this) }//找到第一个符合条件的元素    ages.apply { print("find:") }.find { it > 10 }.apply { println(this) }// 按照条件进行分组val groupBy = ages.apply { print("groupBy:") }.groupBy { it > 10 }    groupBy[false].apply { println(this) }    groupBy[true].apply { println(this) }}

fun main() {testCollectionFun()}

针对数组元素的简单判断,上述提供了简明的示例代码,用List为例,至于Set和Map类似。可以自主去推断写法。

执行结果:

all:falseany:truecount:7find:100groupBy:[1, 2, 3, 4, 5, 6, 7][100, 200]
二级难度api (filter,map,flatMap,flatten)
  • filter和map

fun testCollectionFun2() {//二级难度apival ages = listOf<Int>(1, 2, 3, 4, 5, 6, 7, 100, 200)// 只保留大于10的元素,并返回一个新数组    ages.filter { it > 10 }.apply { println(this) }//遍历List的所有元素,根据条件返回值,创建新的元素内容并放到新List中返回出来    ages.map { if (it > 10) "大于10" else "小于等于10" }.apply { println(this) }}fun main() {testCollectionFun2()}

执行结果:

[100, 200][小于等于10, 小于等于10, 小于等于10, 小于等于10, 小于等于10, 小于等于10, 小于等于10, 大于10, 大于10]
  • flatMap,因为稍复杂

// 比如一个学生,作为一个实体class Student(name: String, math: Int, chinese: Int) {val name: String = nameval score = Score(math, chinese)}//学生的成绩分数作为一个主体class Score(math: Int, chinese: Int) {val math: Int = mathval chinese: Int = chinese}fun testFlatMap() {val students = listOf(Student("zero", 100, 80),Student("alvin", 70, 70),Student("lance", 90, 60))//我只想统计所有的数学成绩的总分和平均分,怎么办    students.flatMap { listOf(it.score.math) }.apply {var total = 0this.forEach {            total += it}println("数学成绩的总分是:$total  平均分是:${total / this.size}")}}fun main() {testFlatMap()}

执行结果:

数学成绩的总分是:260  平均分是:86

当面对复杂数据结构时,我们想要提炼出其中的某一层数据,并不关心其他无关字段。就适合使用 flatMap 进行扁平化提炼。

  • flatten

意味:“平铺”

fun testFlatten(){val list = listOf(listOf("Str1","Str3"), listOf("Str4","Str2"))val listsOfList = list.flatten()println("平铺之前:$list \n平铺之后$listsOfList")}fun main() {testFlatten()}

执行结果:

平铺之前:[[Str1, Str3], [Str4, Str2]]平铺之后[Str1, Str3, Str4, Str2]

在存在List嵌套的情况下,flatten可以把复合式的数据结构 变成 扁平式的。它和flatMap不同,flatMap适合在复杂数据结构中在指定的层级形成一个集合,方便统计和计算。而flatten则更适用于类型相似的集合嵌套扁平化操作。适用场景还是有差别的。

● Kotlin(1) lambda表达式和高阶函数操作符

● 谈谈什么是MySql的索引

● 从阿里手册引出的Join查询

● RPC与RMI服务

● Flutter中的widget

点击在看,驱动原创

java 泛型 t_Kotlin(2) 泛型与集合相关推荐

  1. java可变参数 map_Java第6期Collection、Map、迭代器、泛型、可变参数、集合工具类、集合结构、Debug...

    集合:集合是java中提供的一种容器,可以用来存储多个数据. 集合和数组既然都是容器,它们有啥区别呢?数组的长度是固定的.集合的长度是可变的. 数组中存储的是同一类型的元素,可以存储基本数据类型值.集 ...

  2. Java自学第6期——Collection、Map、迭代器、泛型、可变参数、集合工具类、集合数据结构、Debug

    欢迎访问我的个人网站:https://bengtian.club 集合:集合是java中提供的一种容器,可以用来存储多个数据. 集合和数组既然都是容器,它们有啥区别呢? 数组的长度是固定的.集合的长度 ...

  3. 01.08学习Java的day18【泛型与集合】

    day18[泛型] 主要内容 泛型 Collection集合 学习目标 能够使用泛型定义类.接口.方法 能够理解泛型上限 能够阐述泛型通配符的作用 能够识别通配符的上下限 能够熟练使用Collecti ...

  4. 泛型java 代码讲解_Java泛型详解

    2516326-5475e88a458a09e4.png 一,打破砂锅问到底 泛型存在的意义? 泛型类,泛型接口,泛型方法如何定义? 如何限定类型变量? 泛型中使用的约束和局限性有哪些? 泛型类型的继 ...

  5. Java基础篇:泛型

    文章目录 1.为什么要有泛型 2.在集合中使用泛型 3.自定义泛型结构 4.泛型在继承上的体现 5.通配符的使用 1.为什么要有泛型 泛型:标签 泛型背后的核心思想就是:把一个集合中的内容限制为一个特 ...

  6. java中什么时候不能用泛型_java中泛型的正确使用姿势

    image.png 泛型是Java中一个非常重要的知识点,在Java集合类框架中泛型被广泛应用.利用好泛型,在系统架构中是一把利器. 泛型类 先看一个例子,restful架构中,需要定义api接口返回 ...

  7. Java基础篇:泛型与类型擦除

    一.什么是泛型: 泛型的本质是 参数化类型,也就是说 将所操作的数据类型 指定为一个参数,在不创建新类的情况下,通过参数来指定所要操作的具体类型(类似于方法中的变量参数,此时类型也定义成参数形式),也 ...

  8. yii2数组转为对象_好程序员Java学习路线分享java为什么不支持泛型数组

    本篇文章好程序员为大家一下java为什么不支持泛型数组,希望对大家有所帮助. public class Pair { public void info( ) { System.out.println( ...

  9. java使用泛型后消除泛型_如何以及何时使用泛型

    java使用泛型后消除泛型 本文是我们名为" 高级Java "的学院课程的一部分. 本课程旨在帮助您最有效地使用Java. 它讨论了高级主题,包括对象创建,并发,序列化,反射等. ...

最新文章

  1. Python自然语言处理
  2. unity 草 可以一棵棵种吗?_种黄瓜这几种做法是错误的,却还有好多人在犯
  3. GraphPad轻松绘制配对比较图和双向柱状图
  4. 回来来看初学C语言的一些有趣的图形的输出
  5. HttpModule 介绍(转)
  6. 嵌套的SQL另外一种写法
  7. Real-Time Rendering——16.1 Sources of Three-Dimensional Data三维数据的来源
  8. 使用Box2dWeb模拟飞行箭矢
  9. 手把手教你开发App(HelloWorld)
  10. 中介者模式:还记得你到单位入职的第一天吗?你有没有遇到文中‘王二’的事呢?
  11. Windows权限维持1:账号隐藏
  12. ai为什么要栅格化_AI 效果-栅格化的具体用途是什么
  13. Netty的概念和架构
  14. Richard Stallman简介
  15. BCD码-8421码、5421码、2421码、余3码
  16. 跨越40年的甲骨文公司,正在成为年轻一代的时尚选择
  17. 竹林蹊径:深入浅出Windows驱动开发(china-pub预订中)
  18. eset找不到服务器更新失败,eset nod32无法更新的解决办法-整理常见的nod32更新问题!...
  19. C++结构体 结构体定义和使用、结构体数组、结构体指针、结构体嵌套结构体、结构体做函数参数
  20. NE555进行数据计数流水灯

热门文章

  1. linux cp命令 前面,盘点Linux命令之Linux cp命令使用大全
  2. mysql goto,如何在MySQL存储函数中使用goto标签
  3. 用100元买100支笔c语言,用C编程!有100块钱,买100支笔,其中钢笔3元,圆珠笔2元,铅笔0.5元,问各买多少支?...
  4. java 模拟鼠标键盘_使用SWT模拟鼠标键盘事件
  5. linux下oracle启动关闭
  6. docker 自定义网桥
  7. linux定时刷新窗口,Linux的屏幕刷新率问题 窗口调整问题
  8. discuz登陆首页后提示style_1_commen.css,关于Discuz用户面板必须刷新才能显示登录状态的问题解决办法...
  9. 计算机知识幼儿园,幼儿园中班计算机教学工作计划
  10. 暨南大学计算机专业录取分数线2019,暨南大学2019年在广东省各专业录取分数线...