Kotlin学习笔记
Kotlin学习笔记

Kotlin学习笔记

变量声明

内置数据类型

只读变量

val声明只读变量,但非常量(详见编译时常量);var声明普通变量。

类型推断

变量声明时允许不给出变量类型,编译器会自动推断变量类型。

编译时常量

区别于只读变量,编译时常量只能声明在函数之外,且在编译时值就已经确定。
而只读变量可以在函数内声明,并进行一次赋值。
编译时常量的声明方法是:const val valName,即在val关键字前加const关键字。

range表达式

使用:varName in a..b可定义一个range表达式,其返回一个布尔值,用于判定变量是否处于某一范围内。
也可使用varName !in a..b来判断变量是否处于某一范围内。
其中a..b可替换为诸如List或者Array之类的对象。

IntRange

使用:(1..24)可以返回一个IntRange类型的变量,其代表一个整数范围。
也可以使用until关键字:1 until 24,其中左右两边的数字可以用变量代替。
其有一个shuffled()成员函数可以用于打乱IntRange内的元素,然后用last()函数可以取出其最后一个元素(相当于随机数)。

when表达式

类似于其他语言中的switch表达式。
其基本语法如下:

//可以基于条件返回对应的值
val varName = when(condition){
    "case1" -> "result1"
    "case2" -> "result2"
    else -> "default"
}

//或者只是作为类似switch的功能
when(condition){
    1 ->{
        println("1")
    }
    //使用range表达式
    in 2..4 -> {
        println("2到4")
    }
    else -> {
        println("default")
    }
}

字符串模板

在字符串引号内,可以直接放入变量。
其使用方式如下:

//使用$符号引入字符串模板
val name = "zxy"
println("Your name is $name")
//$后还可以使用{}来引入表达式
val flag = true
println("The flag is ${if(flag) "true" else "false"}")

函数头

Kotlin的函数支持默认参数(给参数指定缺省值)和具名传参(在传递参数时指定要传给的形参名)

Unit类型

相当于其他语言中的void类型。但特点是可以作为类型在泛型中使用。

Nothing类型

类似Java中的封装类Void。区别于Unit,函数返回一个Nothing类型时,其不可被访问,也不能在泛型中使用。但是Nothing?类型可以存放一个null值。

反引号函数名

用反引号括起来的函数名当中可以使用特殊字符诸如空格、中文,等等;还可以使用Kotlin中的关键字作为函数名。
比如:`~~**一个 fun**~~`

匿名函数

匿名函数通常用于传给其他函数或作为返回值返回,其语法如下:

//自定义一个数相运算的方法
fun countFunc(op1:Int, op2:Int, method:(Int, Int)->Int){
    return method(op1,op2)
}
//传入参数1和2,并要求用给定方法返回值(看起来多余,只是用于演示)
val sum= countFunc(1, 2, {a , b ->
    a + b
})

countFunc的第三个参数接受一个“接受两个int类型参数,并返回一个int类型”的函数。
在传入参数时,可以传入匿名函数。
匿名函数用大括号括起,形参列表位于箭头左侧,箭头之后是表达式,匿名函数的返回值由最后一行表达式的值确定,所以return需要省略。
另外值得注意的是,当匿名函数只需要一个参数时,箭头和箭头前面的形参列表可以省略,且形参会自动用it表示。而不需要参数时,箭头也可以省略。
还有就是,当一个函数的参数有且只有一个,且接受的是函数类型时,调用该函数时传递参数时,圆括号可以省略

lambda

匿名函数称为lambda,它的定义称为lambda表达式,它返回的数据称为lambda结果。

函数内联

lambda在JVM中会创建一个独立的对象,会带来很大的开销,kotlin用函数内联的机制来优化这个问题。
一般做法是,在接受参数中有函数的函数前的fun关键字前,加上inline关键字。

函数引用

当想将具名函数作为参数传入一个接受函数作为参数的函数时,可以使用函数引用。
具体做法是,在传参时,用函数名前加上双引号:

fun funcA(pram:(Int)->Int):Int{
    //...
}

fun funcB(pram:Int):Int{
    //...
}

fun main(){
    funcA(::funcB)
}

闭包

闭包的概念与匿名函数联系紧密。匿名函数运行时,引用了其作用域之外的数据时,那些数据也会被囊括进匿名函数当中。这种现象称为闭包。

安全调用操作符

可空类型在调用其成员函数时,需要用到安全调用操作符,即把.换成?.,表明我们明确知道可能会发生空指针异常的风险。当可空类型变量的值为null时,该调用会返回一个null。

对象的标准函数

Kotlin中对象内置5个标准函数,分别是:let、also、with、run、apply。
标准函数的实现基于lambda。

let函数

// 作用1:使用it替代object对象去访问其公有的属性 & 方法
object.let{
   it.todo()
}

// 作用2:判断object为null的操作
object?.let{//表示object不为null的条件下,才会去执行let函数体
   it.todo()
}

// 注:返回值 = 最后一行 / return的表达式

also函数

类似let函数,但区别在于返回值:

  • let函数:返回值 = 最后一行 / return的表达式
  • also函数:返回值 = 传入的对象的本身
// let函数
var result = mVar.let {
               it.function1()
               it.function2()
               it.function3()
               999
}
// 最终结果 = 返回999给变量result

// also函数
var result = mVar.also {
               it.function1()
               it.function2()
               it.function3()
               999
}
// 最终结果 = 返回一个mVar对象给变量result

with函数

作用
调用同一个对象的多个方法 / 属性时,可以省去对象名重复,直接调用方法名 / 属性即可
应用场景
需要调用同一个对象的多个方法 / 属性

// 此处要调用people的name 和 age属性
val people = People("carson", 25)
with(people) {
println("my name is $name, I am $age years old")
}

run函数

run函数结合了let、with两个函数的作用,即:

  1. 调用同一个对象的多个方法 / 属性时,可以省去对象名重复,直接调用方法名 / 属性即可
  2. 定义一个变量在特定作用域内
  3. 统一做判空处理
// 此处要调用people的name 和 age属性,且要判空
val people = People("carson", 25)
people?.run{
    println("my name is $name, I am $age years old")
}

apply函数

作用 & 应用场景

与run函数类似,但区别在于返回值:

  • run函数返回最后一行的值 / 表达式
  • apply函数返回传入的对象的本身

应用场景

对象实例初始化时需要对对象中的属性进行赋值 & 返回该对象

// run函数
val people = People("carson", 25)
val result = people?.run{
    println("my name is $name, I am $age years old")
    999
}
// 最终结果 = 返回999给变量result

// apply函数
val people = People("carson", 25)
val result = people?.apply{
    println("my name is $name, I am $age years old")
    999
}
// 最终结果 = 返回一个people对象给变量result

takeIf函数

takeIf函数接受一个lambda表达式,当lambda表达式返回的值为true时返回对象本身,为false时返回null。

val result = File("D:/a.txt").takeIf{ it.exist() && it.canRead }?.readText()

takeUnless函数

正好和takeIf函数相反,当lambda表达式返回的值为false时返回对象本身,为true时返回null。

非空断言操作符

当一个对象为空,调用这个对象的成员函数时,我们不想其返回null而是抛出异常时,需要用到非空断言操作符:!!.,当对象真为空时,会抛出异常。

空合并操作符

空合并操作符:?:,不同于c++,这个操作符是个二元操作符,左侧对象为空时,该表达式的值会是右边的值;左侧对象不为空时,该表达式就是左边的值。

先决条件函数

==与===

当比较两个字符串时,==用于比较内容是否相同,===用于比较是否是同一对象。

List对象

创建一个List对象通常调用listOf方法,listOf方法接受元素作为参数,返回一个List对象。
获取List中的元素时,可以使用脚标,也可以使用安全函数,如list.getOrElse(index){ "default" },还有list.getOrNull(index)

可变List

listOf创建的List不可修改,要创建可修改的List,要调用mutableListOf函数。
另外,toList和toMutableList函数可以用来在两者之间转换。
添加或删除元素可以使用add和remove函数。
或者使用+=或-=

field关键字

kotlin在定义类成员变量时,会自动生成对应的setter和getter,如果要覆盖自动生成的getter和setter,则要在变量声明下写set和get函数,get和set函数当中,用field关键字访问变量对象,具体如下:

class ClassName{
    var varName = "name"
        get() = field.capitalize()//获取变量时返回首字母大写的版本
        set(value){
            field = value.trim()//赋值时去掉空格
        }
}

fun main(){
    var obj = ClassName()
    print(obj.varName)//会自动调用变量的getter方法
    obj.varName = " hello "//会自动调用变量的setter方法
    print(obj.varName)//会自动调用变量的getter方法
}

主构造函数

Kotlin当中的构造函数可以直接将类声明写成函数的形式,括号中给定参数列表,其中参数的名字开头用下划线,表示是临时变量:

class ExampleClass(
    _a:Int, 
    _b:Int
){
    val a = _a
    val b = _b
}

fun main(){
    val obj = ExampleClass(1,2)
}

也可以直接在圆括号中定义属性:

class ExampleClass(
    var a:Int, 
    var b:Int
){
    //...
}

fun main(){
    val obj = ExampleClass(1,2)
}

次构造函数

除了主构造函数,在类声明里还可以添加次构造函数,需要用到关键字constructor,具体语法如下:

class ExampleClass(
    var a:Int, 
    var b:Int
){
    constructor(_a:Int):this(_a,0){
        //自定义的逻辑
        this.a = _a + 1
    }
}

fun main(){
    val obj1 = ExampleClass(1,2)
    val obj2 = ExampleClass(1)//使用次构造函数
}

其中this()是调用主构造函数。

初始化块

在类声明中使用init关键字的代码块会在构造函数运行时运行:

class ExampleClass(
    var a:Int, 
    var b:Int
){
    constructor(_a:Int):this(_a,0){
        //自定义的逻辑
        this.a = _a + 1
    }
    init{
        //初始化操作
    }
}

fun main(){
    val obj1 = ExampleClass(1,2)
    val obj2 = ExampleClass(1)//使用次构造函数
}

初始化顺序

这么多初始化的方法,他们的执行顺序是怎样的?

通过反编译成java代码,一切就一目了然了:

延迟初始化

lateinit关键字用于声明延迟初始化,允许类构造时不初始化该变量。加在var关键字之前。
这里会牵扯到一个检查变量是否初始化的问题,使用::varName.isInitialized,可以返回一个布尔值,表明变量是否已经初始化。

惰性初始化

区别于延迟初始化,惰性初始化需要实现给出初始化方法,但是在变量首次被用到的时候才会被初始化。
声明惰性初始化使用by lazy关键字,具体使用方法:

class ExampleClass{
    val varName by lazy{loadVar()}
    private fun loadVar(){
        return 123
    }
}

上面的代码中varName变量在初次被使用时会通过loadVar函数初始化。

类的继承

Kotlin当中类默认不可被继承,需要类可以被继承要使用open关键字。
open关键字加在class关键字之前。
继承方式是类名后加冒号,冒号后接基类名。

基类名后接圆括号,可以直接调用基类的构造函数来初始化派生类。

同样的,基类中的函数前也要加上open关键字才能被覆写。
派生类中覆写基类的函数需要在函数前加上override关键字。

类型检测

Kotlin当中可以用is关键字来判断变量是否是某个类型:varName is TypeName,这个表达式会返回一个布尔值

类型转换

使用as关键字可以进行类型转换:varName as TypeName

object关键字创建单例

将class改成object,可以生成一个该类的单例对象。

object还可以用来声明一个匿名对象,类型与java的匿名内部类,具体用法如下:

open class player{
    open fun load() = "loading nothing..."
}

fun main(){
    val p = object : player(){//派生自player类的子类,但是只在这里使用
        override fun load() = "anonymous class load..."
    }
    println(p.load())
}

伴生对象

当想将某个对象的初始化和一个类实例捆绑在一起,可以考虑使用伴生对象,声明伴生对象使用companion修饰符,一个类里只能有一个伴生对象:

open class ConfigMap{
    //只有初始化ConfigMap类或调用load函数时,伴生对象的内容才会载入
    //而且无论ConfigMap实例化多少次,伴生对象始终只有一个实例存在
    companion object{
        private const val PATH = "XXX"
        fun load() = File(PATH).readBytes()
    }
}

fun main(){
    //可以直接通过类名访问到伴生对象中的内容
    ConfigMap.load()
}

数据类

在class关键字前加data关键字可将一个类声明为数据类
数据类只用于储存数据,不储存方法。
使用数据类可以得到重写版本的toString方法、equals方法(可以直接用==判断两个数据类所存的数据是否相同)

数据类还提供一个copy函数,可以简单地得到类实例的副本,copy函数的参数可以是主构造函数的参数(不必给出全部参数),提供参数可以初始化新的内容,剩余内容会和被拷贝的对象一致。需要注意的是copy函数调用给你的是主构造函数,不会调用次构造函数。

数据类还默认支持解构语法↓

解构语法

一个类的对象想要给多个变量赋值,需要用到解构语法,需要写component函数:

//普通类支持解构语法需要写component函数
class Position(val x:Int, val y:Int){
    //数字需要按顺序
    operator fun component1() = x
    operator fun component2() = y
}
//数据类默认支持解构语法
data class PositionData(val x:Int, val y:Int){

}
fun main(){
    val (x,y) = Position(100,200)//给x和y两个变量赋值
    val (x,y) = PositionData(100,200)//给x和y两个变量赋值
}

数据类默认支持解构语法!

运算符重载

运算符重载需要重写对应的函数,用operator fun关键字:

data class PositionData(val x:Int, val y:Int){
    operator fun plus(other:PositionData) = PositionData(x+other.x, y+y.other)
}
fun main(){
    val (x,y) = PositionData(100,200) + PositionData(200,100)
    //可以得到x=300,y=300
}

枚举类

class关键字前加enum关键字,可将类声明为枚举类:

enum class Direction{
    EAST,
    WEST,
    NORTH,
    SOUTH
}
fun main(){
    println(Direciton.EAST)
    println(Direciton.EAST is Direciton)//true
}
4 1 投票
打个分吧!
订阅评论
提醒
guest
2 评论
最多点赞
最新 最旧
内联反馈
查看所有评论
2
0
希望看到您的想法,请您发表评论x