Kotlin零基础入门到精通(精选)
一. Kotlin课程概述
1.1 课程安排:
- 课程介绍、Kotlin介绍、开发环境搭建
- 基本语法:基本类型、空安全类型、智能转换、类与对象同步、数组与区间
- 程序结构:常量与变量、函数、Lambda、类成员、条件表达式、循环语句、运算符、异常捕获
- 面向对象:抽象类和接口、Object、伴生对象、扩展方法、属性代理、数据类、内部类、枚举与密封类
- 高阶函数:基本概念、常见高阶函数、尾递归优化、闭包、函数复合、科里化、偏函数
- DSL:基本概念、案例开发、Gradle脚本
- 协程:基本概念、协程的使用、封装协程库、协程原理分析;
- 与Java混编:基本互操作、正则表达式、集合框架、IO操作、装箱与拆箱、NoArg插件、AllOpen插件、注解处理器
- 应用与展望:前景与展望、编写脚本、服务端、前端、Android、Native
1.2 什么是Kotlin?
- Kotlin就是一门可以运行在Java虚拟机、Android、浏览器上的静态语言,它与Java 100%兼容,如果你对Java非常熟悉,那么你就会发现Kotlin除了自己的标准库之外,大多仍然使用经典的Java集合框架;
- 总结来说:
- Android官方开发语言
- 100%兼容Java
- Kotlin-Js 前端开发
- Kotlin-Jvm 服务端开发
- Kotlin-Native 本地执行程序
Kotlin 是一门全栈语言
1.3 Kotlin的发展历程
- 2010年立项
- 2011.6对外公开
- 2012.2开源
- 2013.8 支持 Android Studio
- 2014.6全新的开源web站点和域名 Kotlinlang.org
- 2016.2 发布1.0
- 2016.9 发布1.0.4, 支持apt
1.4 学习目标
- 学会使用Kotlin
- 熟悉Java生态
- 了解一些特性的背后实现
1.5 必备知识
- 熟悉计算机基础、操作系统相关的知识
- 了解Java及其生态
- 了解Java工程组织的常用工具
- 熟悉IntelliJ Idea
1.6 参考资料
- 官方文档:https://kotlinlang.org/docs/reference
- Kotlin源码:https://github.com/JetBrains/kotlin
- Kotlin官博:https://blog.jetbrains.com/kotlin/
- Kotlin微信公众号:Kotlin
1.7 Hello,world
-
安装Kotlin插件,如图所示:
安装后要重启一次才能生效;
-
创建一个Kotlin工程,如图所示:
3. 根据实际,填写项目的groupId,ArtifactId,以及Version,如图所示:
-
一路默认下去即可,然后成功创建项目,然后在此项目中创建Kotlin包,如图所示:
-
紧接着创建包net.println.kotlin(根据实际来,此处可不一致),然后创建HelloWorld.kt,如图所示:
-
编写Hello,world代码如下:
fun main(args:Array<String>){ println("Hello World") }
-
执行,如图所示:
执行成功后,控制台会打印Hello,world字样,说明运行成功,如图所示:
-
点击println进去,可以看到源码,其打印操作是调用的Java的System.out.println,如图所示:
二. 数据类型
2.1 本章目标
- 认识基本类型
- 初步认识类及其相关概念
- 认识区间和数组
简单来说就是看懂如图例子:
2.2 Boolean类型
- Boolean 值 只有true或者false两个值,它无处不在,相当于Java类型的boolean
- 示例代码:
> var是可变变量(可读可写),val是可读变量(只能读);同时 : 后面的Boolean 是指它的类型,前面的aBoolean或anotherBoolean是它的变量名称,=号后面的true或false是它的值;val aBoolean : Boolean = true val anotherBoolean : Boolean = false
2.3 Number类型
-
数字类型如下:
-
int,Long类型的最大值和最小值
// 2147483647 val maxInt: Int= Int.MAX_VALUE // -2147483648 val mintInt: Int=Int.MIN_VALUE val maxLong : Long=Long.MAX_VALUE val minLong: Long=Long.MIN_VALUE val aFolat: Float=20F val maxFolat: Float=Float.MAX_VALUE val minFolat: Float=- Float.MAX_VALUE val maxShort : Short=Short.MAX_VALUE val minShort : Short=Short.MIN_VALUE val maxByte: Byte= Byte.MAX_VALUE val minByte : Byte= Byte.MIN_VALUE
val后面的为变量名,可替换为实际名称;Long类型的长整形后面可以加个L; Float类型后面必须加F,f;Float是浮点类型,有精度问题,计算钱相关的不要用这个;
-
装箱和拆箱
- 在Kotlin中不区分装箱和拆箱;
2.4 Char类型
-
特点:
- 字符对应Java的Character
- 占两个字节,表示一个16位的Unicode字符
- 字符用单引号’‘引起来,例如: ‘a’,‘0’,’\n’
-
Char类型转义字符如图:
-
不可隐式转换
- 在java中,一个int类型与Long相加,原本的Int类型会自动隐式转换为Long类型,这在Kotlin中是不允许的;
-
比较相等
- ==表示equals 值得相等比较
- === 三个等号表示引用地址的比较,即比较两个值是否是同一个引用地址;
-
字符串模板
- 代码如图:
val arg1: Int =0 val arg2: Int =1 // java款式的加法 println(""+arg1+"+"arg2+"="+(arg1+arg2)) // kotlin款式的加法 println("$arg1+ $arg2=${arg1+arg2}") // 如果要打印美元 $这个符号,则再加一个$ val salary: Int = 1000 println("$"+"$salary") // 或者使用转义符号 println("\$salary") // 三个引号,转义会失效,支持换行 val txt:String =""" hello , world """ // 打印它的字符数量 println(rawString.length)
- 代码如图:
2.5 类与对象
-
什么是类?
- 类,一个抽象的概念
- 具有某些特征的事物的概括
- 不特定指代任何一个具体的事物
- 举例:
- 人、车、书
- 写法:
class<类名>{<成员>}
-
什么是对象?
- 是一个具体的概念,与类相对
- 描述某一种类的具体个体
- 举例:
- 某些人、领导的车、你手里的那本书
-
类和对象的关系?
- 一个类通常可以有很多个具体的对象
- 一个对象本质上只能从属于一个类
- 某一个人,他是工程师,但本质上还是属于人这一类
-
对象也经常被称作“类的对象”或者“类的实例”
- 比如 类: 城市 --> 上海、深圳(对象)
-
类的继承
- 提取多个类的共性得到一个更抽象的类,即父类;
- 子类拥有父类的一切特征
- 子类也可以自定义自己的特征
- 所有类都最终继承自Any
-
类与对象的实例图示:
-
如果在类中加了init字段,则在创建过程中会自动执行其中的方法,如图所示:
-
当构造只有一个的时候可以进行省略,如果有多个则不可以,如图所示:
-
子类继承了父类中的一些方法,如图所示:
-
Any是一切类的父类,它拥有equals,hashCode,toString方法,则说明在Kotlin中的其他所有类,都拥有这些方法,如图所示:
2.6 空类型和智能类型转换
- 任意类型都有可空和不可空两种
- val notNull:String = null // 错误,不能为空。 如果为空会抛出异常
- val nullable:String ?= null // 正确,可以为空。 如果为空,则被赋值的nullable的值为null
- notNull.length // 正确,不为空的值可以直接使用
- nullable.length // 错误,可能为空,不能直接获取长度
- nullable!!.length // 正确,强制认定nullable不可空(如果在这段代码前进行了if判断,比如不为空的时候才执行的这段代码,就没有问题。我们已经认定了这个nullable变量不为空)
- nullable?.length // 正确,若nullable为空,返回空
- Java Style类型转换
val sub: SubClass = parent as SubClass
- 类似于Java 的类型汉族那换,失败则抛出异常;
这里的含义是,判断parent变量为SubClass的子类,若为其子类,则sub是SubClass类型,如不为则直接抛出异常;
- 安全类型转换
- val sub: SubClass? = parent as? SubClass
- 如果转换失败,返回null,不抛异常
> 如果parent不是SubClass的子类,则类型转换失败,sub不能编程SubClass类型,则sub的值为null;
2.7 包
- 概述:
- 包就是命名空间
- 包的声明必须在非注释代码的第一行
- 类的全名:
- net.println.kotlin,chapter2.HelloWorld
- 包即类的全名,import字段进行导入,import同时可以对此包进行命名,可以以另外一个名称进行替代和调用。如图所示:
package net.println.koltin.DemoTest import net.println.kotlin.HelloWord as Hello fun main(args:Array<String>){ val sayHello:Hello=Hello(); }
这里导入的Hello,就是HelloWorld类。通过创建Hello,就相当于创建了一个HelloWord,所以sayHello看似是对Hello实例化,实际上是对HelloWorld进行了实例化;
2.8 区间
- 概述:
- 一个数学上的概念,表示范围
- ClosedRange的子类,IntRange最常用
- 基本写法:
- 0…100表示[0,100]
- 0 until 100 表示 [0,100)
- i in 0…100判断 i 是否在区间 [0,100]中
- 示例代码:
// 前后都闭区间 [0,1024] val range:IntRange=0..1024 // 前闭后开的区间 [0,1024) = [0,1023] val range_exclusive:IntRange= 0 until 1024 val empty_Range:IntRange=0..-1 fun main(args:Array<String>){ // true 是为空,因为此范围中没有值 println(emptyRange.isEmpty()) // true 此范围中包含了50 println(range.contains(50)) // true 检查50是否在range的范围中 println(50 in range) } // 下面打印出来的结果为: 0,1,2,3,4,5,6,7,8,9,10... for(i in range_exclusive){ println("$i,") }
用i in IntRange可以作范围判断,以及辅助遍历等操作
2.9 数组
-
数组是什么?
- 对应英文单词Array:
An impressive display or range of a particular type of thing or an ordered arrangement,in particular
- 跟数一点关系没有
- 就是一系列对象,这个对象可以是各类型的数字,字符,字符串,或者自定义对象等;
- 对应英文单词Array:
-
基本写法
- val array:Array arrayOf(…)
-
基本操作:
- println array[i] 输出第i个成员
- array[i] 指定数组的第i个成员值;我们可以通过此进行赋值或者获取值
- array.length 数组的长度
-
为了避免不必要的装箱和拆箱,基本类型的数组是定制的,如图所示:
-
数组示例代码:
- 创建对象,如图所示:
- 创建示例代码如下:
// 创建一个int数组 val arrayOfint: IntArray = intArrayOf(1,3,5,7) // 创建一个Char数组 val arrayOfChar: CharArray = charArrayOf("H","e","l","o","W","o","r","l","d") // 创建一个自定义对象数组,根据上面创建的类 val arrayOf书记: Array<市委书记> = arrayOf(市委书记("张"),市委书记("赵"),市委书记("黄")) fun main(args: Array<String>){ // 打印 int数组 println(arrayOfInt.size) for(int in arrayOfInt){ println(int) } } println(arrayOf书记[1]) // 这里将方书记赋值给了数组的1 索引处。此处1索引原来的对象会变成新替换的对象,所以重新打印1索引处的书记会变成新的方书记; arrayOf书记[1] = 市委书记("方") println(arrayOf书记[1]) // 这里的joinToString("")表示每个字符之间不用什么连接。 传入制定参数就以指定值进行连接。如果不传,默认以,号连接,比如: H,e,l,l,o... 此处代码打印结果为: HelloWorld println(arrayOfChar.joinToString(""))
- 创建对象,如图所示:
三. 程序结构
3.1 常量与变量
-
什么是常量?
- val= value, 值类型
- 类似Java 的final
- 不可能重复复制;
- 举例:
- 运行时常量: val x=getX()
- 编译期常量(Java中的静态常量 final): const val x=2
-
什么是变量:
- var = variable
- 举例:
- var x =“HelloWorld” //定义变量
- x= “HiWorld” // 再次赋值
-
类型推导- 编译器可以推导量的类型
- val String =“Hello” // 推导出String类型
- val int =5 // Int类型
- var x =getString() + 5 // String类型
-
var是可变量,val 是不可变,它是常量;
-
val虽然是不可变,但是它不是静态的,如果需要在编译期时就加在,可以在前面加一个const字段;
3.2 函数
- 什么是函数?
- 任何函数都是以fun 开头,然后后面为它的名字,括号内为它的参数若有范围值,在后面用:Any ,Any代表返回值类型;
- 比如我们的Main函数:
fun main(args: Array<String>){ println("Hello,world") // 打印args数组的索引为1 的值 println(${args[0]}) if (args.size !=2){ } }
如果没有传参,会报数组索引异常。这里main函数没有为args赋值;如果启动时,传入了参数就可以打印了;同时如果有返回值,则需要在main(…)这个括号后面加 :返回值类型来定义返回值类型;
- 代码示例:
fun main(args: Array<String>){ checkArgs(args) val arg1=args[0].toInt() val arg2=args[1].toInt() println("$arg1+$arg2=${sum(arg1,arg2)}") } fun checkArgs(args: Array<String>){ if(args.size != 2){ println("请传入两个整形参数,例如 1, 2") System.exit(-1) } } fun sum(arg1: Int ,arg2:Int):Int{ return arg1+arg2 }
3.3 Lambda表达式
-
什么是lambda表达式?
- 匿名函数
- 写法:
{[参数列表]->[函数体,最后一行是返回值]}
- 举例:
val sum ={a:Int,b:Int-> a+b}
-
Lambda的类型表示举例:
// 无参,返回值为Unit ()->Unit // 传入整型,返回一个整型 (Int)->Int // 传入字符串、Lambda表达式,返回Boolean (String,(String)->String)->Boolean
-
Lambda表达式的调用
- 用()进行调用
- 等价于invoke()
- 举例:
val sum ={a:Int,b:Int->a+b} sum(2,3) sum.invoke(2,3)
-
Lambda表达式的简化
- 函数参数调用时最后一个Lambda可以移出去
- 函数参数只有一个Lambda,调用时小括号可省略
- Lambda只有一个参数可默认为it
- 入参,返回值与形参一致的函数可以用函数引用的方式作为实参传入;
-
使用for 我们用foreach也可以进行遍历;
3.4 类成员
-
什么是类成员?
- 属性:或者说成员变量,类范围内的变量
- 方法:或者说成员函数,类范围内的函数
-
函数和方法的区别?
- 函数强调功能本身,不考虑从属
- 方法的称呼通常是从类的角度出发
- 叫法不同而已,不要纠结;
-
函数如何定义方法?
- 写法与普通函数一致,函数如果写在类中,它就是方法
- 举例:
class Hello{ fun sayHello(name:String)=println("Hello,$name") }
-
定义属性
- 构造方法参数中val/var的都是属性
- 类内部也可以定义属性
- 举例:
class Hello(val aField:Int,notAField:Int){ val anotherFIeld:Float=3f }
-
属性的访问规则:
- 属性可以定义getter/setter
- 举例如下:
val a:Int=0 get()=field var b:Float=0f set(value){field=value}
get(){return field} 其中field指代了此变量
-
属性初始化
- 属性的初始化尽量在构造方法中完成
- 无法在构造方法中初始化,尝试降级为局部变量
- val用lateinit延迟初始化,val用lazy
- 可空类型谨慎用null直接初始化;
- 举例:
class X class A{ // 使用lateinit延迟初始化,不需要立即给值 lateinit var c: String val e: X by lazy{ X() } }
3.5 运算符
- 任何类可以定义或者重载父类的基本运算符
- 通过运算符对应的具名函数来定义
- 对参数个数作要求,对参数和返回值类型不作要求
- 不能像Scala一样定义任意运算符
3.6 表达式
-
中缀表达式
- 只有一个参数,且用infix修饰的函数
- 举例:
class Book{ infix fun on(place: String){ ... } } if(Book() on "My Desk"){...}
-
if 表达式
- 举例:
if(a==b) ... else ...
在if表达式中,我们既可以用来做条件判断,也可以类似java三元运算符一样直接用来当一个值进行使用。比如
val b=2 // 当b等于2时,则a等于3 否则等于5 val a= if(b==2) 3 else 5
- 表达式完备性:
- 当我们用于类似三元运算符的操作时,必须要有else
- 举例:
-
When 表达式
- 加强版Switch,支持任意类型
- 支持纯表达式条件分支(类似if)
- 表达式与完备性
- 举例:
fun main(args: Array<String>){ val x=5 when(x){ is Int-> 逻辑代码... in 1..100-> 逻辑代码... !in 1..100-> 逻辑代码... args[0].toInt()-> 逻辑代码... } }
这里的when处传入值,下面任意满足条件且按先后只执行第一个符合条件的代码逻辑;
- 同时还有when不带括号的方式:
var str="今天是周末;" val biaodian = when { str.contains(";") -> { ";" } str.contains(";") -> { ";" } str.contains(",") -> { "," } else -> { "," }
含义可以参考此java代码:
var str="今天是周末;" val biaodian = if (str.contains(";")){ ";" }else if (str.contains(";")){ ";" }else if (str.contains(",")){ "," }else{ "," }
3.7 循环
-
for循环
- 基本写法
for(element in elements)...
- 代码示例:
fun main(args: Array<String>){ // 遍历一 for(arg in args){ println(arg) } // 遍历二 for((index,value) in args.withIndex()){ println("$index-> $value") } // 遍历三 for(indexedValue in args.withIndex()){ println("${indexedValue.index} -> ${indexedValue.value}") } }
- 给任意类实现Iterator方法
可网上找
- 基本写法
-
While循环
- 古董级语法
- do … while(…)…
- while(…)…
- 代码示例:
fun main(args:Array<String>){ var x=5 while(x>0){ println(x) x-- } do { println(x) x-- }while(x>0) }
-
跳过和终止循环
- 跳过当前循环用continue
- 终止循环用break
- 多层循环嵌套的终止结合标签使用
3.8 捕获异常
- catch 分支匹配异常类型
- 表达式,可以用来赋值
- finally: 无论代码是否抛出异常都会执行
- 注意下面的写法:
return try( x/y )catch(e:Exception){ 0 }finally{ ... }
异常的捕获及处理与java类似,不过其可以作为值进行使用;
3.9 各种类型参数
-
具名参数
- 给函数的实参附上形参
- 举例:
fun sum(arg1:Int, arg2:Int)= arg1+arg2 sum (arg1=2 ,arg2=3)
-
变长参数
- 某个参数可以接收多个值
- 可以不为最后一个参数
- 如果传参时有歧义,需要使用具名参数
-
Spread Operator
- 只支持展开Array
- 只用于变长参数列表的实参
- 不能重载
-
默认参数:
- 为函数参数指定默认值
- 可以为任意位置的参数指定默认值
- 传参时,如果有歧义,需要使用具名参数
-
代码示例:
fun main(vararg args: String){ var array= intArrayOf(1,3,4,5) // string="Hello" 是一个默认参数 // *array表示将array数组展开,将一个个元素传入,如: hello(3.0,1,3,4,5,string="Hello") hello(3.0,*array,string="Hello") } fun hello(double:Double, vararg ints:Int, string:String){ ints.forEach(::println) println(string) }
3.10 导出为可执行程序
- 在可执行的类上加上如下字段:
apply plugin:'application'
mainClassName="net.println.kotlin.chapter3.CalcKt"
然后gradle会下载一些相关插件,完成后会在Gradle的窗口中,在Tasks-> distribution-> InstallDist 中执行InstallDist. 如图所示:
- 会各生成一个windows和Linux下的脚本,如图所示:
四. 面向对象
4.1 面向对象-抽象类与接口
-
面向对象的基本概念
- 本质上就是解决如何用程序描述世界的问题
- 讨论如何把实际存在的东西映射成程序的类和对象
- 一种程序设计的思路、思想、方法
-
类实例:
// 定义一个类 class Demo{ // 定义一个可读变量 val i=4 // 定义一个方法 fun out(i: int){ println(i) } } // 定义一个接口 interface chouxianglei{ // 定义一个接口方法 fun hello() }
-
继承一个接口的时候,使用类名(),实现一个接口的时候,使用类名即可,在Kotlin 中是单继承,多实现;如图所示:
-
什么是接口?
- 接口,直观理解就是一种约定。 Kotlin的接口与Object-C的Protocol比较类似
- 举例,输入设备接口:
interface InputDevice{ fun input(event: Any) }
-
接口与抽象类的区别:
- 接口:
- 不能有状态
- 必须由类对其进行实现后使用
- 抽象类:
- 实现了一部分协议的半成品
- 可以有状态,可以有方法实现
- 必须由子类继承后使用
- 共性:
- 比较抽象,不能直接实例化
- 有需要子类(实现类)实现的方法
- 父类(接口)变量可以接受子类(实现类)的实例赋值
- 接口:
4.2 继承
-
继承(实现)语法要点
- 父类需要open才可以被继承
- 父类方法、属性需要open才可以被覆写
- 接口、接口方法、抽象类默认为open
- 覆写父类(接口)成员需要override关键字
-
Class D: A(),B,C
- 注意继承类时实际上调用了父类的构造方法
- 类只能单继承,接口可以多实现
-
class Manager(driver: Driver): Driver by driver
- 接口方法实现交给代理类实现
-
接口方法冲突
- 接口方法可以有默认实现
- 签名一致且返回值相同的冲突
- 子类(实现类)必须覆写冲突方法
4.3 可见性
- 可见性Java与Kotlin对比,如图所示:
4.4 Object类
- 特点:
- 只有一个实例的类
- 不能自定义构造方法
- 可以实现接口、继承父类
- 本质上就是单例模式最基本的实现
使用object类可以创建一个最简单的单例类
4.5 伴生对象与静态成员
- 伴生对象的特点:
- 每个类可以对应一个伴生对象
- 伴生对象的成员全局独一份
- 伴生对象的成员类似Java的静态成员
> 伴生对象就相当于静态变量和静态方法的整体;
- 使用伴生对象需要注意的地方:
- 静态成员考虑用包级函数、变量替代
- @JvmField 和 @JvmStatic的使用
- 示例代码:
class Latitude private constructor(val value: Double){ companion object{ @JvmStatic fun ofDouble(double:Double):Latitude{ return Latitude(double) } fun ofLatitude(latitude: Latitude): Latitude{ return Latitude(latitude.value) } @JvmField val TAG: String="Latitude" } }
这里@JvmStatic和@JvmField不加此注解,在companion object代码块中均可作为静态方法或静态属性,但是如果不加不能被java代码所识别。如果涉及到java代码调用此静态方法或静态属性,则需要加@JvmStatic或@JvmField注解;它们分别修饰方法和属性;
4.5 方法重载与默认
-
方法重载的特点:
- Overloads
- 名称相同、参数不同的方法
- Jvm函数签名的概念: 函数名、参数列表
- 跟返回值没有关系
-
默认参数的特点:
- 为函数参数设定一个默认值
- 可以为任意位置的参数设置默认值
- 函数调用产生混淆时用具名参数
-
重载与默认:
- 二者的相关性以及@JvmOverloads
- 避免定义关系不大的重载
- 代码示例如图:
方法重载能够用默认参数来解决,所以能不使用就尽量不使用。@JvmOverloads能用于被java代码所识别,如果只在Kotlin中使用可以不用加这个注解;
4.6 扩展成员
- 在Java中,一些java库的一些方法不够全面,往往我们会自己定义一些Utils,在Kotlin中,我们可以对一些现有的类进行扩展;
- 特点:
- 为现有成员添加方法、属性:
// 添加方法: fun X.y():Z{...} // 添加属性 val X.m 注意扩展属性不能初始化,类似接口属性
- Java调用扩展成员类似调用静态方法
- 代码如图所示:
- 为现有成员添加方法、属性:
4.7 属性代理
-
如图所示,此懒加载就是一个代理:
-
特点:
- 定义方法:
val/var <property name>: <Type> by <expression>
- 代理者需要实现相应的setValue/getValue方法
实际上就是说,当我们使用了 val/var xxx by yyy()的属性代理时,其代码执行逻辑主要看yyy()了。如果是var方法我们需要实现setValue和getValue,如果是val则只需要实现getValue方法即可,因为val是可读常量;
- 定义方法:
-
代码示例:
其中定义了存值和赋值的get,setValue方法;当我们创建时则会自动执行setValue,当取值时则会调用getValue;
4.8 数据类
-
特点:
- 再见,JavaBean
- 默认实现的copy、toString等方法
- componentN方法
- allOpen和noArg插件
-
代码示例:
-
同时,我们可以直接在类中直接定义构造参数值,如图所示:
-
此时不需要传入构造参数,便可将构造参数值外传并执行其他操作,如打印,如图所示:
-
数据类有一个问题,就是它没有空的默认的构造方法。编译生成的类是一个final的且无空构造方法,所以不是JavaBean,在有些时候使用是有问题的。我们可以通过noarg和allopen插件解决:
- 引入依赖:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200611225801792.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3MTI4MDQ5,size_16,color_FFFFFF,t_70)
- 定义并应用插件:
- 对指定的数据类使用@PoKo注解,如图所示:
- 引入依赖:
4.9 内部类与匿名内部类
-
特点:
- 定义在类内部的类
- 与类成员有相似的访问机制
- 默认是静态内部类,非静态用inner关键字
- this@Outter,this@Inner的用法
-
匿名内部类:
- 没有定义名字的内部类
- 类名编译时生成,类似Outter$1.class
- 可继承父类、实现多个接口,与Java注意区别
-
定义一个内部类(非静态):
非静态内部类,需要在 类的class 前面加inner关键字
-
定义一个内部类(静态):
静态内部类。默认内部类为静态内部类;调用时,外部类.内部类()即可;静态内部类无法获取非静态的外部类的属性和方法,因为它是先被编译加载的;而非静态内部类可以持有外部类的非静态属性和方法;
-
当调用外部类的属性时,我们可以直接调用,也可以用this.@外部类名.属性或方法来执行,如图所示:
-
匿名内部类实现方法示例:
4.10 枚举
-
特点:
- 实例可数的类,注意枚举也是类
- 可以修改构造,添加成员
- 可以提升代码的表现力,也有一定的性能开销
- 枚举的属性和方法之间必须要用 ; 号隔开,这里可能是Kotlin中唯一强制要求使用 ; 号的地方
-
定义一个枚举类,如图所示:
$name 是它的名称 $ordinal 是它括号里的值
-
调用示例:
// 打印指定的枚举类 1,DEBUG println(LogLevel.DEBUG.getTag()) // 打印指定枚举类的序号 1 println(LogLevel.DEBUG.ordinal) // 打印指定枚举类的实例 ERROR,4 println(ERROR,4)
4.11 密封类
-
特点:
- 子类可数:
- Kotlin版本小于1.1时,子类必须定义为密封类的内部类
- 在1.1之后,子类只需要与密封类在同一个文件中
- 仔细体会与枚举的不同
- 子类可数:
-
密封类代码示例:
使用枚举适用于没有参数的情况下,而使用密封类可以用在多参数的情况下,每个类的参数都不尽然相同,同时又想保护此类,不让其他fun方法返回此类,就可以使用密封类;
-
密封类在class前面加 sealed关键字
五. 高阶函数
5.1 高阶函数的基本概念
- 高阶函数就是把函数作为参数或者返回值的函数
- 传入或者返回函数的函数
- 函数引用 ::prinltn
> 其他都以此延伸,调用与此类似
-带有Receiver的引用 pdfPrinter::println - 示例:
第一种方式 ::println 说明任何对象(Any)都可以调用此方法,所以才能这样写;
5.2 常用高阶函数
-
map: 一对一映射处理
-
flatMap: 最细化分离
- 第一个是可以跟map一样进行一一映射
- 第二是可以把集合中的集合进行打散;
-
joinToString(",") 字符串连接
-
taskWhile 符合条件立即结束,返回符合条件之前被校验的数据
-
.let 调用者不为空时,则执行后面的逻辑;
-
常见高阶函数
- map/flatMap
- fold/reduce
- filter/takeWhile
- let/apply/with/use
具体可百度详细学习
5.3 尾递归优化
- 函数在调用自己之后没有任何操作,这就是尾递归;
- 使用tailrec可以检查是否是尾递归函数;
- 它是递归的一种特殊形式
- 调用自身后无其他操作
- tailrec关键字提示编译器尾递归优化
- 尾递归与迭代的关系;
5.4 闭包
- 概念:
- 函数运行的环境
- 持有函数的运行状态
- 函数内部可以定义函数
- 函数内部也可以定义类
- 代码示例:
- 定义类的闭包函数:
fun main(args: Array<String>){ val add5=add(5) println(add5(2)) } fun add(x: Int):(Int)->Int{ data class Person(val name:String,val age:Int) return fun(y:Int):Int{ return x + y } }
- 定义闭包函数,内部的count能够一直累加,而不会被释放
fun makeFun():()->Unit{ var count=0 return fun(){ println(++count) } } fun main(args: Array<String>){ val x=makeFun() x() x() x() ... }
- 定义类的闭包函数:
5.5 中缀表达式
- infix 关键字可以简化操作函数,具体可以百度;
- P1,P2,R 表示:参数1,参数2,返回值
- 代码图1:
- 代码图2:
- 函数复合:
- f(g(x))
- 如何实现函数复合
- 回顾:infix的使用
5.6 Currying 科理化
-
概述:
- 理解Currying的概念
- 简单说就是多元函数变换成一元函数调用链(就是将多个参数的函数变成一个参数的函数的调用链)
- 了解Currying的实现方法
- 有科理化就有反科化,就是将一元函数调用链变成多元函数
- 理解Currying的概念
-
代码图片示例:
这里是打印参数的例子;
5.7 偏函数
- 把函数中其中一个或多个值固定为某值的函数
- 概述:
- 理解函数的概念:
- 传入部分参数得到的新函数
- 仔细体会与Currying的不同
- 了解偏函数的实现方法
- 理解函数的概念:
- 图示:
5.8 小案例: 统计字符串个数
- 图示:
- 总结:本章节主要讲解到了高阶函数的一些简单概念及基本使用,为了解及熟悉高阶函数打开了大门,在日常使用过程中,熟悉这些高阶函数,能够让我们更快的写入Kotlin的代码,让程序更加简洁优雅;
六. 领域特定语言: DSL
6.1 DSL的特点
- 概述: 它是一门计算机编程语言,具有语言的表达能力,但是它的表达能力有限,通常只关注某个特定的领域,与java可以写web,可以写安卓可以写桌面程序不一样,它的适用面仅限于特定领域,作用范围更小;
6.2 HTML DSL
- 通过写Kotlin代码能生成处Html代码;
- 代码如图所示:
6.3 Gradle Kotlin脚本编写
-
概述:我们项目工程是以Gradle编写的,可以用Kotlin脚本编写;
-
好处:
- 带提示
- 更简洁
- 能使用Kotlin语法
- 功能更丰富
-
一些区别:
- Kotlin脚本需要将build.gradle改名为build.gradle.kts
- 改名后需要重启
- Kotlin脚本中需要将单引号改为双引号
-
改造前:
-
改名后,全部爆红:
-
重启之后,按照Kotlin写法改正后:(可以与原来代码比对,以学习kotlin脚本语法)
改写之后能够正常编译、打包、运行操作,与之前一样;
七、协程
7.1 协程的基本概念
-
什么是协程
- 协作程序,解决异步问题
- 应用层完成调度
- 支持协程的语言例如:
-
协程的特点:
- 协程是协同作事情、Java的多线程是抢占的
- 协程是以同步的代码作异步的活;
- 协程消耗资源更少,只需要记录位置,和结束标记等;
- 协程的图片示例:
-
协程要解决什么问题
- 异步代码像同步代码一样直观
- 简化异步代码异常处理
- 轻量级的并发方案
- 它从1.1开始支持,是实验性质的API,后面可能有一定的变化,但目前已经变化很小了;
-
如何支持协程
- 编译器对suspend函数的编译支持
- 标准库的基本API支持
- kotlinx.coroutine应用级支持
-
本章目标
- 掌握协程标准库API的使用方法
- 了解协程的运行原理
- 了解 kotlinx.coroutine框架
7.2 了解协程
- enqueue: 表示异步处理
- 协程是没有异步能力的,这需要我们手动去操作;
- 协程的基本API
- createCoroutine:创建协程
- startCoroutine:启动协程
- suspendCoroutine:挂起协程
- Continuation接口: 运行控制类,负责结果和异常的返回
- CoroutineContext接口:运行上下文,资源持有,运行调度
- ContinuationInterceptor接口
- 协程控制拦截器
- 可用来处理协程调度
- 执行流程:
- 携程被编译成状态机
- suspend函数即状态转移,如图所示:
- 详细来说,就是正常的结果通过resume返回,异常通过resumeWithException抛出,如图所示:
这里的圈可能会转很多次,取决于调用多少次suspend函数;
7.3 kotlin.coroutine框架介绍
- 主要模块,如图所示:
八. Kotlin与Java混合开发
8.1 基本互操作
- 属性读写:
- Kotlin自动识别Java Getter/Setter
- Java操作Kotlin属性通过Getter/Setter
- 空安全类型
- Kotlin有空安全
- Java没有,所以可能会涉及Platform Type,我们可以通过@Nullable和@NotNull来弥补java的不足
- 几类函数的调用:
- 包级函数: 静态方法
- 扩展方法:带Receiver的静态方法
- 运算符重载: 带Receiver的对应名称的静态方法
- 几个常见注解的使用:
- @JvmField: 将属性编译为Java变量
- @JvmStatic: 将对象的方法编译成Java静态方法
上面这两个都是加上之后与java的静态变量|静态方法 没有差别,否则不能被java所识别;
- @JvmOverloads: 默认参数生成重载方法
标注这个注解后能被java识别,它是一个默认参数,java中没有这个,标注后就可以使用了
- @file:JvmName : 指定Kotlin文件编译后的类名
- NoArg与AllOpen
- NoArg为被标注的类生成无参构造;
- 支持JPA注解,如@Entity
- AllOpen为被标注的类去掉final,允许被继承
- 支持Spring注解,如@Component
- 支持自定义注解类型,例如:@PoKo
- NoArg为被标注的类生成无参构造;
- 泛型
- 通配符Kotlin的*对应于Java 的 ?
- 协变和逆变 out/in
- ArrayList
- 没有Raw类型
- Java的List->Kotlin的List<*>
8.2 SAM转换
- 概述:
- Single Abstract Method
- SAM转换的条件
- Java的接口,单一接口方法
- 注意转换后的实例变化
8.3 正则表达式
-
概述:
- 用Raw字符串定义正则表达式(就是不需要转义符,然后***三个)
- Java的Pattern
- Kotlin的Regex
-
代码如图:
8.4 集合框架
-
概述:
- Kotlin到Java的类型映射
- 不可变与可变集合接口
- 部分接口优化
-
代码如图:
-
集合类型of 表示为不可变集合,初始化之后就不能进行操作了;而Mutable集合类型表示为可变集合
- 代码如图:
- 代码如图:
8.5 IO操作
- Java版本读取操作:
- Kotlin读取操作:
- 使用use关键词能自动关闭流,如图所示:
- 小文件可以用Kotlin的扩展方法:
- 总结IO操作:
- File、Stream、Reader、Writer的扩展方法
- 使用use扩展自动关闭资源
- 小文件一次性读取操作
8.6 拆箱与装箱
- 了解Kotlin基本类型到Java的映射关系
- 注意规避基本类型相关的问题
- 代码示例:
如果遇到这种问题,可以定义java代码或者中间通过java代码进行处理
8.7 注解处理器
- 首先添加插件:
apply plugin: "kotlin-kapt" // 添加生成路径 sourceSets{ main.kotlin.srcDirs+= "build/generated/source/kapt/main" }
然后更新Gradle,通过Gradle右侧Build命令,使用IDEA的Build不能编译,IDEA还不支持
- 如图所示:
九. Kotlin的未来与展望
9.1 Kotlin的应用场景
-
Kotlin Sript
- Gradle脚本,Gradle 3.0开始部分支持,尚在完善中
-
Java虚拟机应用
- Web应用,完美支持
- JavaFx,完美支持
-
前端开发
- 1.1开始正式支持Kotlin-JavaScript
-
Android应用开发
- Kotlin目前的主战场
-
Native程序
- 直接编译Kotlin代码为机器码,不依赖Jvm
- 支持与C代码交互
- 技术预览版功能有限,前途无量
9.2 Kotlin-Script脚本编写
- 代码示例:
- 只要以后缀名为kts,就能马上识别出这是脚本
- 还能创建安卓、前端项目,SpringBoot项目,具体省略;
9.3 创建SpringBoot项目
-
选择项目类型:
-
输入项目名称和组名:
然后一直next 成功创建后到主界面;
-
创建成功后如图所示:
-
配置noarg插件,它能在程序编译的时候,自动的为对象生成默认的无参构造方法
-
配置allopen插件,因为kotlin的类都是final的,我们继承它的时候需要open,能在编译的时候去掉final;
以上两个只有编译期能使用
- 操作如图所示:(添加的不止于上方所述的内容)
里面配置的如jpa的部分可以根据实际情况决定,不需要可以去掉;
9.4 Kotlin-Natie项目开发
- 可以不需要使用jvm进行编译
- 目前还未正式发布,仅用于了解
- 可直接与C语言进行交互
更多可百度学习
9.5 结束
- 互勉互励,点个关注一起加油吧!( ^∀^)