Kotlin 学习笔记

长路漫漫 唯剑作伴

此篇仅用来记录参考学习,如有错误还请指正

更新日期:2023/10/30


1.关键字

  • var:定义变量,默认是private的,并且会生成get(),set()方法,调用时通过方法访问。
  • val:定义常量,默认是private的,可见性为private final static,并且会生成get()方法,调用时通过方法访问。
  • lateinit var :延迟初始化变量,默认不允许为空,延迟但必须要初始化的变量
  • const val: 定义常量,相当于Java public final static 只能用在顶级属性,以及object对象的属性中,可直接访问,在定义常量时相较于val 减少了函数调用。
  • fun:定义方法,默认是public final
  • Unit:默认方法返回值,类似于Java中的void,可以理解成返回一个没有意义的值
  • vararg:可变参数
  • $:字符串模板(取值),用于替代Java 字符串拼接操作
  • 位运算符:or(按位或),and(按位与),shl(有符号左移),shr(有符号右移),ushr(无符号右移),xor(按位异或),inv(按位取反)
  • in:在某个范围中
for (s in mTitles) {
//遍历mTitles
 }
  • downTo:递减,循环时可用,每次减1
  • step:步长,循环时可用,设置每次循环的增加或减少的量
  • when:Kotlin中增强版的switch,可以匹配值,范围,类型与参数
  • is:判断类型用,类似于Java中的instanceof()
  • as:可以用于对象的类型转换,取代Java中的强转
  • open :在Kotlin开发中类和方法默认不允许被继承和重写,等同于Java中用 final 修饰类和方法。如果在Kotlin 中类和方法想被继承和重写,需添加open 关键字修饰。
  • !!: 加在变量后如果对象为null,一定会报异常,与 ?相反,相当于直接用Java 的方式
  • ?: :空合并操作符,相当于你的备用选项,如val len= value?.length ?: 0 ,如果value为null,则len等于你的默认值 0
  • :: :表示把一个方法当做一个参数,传递到另一个方法中进行使用,通俗的来讲就是引用一个方法
  • ==:用于比较两个变量值的大小,同Java 中equals()
  • ===: 用于比较对象的地址是否相同
  • """: 所见即所得,字符串在你编辑器中什么效果,显示出来就是什么效果
  • inline:通过内联化 lambda 表达式可以消除压栈出栈的开销
  • noinline:只想被(作为参数)传给一个内联函数的 lamda 表达式中只有一些被内联,你可以用 noinline 修饰符标记某些lambda表达式禁止内联,定义了noinline 的lambda表达式将不能直接调用 return ,否则会报错
  • crossinline:也可以限制lambda表达式将不能直接调用 return ,它与 noinline 的区别在于使用crossinline的lambda仍然是inline的
  • *:在 Kotlin 中,* 号代表展开操作符(Spread Operator),它可以用于将数组的元素作为多个参数传递给函数或构造函数。
例如:
val array = arrayOfNulls<String>(2)
array[0] = "Hello"
array[1] = "World"

someFunction(*array)

下面有详细介绍

2.数据类型

Kotlin中数字相关的内置类型有: byte、short、Int、Long、Float、Double
需要注意的:

  1. 没有自动向上转型,比如Int转Long,需要自己调toXxx方法转;
  2. Long类型结尾必须为大写的L,不能为小写,比如1024L
  3. 字符Char不是Number,用单引号来声明,比如’c’,不能像Java一样直接拿来当数字使,如果你想把Char的值给Int,需要调toInt()方法
  4. Boolean的值为true或false
  5. Kotlin不支持8进制,十六进制0x开头,二进制0b开头
  6. 位运算符,Java中的与或运算符用:|和&,kotlin中使用or和and关键字来替代其他运算符也有分别的关键字替代:shl(有符号左移),shr(有符号右移),ushr(无符号右移),xor(按位异或),inv(按位取反)

3.关键字及函数解读

?


可空类型 例如 :Int?

Any


Any即相当于 Java 中的Object类
所有非空类型的基类 Any
any 的可空类型: any?

Unit


当函数没有返回值时,使用Unit进行修饰
当函数没有返回值试 通常无需显示声明Unit 而是忽略不写
Unit本质是一个全局单例object实例对象,所以它是一个Any类型数据,它的效果等同于 Java 语言的void关键字

Nothing


表示一个永远不存在的值,可认为是 空类型

当函数返回值类型声明为Nothing时,表示该函数永远不会正常终止,通常表现为函数运行时永远会抛出异常

Nothing的这个特性通常有两方面用途:

  • 由于Nothing函数不会正常终止,因此如果能执行到调用Nothing函数,那么其后面的代码就不会被执行到,这些代码可被优化掉
  • 通常使用Nothing的这个特性用来声明一些暂未完成的函数

Nothing与Unit的区别在于:Unit是一个真正的实例对象,其类型为Any,只是它表示返回值内容为空,但我们仍然能通过val ret = funcReturnsUnit()来获取函数返回值,而Nothing表示函数抛出异常,没有返回值。

object


在类中:
用object 修饰的类为静态类,里面的方法和变量都为静态的。
实现接口:
在java中实现接口为new callback(){ } 方式
而在Kotlin中实现接口方式之一是 object : callback { }

companion object


companion object只能定义在class中,并且一个class中只能定一个一个companion object。companion object作用域为代码块,里面成员和方法可通过companion object直接调用(companion object会生成一个companion对象)

?:


空合并操作符 相当于 else 或 default

takeIf


someObject?.takeIf{ status }?.apply{ doThis() }

类似于比if多个判空,而且后面不加?的话apply里的代码不管status false or true 一定会执行!!!

apply函数


apply函数扩展了所有的泛型对象,在闭包范围内可以任意调用该对象的任意方法,并在最后返回该对象.

常用方式,用于对象的 配置,使用this引用对象

主要的作用:是可以用来简化初始化对象的功能。
特别需要注意的是apply函数中表示对象本身使用的是this关键字而不是it。

webSettings?.apply {
    setSupportZoom(true) //支持缩放,默认为true。是下面那个的前提。
    builtInZoomControls = true //设置内置的缩放控件。若为false,则该WebView不可缩放
    displayZoomControls = true; //隐藏原生的缩放控件
    blockNetworkImage = false;//解决图片不显示
    loadsImagesAutomatically = true; //支持自动加载图片
    defaultTextEncodingName = "utf-8";//设置编码格式
}

在这里插入图片描述

inline 内联


当一个函数被内联 inline 标注后,在调用它的地方,会把这个函数方法体中的所以代码移动到调用的地方,而不是通过方法间压栈进栈的方式。

换句话说:在编译时期,把调用这个函数的地方用这个函数的方法体进行替换。

inline 能带来的性能提升,往往是在参数是 lambda 的函数上。

inline 不适合在无参数的函数中, 适合在包含 lambda 参数的函数上。

  1. 不带参数,或是带有普通参数的函数,不建议使用 inline
  2. 带有 lambda 函数参数的函数,建议使用 inline

下面是一个使用 inline 关键字定义的简单函数示例:

inline fun measureTimeMillis(block: () -> Unit): Long { 
    val start = System.currentTimeMillis() 
    block() 
    return System.currentTimeMillis() - start 
}

在上面的代码中,measureTimeMillis 函数接受一个 Lambda 表达式参数,并返回其执行所需的时间。使用 inline 关键字定义该函数,它会在函数调用处直接将 Lambda 表达式中的代码拷贝下来,从而避免了函数调用的开销。

使用内联函数还可以帮助减少代码重复,提高代码的可读性和可维护性。下面是一个使用内联函数优化代码复用的示例:

inline fun <reified T : Activity> Context.startActivity() {
    val intent = Intent(this, T::class.java)
    startActivity(intent)
}

在上面的代码中,该内联函数扩展了 Context 类,可以直接启动指定类型的 Activity。使用 inline 关键字定义该函数,可以直接在函数调用处拷贝函数体中的代码。由于使用了泛型和 reified 关键字,该函数可以根据传入的参数类型自动推导 Class 对象,避免了代码中重复声明和转换类型的情况,提高了代码的可读性和可维护性。

noinline


noinline 是用来修饰函数参数的一个关键字。当一个函数参数被 noinline 修饰时,其对应的 Lambda 表达式将不能够被内联执行。
当编写高阶函数时,我们通常会接受一个 Lambda 表达式作为参数。默认情况下,Kotlin 编译器会尝试将这些 Lambda 表达式内联到函数中,以提高代码的执行效率。但是,在一些特殊情况下,我们可能需要 Lambda 表达式的特殊行为,例如将其作为一个参数传递给另一个函数等。这时可以使用 noinline 关键字来禁止编译器内联 Lambda 表达式。

inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    val result = mutableListOf<T>()
    for (t in this) {
        if (predicate(t)) {
            result.add(t)
        }
    }
    return result
}

fun main() {
    val numbers = listOf(1, 2, 3, 4, 5)
    val evenNumbers = numbers.filter { number ->
        number % 2 == 0
    }
}

在上面的示例中,我们定义了一个 filter() 函数,它接受一个 Lambda 表达式作为参数,并返回一个过滤后的列表。这个函数使用了 inline 关键字来告诉编译器,该函数的实现应该被内联。但是,我们在 Lambda 表达式参数前加上了 noinline 关键字,表示该 Lambda 表达式不应该被内联。

如果我们省略了 noinline 关键字,则编译器将会尝试内联 predicate 参数,这将导致代码出错。

总之,noinline 关键字用于指示编译器不要把一个 Lambada 表达式参数内联到高阶函数中去。这种情况通常在需要将 Lambda 表达式作为参数传递给另一个函数时出现。

crossinline


crossinline 是用来修饰函数参数的一个关键字。当一个函数参数被 crossinline 修饰时,其对应的 Lambda 表达式将不能够使用 return 关键字进行非局部返回。
当编写高阶函数时,我们通常会接受一个 Lambda 表达式作为参数。在 Lambda 中使用 return 关键字时,它通常会直接返回封闭函数,导致封闭函数中的代码无法得到执行。

fun doSomething(callback: () -> Unit) {
    Thread {
        callback()
    }.start()
}

fun main() {
    doSomething {
        if (somethingBadHappened) {
            return // 编译错误:return 不允许在这个上下文中使用
        }
        // 正常情况的处理逻辑
    }
}

在上面的代码示例中,doSomething 函数接受一个 Lambda 表达式参数,并在新线程中执行它。当 Lambda 中发生了一些错误时,我们希望它能够非局部返回,以提前结束整个线程。但是,由于默认情况下 Lambda 是允许使用 return 关键字进行非局部返回的,这将导致编译错误。

这时可以使用 crossinline 关键字来解决这个问题,它告诉编译器对于在 Lambda 中使用 return 关键字时要有不同的处理。使用 crossinline 关键字修饰之后,虽然可以在 Lambda 中使用 return 关键字,但是它只能在 Lambda 函数自身中进行局部返回操作,而不能对其它外围函数进行非本地返回操作。

fun doSomething(crossinline callback: () -> Unit) {
    Thread {
        callback()
    }.start()
}

fun main() {
    doSomething {
        if (somethingBadHappened) {
            return@doSomething // 在 Lambda 函数自身中进行局部返回
        }
        // 正常情况的处理逻辑
    }
}

在上面修改后的示例中,使用 crossinline 关键字修饰了 callback 参数,Lambda 中可以使用 return@doSomething 来在 Lambda 函数自身中进行局部返回,而不能直接返回封闭函数。

总之,crossinline 关键字用于修饰函数参数,可以在 Lambda 表达式中使用 return 关键字,但是只能在 Lambda 函数自身中进行局部返回操作,不能对其它外围函数进行非本地返回操作,以避免影响封闭函数中的代码执行。

run 函数


返回函数值是函数最后的一行,或者指定return表达式。

let 函数


调用某个对象的let函数,则将该对象作为函数的参数。在函数块中可以通过it指代对象。返回值是函数最后的一行,或者指定return表达

also 函数


调用某对象的also函数,在函数块内可以通过it指代该对象。返回值是对象的本身

with 函数


with函数和前面的几个函数的使用方式不同,它不再是以扩展的形式存在的,它是将对象作为函数的参数,在函数块内可以通过this指定该对象。返回值是函数最后的一行,或者指定return表达

as


:
 val any: Any = "abc"
 any as String
 print(any.length.toString())

by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED)


委托,懒加载
如:

//只有使用A时 {}里的代码才会被加载,并且下面代码是双重校验锁实现的单例
A by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { B() }

LazyThreadSafetyMode有三种:

  • SYNCHRONIZED同步:只会调用一次初始化方法。单例模式:懒汉式,线程安全
  • PUBLICATION:会调用多次初始化方法,但只有第一次的有效。
  • NONE:会调用多次,且会改变常量的值为最后一次的值。单例模式:懒汉式,线程不安全

密封类(Sealed class)


密封类(Sealed class)是一种特殊的类,用于表示受限的类继承结构。它允许定义一个类的层次结构,并限制该层次结构的子类集合。密封类的主要特点是其子类必须位于密封类的同一文件内,这使得密封类能够对可能的子类进行严格控制。

  1. 定义密封类:使用 sealed 修饰符来声明密封类。密封类可以有直接的子类,但是它不能被实例化。
sealed class Result
  1. 子类定义:在密封类的同一文件内,可以定义密封类的子类。子类可以拥有不同的属性、方法和状态。
class Success(val data: String) : Result()
class Error(val message: String) : Result()
  1. 使用密封类:可以使用 when 表达式来处理密封类的实例。当使用 when 表达式时,Kotlin 会检查是否处理了所有密封类的可能状态。这种特性使得保证代码的完整性更加容易,并且可以在编译时捕获缺失的分支。
fun handleResult(result: Result) {
    when (result) {
        is Success -> println(result.data)
        is Error -> println(result.message)
    }
}
  1. 密封类与继承:密封类本身是一个抽象的超类,因此可以扩展其他类并继承其他接口。但密封类的直接子类必须位于同一文件中,并且可以是密封类本身的嵌套类。
sealed class Result {
    class Success(val data: String) : Result()
    class Error(val message: String) : Result()
}
  1. 密封类的优势:密封类适用于描述有限的可能状态或类型的场景。它提供了一种优雅的方式来管理类的继承关系并限制可能的子类集合,如网络请求返回结果处理。

4.Kotlin扩展

Kotlin 可以对一个类的属性和方法进行扩展。
扩展是一种静态行为,对被扩展的类代码本身不会造成任何影响。
如:

fun receiverType.functionName(params){
    body
}

receiverType:表示函数的接收者,也就是函数扩展的对象
functionName:扩展函数的名称
params:扩展函数的参数,可以为NULL

5.数据结构

  • 数组(Array):用于存储一组相同类型、固定长度的数据。

  • 列表(List):用于存储一组有序的数据,支持重复元素。Kotlin 中的列表可以使用可变类型(MutableList)和不可变类型(List)来表示。

  • 集合(Set):用于存储一组无序、不重复的数据。Kotlin 中的集合可以使用可变类型(MutableSet)和不可变类型(Set)来表示。

  • 映射(Map):用于存储一组键值对数据,其中每个键都是唯一的。Kotlin 中的映射可以使用可变类型(MutableMap)和不可变类型(Map)来表示。

  • 字符串(String):用于存储一组字符序列。

  • 区间(Range):用于表示一段连续的数值区间,可以使用整型、字符型等类型来表示。Kotlin 中的区间可以使用闭区间(a…b)和半开区间(a until b)来表示。

  • 字符串模板(String Template):用于将字符串中的某些部分动态地替换为变量或表达式的值。

  • 元组(Tuple):用于将多个值组合在一起,形成一个不同类型、不同含义的元素集合。

Mutable和非Mutable的区别,以MutableList为例:

  • 可变性:MutableList 是可变类型的列表,支持添加、删除、修改元素等操作;而 List 是不可变类型的列表,不能添加、删除、修改元素。

  • 接口: MutableList 实现了 List 接口,因此可以使用 List 接口中的方法和属性,如 get(),size(),contains() 等,同时还额外提供了可以修改列表元素的方法,包括 add(),remove(),set() 等。

  • 效率:MutableList 操作效率较高,因为它是基于可修改的指针来实现的,而 List 是不可修改的,因此需要在每次操作时都创建一个新的不可变列表对象,效率较低。

在实际开发中,如果需要频繁修改列表元素,建议使用 MutableList,因为它提供了更好的性能和更灵活的数据操作;如果只需要访问列表元素而不需要修改,可以考虑使用 List,因为它更安全、更稳定、更可靠。

更多

object和companion object 区别

  • object 可以定义在全局也可以在类的内部使用
  • object 就是单例模式的化身
  • object 可以实现 Java 中的匿名类
  • companion object 就是 Java 中的 static 变量
  • companion object 只能定义在对应的类中
    更多区别
  • object 可以作为变量的定义也可以是表达式
  • object 匿名类可以继承并超越 Java 中匿名类而实现多个接口
  • object 表达式当场实例化,但定义的 object 变量是延迟实例化的
  • object 和 companion object 都可以为其取名也可以隐姓埋名
  • object 匿名内部类甚至可以引用并更改局部变量
  • companion object 甚至还可以被扩展
  • Java 中需要结合 @JvmStatic 和 @JvmField 使用

companion object 的定义完全属于类的本身,所以 companion object 肯定是不能脱离类而定义在全局之中。它就像 Java 里的 static 变量,所以我们定义 companion object 变量的时候也一般会使用大写的命名方式。
同时,和 object 类似,可以给 companion object 命名,也可以不给名字,这个时候它会有个默认的名字: Companion ,而且,它只在类里面能定义一次。

未完待续…

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值