Kotlin笔记

Kotlin大纲

大纲-Kotlin

Kotlin基础、类、对象:

Kotlin基础、类、对象

一些链接、资源

Kotlin参考文档
https://www.cnblogs.com/Jetictors/p/9227498.html

《Kotlin实战》
《Kotlin从零到精通Android开发》

kotlin的活动等信息公布网站:cn.kotlin.tips

Java与Kotlin互转

(借助Android Studio)

Java转Kotlin

打开要转的文件

  • 方法1
    Ctrl+Shift+Alt+K
  • 方法2
    Code - Convert Java File To Kotlin File

Kotlikn 转 Java

  1. Tools>Kotlin>Show Kotlin Bytecode
  2. Decompile

Kotlin特性

  1. 空判断,空安全检查
  2. =======判断值相等(等价于equals),===判断引用(等价于判断内存地址)
  3. 构造函数,也是个函数,跟普通函数调用一样,调用后构造一个对象
  4. inline,函数内联,在编译时就进行内联优化,而不是运行时
  5. sealed class(“密封类”)同时包含了两个优势–抽象类表示的灵活性和枚举里集合的受限性
  6. 互操作性:Kotlin与所有基于Java的框架完全兼容,而无需将所有代码迁移到Kotlin
  7. 支持多平台开发:不仅可以使用Kotlin开发Android,还可以开发iOS、后端与Web应用程序。享受在平台之间共享公共代码的好处

开发环境

Kotlin与Android开发的关系

Kotlin开发工具

  1. IntelliJ IDEA 可以运行
  2. 在线运行 Kotlin 代码
  3. 使用 Android Studio

SDK安装与插件升级

Kotlin简单配置

Kotlin相关技术

【Hencoder】

变量和函数

Kotlin的变量、函数和类型

视频链接
文章链接

1
2
3
//声明变量。这样写会报Property must be initialized or be abstract
var v:View
//Kotlin变量没有默认值,要给初始值(Java的field有默认值。Java局部变量也没有默认值,也要给初始值)

java中的“静态”

在kotlin中有三种方式可以实现java中静态函数的声明

  1. 直接在kotlin文件中直接声明fun函数(不是在class内)
    会根据文件编译生成属于这个文件的静态函数。叫顶层函数、包级函数
    在java中调用kotlin的顶层函数:文件名Kt.顶层函数名(参数)
    在kotlin中调用java的静态方法:文件名.静态函数(参数)

    这种直接在kotlin文件中写的方式,函数不知道归于哪个类。

    可在首行添加 @file:JvmName("KotlinUtils")来规定类名,那么在java中调用:KotlinUtils.函数(参数)

    @file表示注解使用处的目标,告诉注解它的作用对象是文件

  2. object 类名{} 里面所有函数、变量都会是静态的
    在java中调用:类名.INSTANCE.函数(参数)
    在kotlin中调用:类名.函数(参数)

    使用object修饰的类,它会自动创建一个单例,所以在java中调用的话要通过它的单例来调用。

  3. 伴生对象(在内部会维护一个内部类的单例对象)【Application(是由Android的Framework来创建)不能使用第2点的自动生成,得用这种伴生对象的方式】

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class BaseApplication : Application(){
    companion object{
    private lateinit var currentApplication: Context

    @JvmStatic
    fun currentApplication(): Context{
    return currentApplication
    }
    }

    override fun onCreate(){
    super.onCreate()
    currentApplication = this
    }
    }

    不加@JvmStatic那么它只是个普通函数

    在java中调用:BaseApplication.Companion.currentApplication();

    在kotlin中调用:

java中的匿名内部类

java中的匿名内部类:就是创建一个类的对象,在kotlin中可用object的方式来实现这个效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
object HttpClient: OkHttpClient(){
private val gson = Gson()

@NonNull
fun <T> convert(json: String?, type: Type): T{
return gson.fromJson(json, type)
}

fun <T> get(path: String, type: Type, entityCallback: EntityCallback<T>){
val request = Request.Builder()
.url("https://api.hencoder.com/$path")
.build()
val call = this.newCall(request)
//此处实现的是java中的匿名内部类的效果
call.enqueue(object: Callback{
override fun onFailure(call: Call, e: IOException){
entityCallback.onFailure("网络异常")
}
override fun onResponse(call: Call, response: Response){
val code = response.code()
when (code) {
// code>=200 && code <=299
in 200..299->{

}
else -> {

}
}
}
})
}
}

kotlin中没有强制捕获异常

  • Java中某个sdk的类在库中可以相互引用,但不想被外面的调用类看到,在这个类的注释中添加 {@hide}

  • Kotlin中想实现上述的功能,在类声明时添加 internalinternal class 类名{}

  • Kotlin中加abstract(类)、open(方法)、override(方法)才可以被继承或重写,普通类默认是final不可以被继承

  • 主构造器,把构造器放到类名后面。原来的次构造器方法名改成init

    kotlin会按代码顺序把init成员变量放入类中

    简化:主构造器 contract(var data: String?)其中加var会默认生成一个data的成员变量【不加var就不是成员属性

  • enum class 是写枚举类; data class 写数据类(就不用重复写hashcode、equals)

  • 解构:val(构造参数1, 构造参数2, 构造参数3)=execute()一次性得到三个值【顺序跟构造器中的参数是一一对应的】

    如果某参数不想要使用结构得重写对应的 Component1()、Component2()、Component3()

    在java中要这样:

    val response = execute()

    val body1 = response.body

    val code1 = response.code

    val user1 = response.user

    在kotlin中用解构可一次性得到三个值 val(body,code,user) = execute()

  • 自定义操作符

    加 operator

  • 遍历写法简化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    //原来
    val playbackLessons = ArrayList<Lesson>()
    for(lesson in lessons){
    if(lesson.state === Lesson.State.PLAYBACK){
    playbackLessons.add(lesson)
    }
    }

    //简化
    lessons.forEach({lesson: Lesson ->
    if(lesson.state === Lesson.State.PLAYBACK){
    playbackLessons.add(lesson)
    }})
    //最后一个传入参数是lambda,那么大括号可以挪外面
    lessons.forEach(){lesson: Lesson ->
    if(lesson.state === Lesson.State.PLAYBACK){
    playbackLessons.add(lesson)
    }}
    //传入参数只有一个lambda,那么小括号可以省略
    lessons.forEach{lesson: Lesson ->
    if(lesson.state === Lesson.State.PLAYBACK){
    playbackLessons.add(lesson)
    }}
    //类型可以推导,简化
    lessons.forEach{lesson ->
    if(lesson.state === Lesson.State.PLAYBACK){
    playbackLessons.add(lesson)
    }}
    //kotlin中lambda只有一个参数,那么参数可以省略
    lessons.forEach{
    if(it.state === Lesson.State.PLAYBACK){
    playbackLessons.add(it)
    }
    }
    //如果学过rxjava,看到forEach会联想到filter操作符,简化
    val filter = lessons.filter{it.state === Lesson.State.PLAYBACK}
  • 循环

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //循环100次,输出0..99
    fun main(){
    //传入两个参数,最后一个参数是lambda所以可以提取到小括号外,lambda只有一个参数所以省略成it
    repeat(100){
    println(it)
    }


    //java中的fori,在kotlin中有in的写法
    val array = arrayOf(1,23,452,213,23,1)
    for (i in 0..(array.size-1)){}
    //这种写法不优雅,改成
    for (i in 0 until array.size){}
    //实际是 for(i in 0.until(array.size)){},点until跳过去可以看到是加infix的扩展函数。使得可以写成上面的形式让代码可读性更高点
    //此处array自带一个方法,可以返回一个区间,简化
    for (i in array.indices){}

    }
  • 函数不想被别的地方调用,函数可以写在另一个函数中,只能被外部函数访问到

    函数嵌套会在外面函数每次被调用的时候生成一个额外对象。所以要考虑外面函数是不是被频繁调用,如果被频繁调用那么不适合这种写法(会额外生成很多临时对象)

  • 不想让外面更改某个变量,加 private set

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    //原来:
    class BaseApplication : Application(){
    companion object{
    private lateinit var currentApplication: Context

    @JvmStatic
    fun currentApplication(): Context{
    return currentApplication
    }
    }

    override fun onCreate(){
    super.onCreate()
    currentApplication = this
    }
    }


    //更改成
    class BaseApplication : Application(){
    companion object{
    lateinit var currentApplication = Context
    private set
    }
    override onCteate(){
    currentApplication = this
    }
    }
    //kotlin中调用
    BaseApplication.currentApplication
    //java中调用
    BaseApplication.Companion.getCurrentApplication()


    //如果java中调用不想加Companion,那么加@JvmStatic
    class BaseApplication: Application(){
    companion object{
    @JvmStatic
    lateinit var currentApplication = Context
    private set
    }
    override onCreate(){
    currentApplication = this
    }
    }
    //java中调用
    BaseApplication.getCurrentApplication()

Kotlin知识体系图

基础

基本数据类型

  • 整型

    • Byte(8)

    • Short(16)

    • Int(32)

    • Long(64)

      默认整数类型为Int,需要在后面标记L或l

  • 浮点型

    • Float(32)

      默认浮点数类型为Double,需要在浮点数后标记f或F

    • Double(64)

  • 布尔型

  • Boolean

  • 字符型

    • Char

      字符用 Char 表示,不能直接当做数字

  • 显式转换

    • toByte(): Byte

    • toShort(): Short

    • toInt(): Int

    • toLong(): Long

    • toFloat(): Float

    • toDouble(): Double

    • toChar(): Char

  • 支持的进制

    • 二进制

    • 十进制

    • 十六进制

    • 注意:不支持八进制

  • 注意

    • 不显式转换的情况下不能把较小类型的变量赋值给较大类型

    • ===:内存地址值相等

    • ==:值相等

    • 当需要可空引用时,像数字、字符会被装箱。装箱操作不会保留同一性。

基本数据类型名称 Kotlin的数据类型 Java的数据类型
字节型 Byte(8) byte和Byte(8)
整型 Int(32) int和Integer(32)
长整型 Long(64) long和Long(64)
浮点型 Float(32) float和Float(32)
双精度 Double(64) double和Double(64)
布尔型 Boolean(8) boolean和Boolean(8)
字符型 Char char(16)
字符串 String String

  • 声明:package package.name

  • 如果没有显示的声明包名,则使用默认的命名空间

  • 和 Java 不同的是:目录与包的结构无需匹配:源代码可以在文件系统的任意位置

  • 如果两个文件的包名一致,无需导入可以直接使用对方的类和顶层函数、属性

  • import 的使用方式

    • import package.name.*

      导入包下的所有顶层的类、函数、属性

    • import package.name.funname

      导入包下的具体某个顶层函数

    • import package.name.propertyname

      导入包下具体的某个顶层属性

    • import package.name.classname

      导入包下具体的某个类

    • import package.name.classname.*

      导入某个类下的所有的成员

  • 注意

    • 在同一个包下不能存在相同的函数、属性、类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      //A.kt
      package cn.intozhou.bean
      val a = 1
      fun sayHello(){

      }

      class person1
      //B.kt
      package cn.intozhou.bean

      val a = 2
      fun sayHello(){

      }

      class person1

      //上面的写法是错误的,因为在package相同的情况下函数、类、属性不能相同。这里会编译时会抛出:sayHello a person1 已经在 A.kt 文件中定义
    • 如果在一个文件中导入不同目录下的相同的类、函数、属性,可以使用关键字 as 为不同命名空间下的类、函数、属性起别名

函数

  • 无返回值的函数

    1
    2
    3
    4
    5
    6
    fun main(args:Array<string>){
    }

    //等同
    fun main(args:Array<string>):Unit{
    }
  • 有返回值的函数

    1
    2
    3
    fun add(x:Int,y:Int):Int{
    return x + y;
    }
  • 返回值可为空的函数

    1
    2
    3
    fun parseInt(str:String):Int?{
    return str.toIntOrNull()
    }
  • 表达式主体函数

    1
    fun test(x:Int,y:Int) = if(x > y)  x else  y
  • 有默认参数值的函数
    有默认值的参数必须在无默认值参数的右边

    1
    2
    3
    fun test(name:String,age:Int = 18):String{
    return "姓名:$name,年龄:$age";
    }

变量

  • var(可变变量)
    在其初始化赋值后可再次赋值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    fun main(args:Array<string>){
    var x = 0;//根据值自动推断变量类型
    x = 1;//再次赋值Int类型的值,ok
    x = "3"//错误。只有在初始赋值的时候会自动推断变量的类型,在此之后的所有赋值的数据类型必须与第一次赋值的类型相同

    var y :String = ""//显示声明变量的类型并赋值
    var b :Boolean//如果声明变量但不赋初始值则必须显示声明变量的类型
    b = true
    }
  • val(不可变变量)
    在初始化赋值后不可再赋值的的变量

    1
    2
    3
    4
    fun main(args:Array<string>){
    val PI = 3.141596253f
    PI = 4//错误,不可再次赋值
    }
    1
    2
    3
    4
    5
    6
    7
    class lessonPresenter{
    //使用伴生对象的形式,让常量是静态的
    companion object{
    //加const让常量变成是编译期常量
    const val LESSON_PATH = ‘lessons
    }
    }

字符串模板

1
2
3
4
5
fun main(args:Array<string>){
val name = "intozhou"
val age = 23
println("姓名$name,年龄:$age,${if(age > 18) "已经成年了" else "未成年不予录用"}");
}

表达式

  • if表达式

    1
    2
    3
    fun test3(age:Int){
    println(if(age < 18) "未成年" else if (age >= 18 &amp;&amp; age < 30) "青年" else "中年、老年")
    }

    if 既可以作为语句来使用,也可以作为表达式来使用

    如果你使用if作为表达式来使用,那么 else 分支是必须要存在的

  • when表达式

    1
    2
    3
    4
    5
    6
    7
    8
    fun describe(obj: Any): String =
    when (obj) {
    1 -> "One"
    "Hello" -> "Greeting"
    is Long -> "Long"
    !is String -> "Not a string"
    else -> "Unknown"
    }

    使用方式

    • 有参数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      fun  getMnemonic(color: Color) =
      when(color){
      Color.RED -> "Richard"
      Color.ORANGE -> "Of"
      Color.YELLOW -> "York"
      Color.GREEN -> "Gave"
      Color.BLUE -> "Battle"
      Color.INDIGO -> "In"
      Color.VIOLET -> "Vain"
      }
    • 无参数

      1
      2
      3
      4
      5
      6
      7
      8
      //不带参数的when
      fun mixOptimized(c1 : Color, c2 : Color) =
      when{
      (c1 == RED &amp;&amp; c2 == YELLOW) || (c1 == YELLOW &amp;&amp; c2 == RED) -> ORANGE
      (c1 == YELLOW &amp;&amp; c2 == BLUE) || (c1 == BLUE &amp;&amp; c2 == YELLOW) -> GREEN
      (c1 == BLUE &amp;&amp; c2 == VIOLET) || (c1 == VIOLET &amp;&amp; c2 == BLUE) -> INDIGO
      else -> throw Exception("Dirty color")
      }
    • 一条分支多个选项

      1
      2
      3
      4
      5
      6
      7
      //在一个when分支上合并多个选项
      fun getWarmth(color: Color) =
      when(color){
      RED,ORANGE,YELLOW -> "warm"
      GREEN -> "neutral"
      BLUE,INDIGO,VIOLET -> "cold"
      }
    • 任意参数

      1
      2
      3
      4
      5
      6
      7
      8
      //when 表达式的判断条件可以是任何对象
      fun mix(c1:Color,c2:Color) =
      when(setOf(c1,c2)){
      setOf(RED,YELLOW) -> ORANGE
      setOf(YELLOW,BLUE) -> GREEN
      setOf(BLUE,VIOLET) -> INDIGO
      else -> throw Exception("Dirty color")
      }
    • 取代了 switch 操作符,比switch更强大
      when 可以接受任意对象为参数,switch 只支持 常量:枚举常量、字符串常量、数字常量

    • when 既可以作为语句来使用,也可以作为表达式来使用

    • 如果你使用 when 作为表达式来使用,那么 else 分支是必须要存在的,除非编译器确认分支已经包含所有的可能性

    • 替代 if…else if…else…,可以不提供参数,所有的分支条件都是简单的布尔表达式

      1
      2
      3
      4
      5
      6
      7
      when{
      x % 2 == 0 -> println("是偶数")
      x % 2 == 1 -> println("是奇数")
      else ->{
      println("不是奇数页不是偶数")
      }
      }
  • 在Kotlin中不支持三元表达式

  • 在kotlin中不支持switch

空值和null检查

1
2
3
4
5
6
7
8
9
fun getStringLength(obj: Any): Int? {
if (obj is String) {
// `obj` 在该条件分支内自动转换成 `String`
return obj.length
}

// 在离开类型检测分支后,`obj` 仍然是 `Any` 类型
return null
}

类型检查和智能类型转换

循环

  • for循环

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    fun main(args: Array<string>) {
    //sampleStart
    val items = listOf("apple", "banana", "kiwi")
    for (item in items) {
    println(item)
    }
    //sampleEnd
    }

    fun main(args: Array<string>) {
    //sampleStart
    val items = listOf("apple", "banana", "kiwi")
    for (index in items.indices) {
    println("item at $index is ${items[index]}")
    }
    //sampleEnd
    }
  • while循环

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    fun main(args: Array<string>) {
    //sampleStart
    val items = listOf("apple", "banana", "kiwi")
    var index = 0
    while (index < items.size) {
    println("item at $index is ${items[index]}")
    index++
    }
    //sampleEnd
    }
  • do…while()循环

  • break、continue

    • break: 跳出循环

    • continue: 跳出本次循环,执行下次循环

  • 标签处返回

    • label@

区间

  • 是否在区间

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    val x = 9
    val y = 10

    //是否在区间内
    if(x in 0..y){
    println("x 在区间内")
    }
    println("--------")

    //是否不在区间内
    if(x !in 0..y){
    println("x 不在区间内")
    }

    if(x in x..y)

  • 迭代区间

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    fun main(args: Array<string>) {
    //sampleStart
    for (x in 1..5) {
    print(x)
    }
    //sampleEnd
    }

    fun main(args: Array<string>) {
    //sampleStart
    for (x in 1..10 step 2) {
    print(x)
    }
    for (x in 9 downTo 0 step 3) {
    print(x)
    }
    //sampleEnd
    }

    for(x in items)

集合

  • List

    1
    val list = listOf("A","B","D")
  • Set

    1
    val set = setOf("A","B","D","E")

类和对象

类与继承

  • 类声明

    • 可是使用 class 关键字来声明类。类声明由类名、类头(指定其类型参数、主构造函数等)和由大括号包围的类体构成。类头和类体都是可选的; 如果一个类没有类体,可以省略花括号。
  • 类成员

    • 属性

    • 函数

    • 嵌套类和内部类

    • 对象声明

构造函数

  • 构造函数的可见性是 public 的,如果不希望外部创建改类的实例则需要显式的使用可见性修饰符来修饰构造函数

  • 主构造函数

    • 只能有一个

    • 主构造函数是类头的一部分:它跟在类名(和可选的类型参数)后。

    • 使用 constructor 关键字来声明主构造函数。如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字。

    • 主构造函数不能包含任何的代码。初始化的代码可以放到以 init 关键字作为前缀的初始化块

    • 主构造的参数可以在初始化块中使用。它们也可以在类体内声明的属性初始化器中使用

    • 主构造函数中声明的属性可以是可变的(var)或只读的(val

    • JVM 上,如果主构造函数的所有的参数都有默认值,编译器会生成 一个额外的无参构造函数,它将使用默认值

    • 如果构造函数有注解或可见性修饰符,这个 constructor 关键字是必需的,并且这些修饰符在它前面

  • 次构造函数

    • 可以有一个或多个

    • 如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用 this 关键字即可

    • 如果一个类没有主构造函数和次构造函数,它会有一个不带参数的主构造函数

继承

  • Kotlin 中所有类都有一个共同的超类 Any,这对于没有超类型声明的类是默认超类

  • 如果该基类有一个主构造函数,其子类型可以(并且必须) 用(基类型的)主构造函数参数就地初始化。如果类没有主构造函数,那么每个次构造函数必须使用 super 关键字初始化其基类型,或委托给另一个构造函数做到这一点

  • 类上的 open 标注与 Javafinal 相反,它允许其他类从这个类继承。默认情况下,在 Kotlin 中所有的类都是 final-要么为继承而设计,并提供文档说明,要么就禁止继承

  • 覆盖方法

    • Java 不同,Kotlin 需要显式标注可覆盖的成员(我们称之为开放)和覆盖后的成员

    • 首先只能覆盖标注了 open 的成员,其次如果覆盖则必须显式地使用 override 标注覆盖后的成员

    • 覆盖成员的前提是类是开放的

属性与字段

接口

可见性修饰符

扩展

数据类

密封类

泛型

嵌套类

枚举类

  • 使用 enum class 来定义一个枚举类

    1
    enum class Color
  • enum 是软关键字,只有在和class一起使用时才具有特殊含义

  • 枚举常量和方法之间要使用分号(;)分割,这是必须的

  • 如果枚举类定义了主构造函数,那么每个枚举常量在创建时必须指定起初始值

    1
    2
    3
    4
    5
    6
    7
    enum class Color(var r :Int,var g:Int,var b:Int){
    RED(255,0,0),ORANGE(255,165,0),
    YELLOW(255,255,0),GREEN(0,255,0),
    BLUE(0,0,255),INDIGO(75,0,130),
    VIOLET(238,130,238);//这里的分号是必须的,用于划分常量列表和方法
    fun rgb() = (r * 256 + g) * 256 + b
    }

对象

委托

委托属性