变量
变量的声明
Kotlin
使用**var,val
**来声明变量,注意:Kotlin
不再需要;
来结尾
var
可变变量,对应java
的非final
变量
Kotlin
var b = 1
**val
**不可变变量,对应java
的final
变量
Kotlin
val a = 1
两种变量并未声明类型,这是因为Kotlin
存在类型推导机制,上述的a,b
会默认为Int
。假设想声明具体类型,则需下面的方式。
Kotlin
var c: Int = 1
基本类型
Kotlin
不再存在基本类型,将全部使用对象类型
Java基本类型 | Kotlin对象类型 | 对象类型说明 |
---|---|---|
int | Int | 整型 |
long | Long | 长整型 |
short | Short | 短整型 |
float | Float | 单精度浮点型 |
double | Double | 双精度浮点型 |
boolean | Boolean | 布尔型 |
char | Char | 字符型 |
byte | Byte | 字节型 |
函数
函数的声明
无参无返回值
Kotlin
fun test() {
}
有参有返回值
参数的类型需要写在形参名后面中间使用:连接多个参数使用,分割","返回值使用":"拼接
Kotlin
fun add(a: Int, b: Int): Int {
return a + b
}
声明技巧
当函数体只有一行代码时可直接使用下面方式声明方法
Kotlin
fun add (a: Int, b: Int): Int = a + b
Kotlin
存在类型推导 ,返回值类型也可省略
Kotlin
fun add (a: Int, b: Int) = a + b
函数的调用
Kotlin
fun main() {
test()
println(add(1, 2))
}
//运行结果
//test
//3
if语句
Kotlin
中的选择控制有两种方式。if
和when
if
与Java
的if
区别不大,实现一个返回最大值的函数
Kotlin
fun max(a: Int, b: Int): Int {
if (a > b) return a
else return b
}
Kotli
n的if
可以包含返回值,if
语句的最后一行会作为返回值返回
Kotlin
fun max(a: Int, b: Int): Int {
return if (a > b) a else b
}
上述我们说过一行代码可省略返回值
Kotlin
fun max(a: Int, b: Int) = if (a > b) a else b
when语句
实现一个查询成绩的函数,用户传入名字,返回成绩级别
if实现
Kotlin
的if
语句必须要有else
,不然会报错
Kotlin
fun getScore(name: String) = if (name == "Tom") "不及格"
else if (name == "Jim") "及格"
else if (name == "Pony") "良好"
else if (name == "Tony") "优秀"
else "名字非法"
Kotlin
中==
等价于Java
的equals
比较的时是对象里的内容, ===
等价于Java
的==
,比较的为对象的引用。
when实现(相当于swich)
也必须实现else
,否则报错
Kotlin
fun getScore(name: String) = when(name) {
"Tom" -> "不及格"
"Jim" -> "及格"
"Pony" -> "良好"
"Tony" -> "优秀"
else -> "名字非法"
}
循环语句
Kotlin
有两种循环方式,while 和for-in ,while
与java
中的while
没有区别,for-in 是对Java for-each
的加强,Kotlin
舍弃了for-i
的写法
while
不再赘述,在学习for-in
之前需要明确一个概念-区间
Kotlin
val range = 0..10 //区间代表[0,10]
for-in
需借助区间来使用
Kotlin
fun main() {
val range = 0..10
for (i in range) { //也可直接for (i in 0..10)
println(i)
}
//输出结果为 从0打印到10
}
0..10
代表双闭区间,如果想使用左闭右开呢,需要借助until
关键字
Kotlin
fun main() {
for (i in 0 until 10) {
println(i)
}
//输出结果为 从0打印到9
}
上述实现是逐步进行相当于i++
,Kotlin
也支持跳步
Kotlin
fun main() {
for (i in 0 until 10 step 2) {
println(i)
}
//输出结果为0,2,4,6,8
}
for-in
不仅可对区间进行遍历,还可对集合进行遍历,后续在集合处进行展示。
类和对象
类的创建和对象的初始化
创建Person
类,并声明name
,age
,创建printInfo
方法
Kotlin
class Person {
var name = ""
var age = 0
fun printInfo() {
println(name +"'s age is " + age)
}
}
在main
方法中声明一个Person
对象并调用printInfo
方法
Kotlin
fun main() {
val person = Person()
person.name = "zjm"
person.age = 20
person.printInfo()
}
//结果如下zjm's age is 20
继承
声明Student
类继承Person
,Kotlin
中继承使用**:**,后接父类的构造,为什么需要构造后续讲解
Kotlin
class Student : Person(){ //此时Person报错
var number = ""
var grade = 0
fun study() {
println(name + "is studying")
}
}
Person
类为final
不可被继承,因此需借助open关键字
只需在Person
类前加上open
Kotlin
open class Person {
...
}
构造
构造分为主构造和此构造
主构造
主构造直接写在类后面
修改Student
类
Kotlin
class Student(val number: String, val grade: Int) : Person(){
...
}
因之前Person
还有name
和age
,下面修改Person
类的主构造
Kotlin
open class Person(val name: String, val age: Int) {
...
}
此时Student
报错,因为继承Person
时,后边使用的是Person()
无参构造,上面我们修改了Person
的构造,则不存在无参构造了。
再修改Student
Kotlin
class Student(name: String, age: Int, val number: String, val grade: Int) : Person(name, age){
...
}
此时不在报错,声明方式如下
Kotlin
val student = Student("zjm", 20, "1234", 90)
在构造时需要进行特殊处理怎么办,Kotlin提供了init结构体 ,主构造的逻辑可在init
中处理
Kotlin
open class Person(val name: String, val age: Int) {
init {
println("name is" + name)
println("age is" + age)
}
}
上述修改都为主构造,那如果类想有多个构造怎么办,此时需借助次构造
次构造
此时实现Student
的另外两个构造
三个参数的构造,name
,age
,number
,grade
不传参默认为``0
无参构造,字符串默认为"",int默认为0
Kotlin
class Student(name: String, age: Int, val number: String, val grade: Int) : Person(name, age){
constructor(name: String, age: Int, number: String) : this(name, age, number, 0) {
}
constructor() : this("", 0, "", 0) {
}
...
}
创建如下:
Kotlin
fun main() {
val student1 = Student("zjm", 20, "123", 90)
val student2 = Student("zjm", 20, "123")
val student3 = Student()
}
无主构造
若类不使用主构造,则后续继承类也不需要使用构造即可去掉继承类的()
,次构造可以调用父类构造super
进行初始化,但是次构造的参数在其他地方无法引用
Kotlin
class Student : Person {
constructor(name: String, age: Int, number: String) : super(name, age) {
}
fun study() {
//name,age可使用
println(name + "is studying")
//使用number则会报错,若number是主构造的参数则可引用
//println(number) 报红
}
}
接口
接口的定义
和Java
中的接口定义类似
Kotlin
interface Study {
fun study()
fun readBooks()
fun doHomework()
}
接口的继承
继承接口只需在后用","
拼接,需实现Study
声明的全部函数
Kotlin
class Student(name: String, age: Int, val number: String, val grade: Int) : Person(name, age), Study{
...
override fun study() {
TODO("Not yet implemented")
}
override fun readBooks() {
TODO("Not yet implemented")
}
override fun doHomework() {
TODO("Not yet implemented")
}
}
Kotlin
支持接口方法的默认实现,JDK1.8
以后也支持此功能,方法有默认实现则继承类无需必须实现此方法
Kotlin
interface Study {
fun study() {
println("study")
}
fun readBooks()
fun doHomework()
}
权限修饰符
Java
和Kotlin
的不同如下表所示:
修饰符 | Java | Kotlin |
---|---|---|
public | 所有类可见 | 所有类可见(默认) |
private | 当前类可见 | 当前类可见 |
protected | 当前类,子类,同包下类可见 | 当前类,子类可见 |
default | 同包下类可见(默认) | 无 |
internal | 无 | 同模块下的类可见 |
Kotlin
引入**internal
,摒弃了default
**
使用:
类上
Kotlin
public open class Person(val name: String, val age: Int){...}
变量上
Kotlin
private val value = 1
方法上
Kotlin
private fun test() {
}
数据类和单例类
数据类
数据类则只处理数据相关,与Java Bean
类似,通常需要实现其get
,set
,hashCode
,equals
,toString
等方法
下面实现UserBean
,包含id
,name
,pwd
属性
Java
编写入如下:
java
public class UserBean {
private String id;
private String name;
private String pwd;
public UserBean() {
}
public UserBean(String id, String name, String pwd) {
this.id = id;
this.name = name;
this.pwd = pwd;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserBean userBean = (UserBean) o;
return Objects.equals(id, userBean.id) && Objects.equals(name, userBean.name) && Objects.equals(pwd, userBean.pwd);
}
@Override
public int hashCode() {
return Objects.hash(id, name, pwd);
}
@Override
public String toString() {
return "UserBean{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", pwd='" + pwd + '\'' +
'}';
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
}
Kotlin
编写此类将变得非常简单
一行代码即可搞定,Kotlin会自动实现上述方法。
Kotlin
data class UserBean(val id: String, val name: String, val pwd: String)
若无**data
关键字**,上述方法**(hashCode
,equals
,toString
)**无法正常运行,去掉data
查看Kotlin
对应的java
文件:
Kotlin
public final class UserBean {
@NotNull
private final String id;
@NotNull
private final String name;
@NotNull
private final String pwd;
@NotNull
public final String getId() {
return this.id;
}
@NotNull
public final String getName() {
return this.name;
}
@NotNull
public final String getPwd() {
return this.pwd;
}
public UserBean(@NotNull String id, @NotNull String name, @NotNull String pwd) {
Intrinsics.checkNotNullParameter(id, "id");
Intrinsics.checkNotNullParameter(name, "name");
Intrinsics.checkNotNullParameter(pwd, "pwd");
super();
this.id = id;
this.name = name;
this.pwd = pwd;
}
}
发现上面代码既无**hashCode
,equals
,toString
也无set
**
加上**data
且把变量改为var
**,对应的java
文件如下:
java
public final class UserBean {
@NotNull
private String id;
@NotNull
private String name;
@NotNull
private String pwd;
@NotNull
public final String getId() {
return this.id;
}
public final void setId(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.id = var1;
}
@NotNull
public final String getName() {
return this.name;
}
public final void setName(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.name = var1;
}
@NotNull
public final String getPwd() {
return this.pwd;
}
public final void setPwd(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.pwd = var1;
}
public UserBean(@NotNull String id, @NotNull String name, @NotNull String pwd) {
Intrinsics.checkNotNullParameter(id, "id");
Intrinsics.checkNotNullParameter(name, "name");
Intrinsics.checkNotNullParameter(pwd, "pwd");
super();
this.id = id;
this.name = name;
this.pwd = pwd;
}
@NotNull
public final String component1() {
return this.id;
}
@NotNull
public final String component2() {
return this.name;
}
@NotNull
public final String component3() {
return this.pwd;
}
@NotNull
public final UserBean copy(@NotNull String id, @NotNull String name, @NotNull String pwd) {
Intrinsics.checkNotNullParameter(id, "id");
Intrinsics.checkNotNullParameter(name, "name");
Intrinsics.checkNotNullParameter(pwd, "pwd");
return new UserBean(id, name, pwd);
}
// $FF: synthetic method
public static UserBean copy$default(UserBean var0, String var1, String var2, String var3, int var4, Object var5) {
if ((var4 & 1) != 0) {
var1 = var0.id;
}
if ((var4 & 2) != 0) {
var2 = var0.name;
}
if ((var4 & 4) != 0) {
var3 = var0.pwd;
}
return var0.copy(var1, var2, var3);
}
@NotNull
public String toString() {
return "UserBean(id=" + this.id + ", name=" + this.name + ", pwd=" + this.pwd + ")";
}
public int hashCode() {
String var10000 = this.id;
int var1 = (var10000 != null ? var10000.hashCode() : 0) * 31;
String var10001 = this.name;
var1 = (var1 + (var10001 != null ? var10001.hashCode() : 0)) * 31;
var10001 = this.pwd;
return var1 + (var10001 != null ? var10001.hashCode() : 0);
}
public boolean equals(@Nullable Object var1) {
if (this != var1) {
if (var1 instanceof UserBean) {
UserBean var2 = (UserBean)var1;
if (Intrinsics.areEqual(this.id, var2.id) && Intrinsics.areEqual(this.name, var2.name) && Intrinsics.areEqual(this.pwd, var2.pwd)) {
return true;
}
}
return false;
} else {
return true;
}
}
}
此时则和手动编写的java bean
功能一样了,所有方法都可正常运行
单例类
目前Java
使用最广的单例模式的实现如下:
java
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
public void test() {
...
}
}
在Kotlin
中创建单例类需选择Object

生成代码如下
Kotlin
object Singleton {
fun test() {
...
}
}
其对应的java
文件如下,和上述使用最多的java
单例实现类似
Kotlin
public final class Singleton {
@NotNull
public static final Singleton INSTANCE;
public final void test() {
}
private Singleton() {
}
static {
Singleton var0 = new Singleton();
INSTANCE = var0;
}
}
使用如下:
Kotlin
fun main() {
Singleton.test() //对应的java代码为Singleton.INSTANCE.test();
}
许多高级语言都支持Lambda
,java
在jdk1.8
以后才支持Lamda
语法,Lamda
是Kotlin
的灵魂所在,此小节对Lambda
的基础进行学习,并借助集合练习。
集合的创建和遍历
List
Kotlin
fun main() {
//常规创建
val list = ArrayList<Int>()
list.add(1)
list.add(2)
list.add(3)
//listOf不可变,后续不可添加删除,只能查
val list1 = listOf<Int>(1, 2, 3 ,4 ,5)
list1.add(6)//报错
//mutableListOf,后续可添加删除
val list2 = mutableListOf<Int>(1, 2, 3 ,4 ,5)
list2.add(6)
//循环
for (value in list2) {
println(value)
}
}
Set
set
用法与List
类似,只是把**listOf
替换为mapOf
**
Map
Kotlin
fun main() {
val map = HashMap<String, String>()
map.put("1", "zjm")
map.put("2", "ljn")
//Kotlin中map支持类似下标的赋值和访问
map["3"] = "lsb"
map["4"] = "lyx"
println(map["2"])
println(map.get("1"))
//不可变
val map1 = mapOf<String, String>("1" to "zjm", "2" to "ljn")
map1["3"] = "lsb" //报错
//可变
val map2 = mutableMapOf<String, String>("1" to "zjm", "2" to "ljn")
map2["3"] = "lsb"
for ((key, value) in map) {
println(key + " " + value)
}
}
Lambda
Lambda的使用
方法在传递参数时都是普通变量,而Lambda可以传递一段代码
Lambda表达式的语法结构
{参数名1: 参数类型, 参数名2:参数类型 -> 函数体}
Kotlin的list提供了maxByOrNull函数,返回当前list中xx最大的元素,XX是我们定义的条件,可能为长度,可能是别的,我们拿长度举例。
若不使用maxBy,实现如下
Kotlin
fun main() {
val list = listOf<String>("a", "aba", "aabb", "a")
var maxStr = ""
for (str in list) {
if (str.length > maxStr.length) {
maxStr = str;
}
}
println(maxStr)
}
maxByOrNull
是一个普通方法,需要一个Lambda
参数,下面结合Lambda
使用maxByOrNull
Kotlin
fun main() {
val list = listOf<String>("a", "aba", "aabb", "a")
var lambda = {str: String -> str.length}
var maxStr = list.maxByOrNull(lambda)
println(maxStr)
}
直接当成参数也可传递
Kotlin
var maxStr = list.maxByOrNull({str: String -> str.length})
若Lambda
为方法的最后一个参数,则可将{}
提到外面
Kotlin
var maxStr = list.maxByOrNull() {str: String -> str.length}
若有且仅有一个参数且是Lambda
,则可去掉()
Kotlin
var maxStr = list.maxByOrNull {str: String -> str.length}
Kotlin
拥有出色的类型推导机制,Lambda
参数过多时可省略参数类型
Kotlin
var maxStr = list.maxByOrNull {str -> str.length}
若Lambda
只有一个参数,则可用it
替代参数名
Kotlin
var maxStr = list.maxByOrNull {it.length}
集合还有许多此类函数
创建list,后续操作都由此list
转换
Kotlin
val list = listOf<String>("a", "aba", "aabb", "a")
map
映射,返回新集合,将集合中的元素映射成另一个值
Kotlin
val newList = list.map { it.toUpperCase() }//将集合中的元素都准换成大写
**filter
**过滤,返回新集合,将集合中的元素进行筛选
Kotlin
val newList = list.filter { it.length > 3 }//筛选出长度大于3的元素
**any
**返回Boolean
,集合中是否存在元素满足Lambda
的条件,有则返回true
,无则false
Kotlin
val isAny = list.any {it.length > 10} //返回false
**all
**返回Boolean
,集合中元素是否全部满足满足Lambda
的条件,有则返回true
,无则false
Kotlin
val isAll = list.all {it.length > 0} //返回true
Lambda
的简单使用到这就结束了
Java函数式API的使用
Kotlin
调用Java
方法,若该方法接收一个Java
单抽象方法接口参数,则可使用函数式API
。Java
单抽象方法接口指的是接口只声明一个方法,若有多个方法则无法使用函数式API
。
Java
单抽象方法接口例如Runnable
Kotlin
public interface Runnable {
void run();
}
在Java
中启动一个线程如下:
Kotlin
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("test");
}
}).start();
Kotlin
启动线程如下:
Kotlin
摒弃了**new
,若想声明匿名内部类必须使用object
**
Kotlin
Thread(object : Runnable {
override fun run() {
println("test")
}
}).start()
因**Runnable
是Java
单抽象方法接口**,可对代码进行简化
Kotlin
Thread(Runnable {
println("test")
}).start()
**Runnable
**接口只用一个方法,使用Lambda
也不会有歧义,Kotlin
知道此Lambda
一定实现的为run
函数,借用Lambda
进一步简化:
Kotlin
Thread({
println("test")
}).start()
又因**Thread
**只需一个参数Runnable
参数,则可省略()
Kotlin
Thread {
println("test")
}.start()
与上类似的,click
也使用上述方法
Kotlin
button.setOnClickListener { println("test") }
这种方式可极大缩减代码量
空指针检查机制
国外统计程序出现最多的异常为空指针异常,Kotlin
存在编译时检查系统帮助我们发现空指针异常。
查看下面Java
代码
java
public void doStudy(Study study) {
study.doHomework();
study.readBooks();
}
上述代码时存在空指针风险的,传入null,则程序崩溃,对其进行改进
java
public void doStudy(Study study) {
if (study != null) {
study.doHomework();
study.readBooks();
}
}
对于Kotlin
来讲任何参数和变量不能为空
Kotlin
fun study(study: Study) {
study.doHomework()
study.readBooks()
}
fun main() {
study(null) //报错
study(Student()) //正确
}
Kotlin
把空指针异常的检查提前到了编译期,若空指针则编译期就会崩溃,避免在运行期出现问题
若我们有特殊的需求可能需要传递null参数,参数则按照下面声明
Kotlin
fun study(study: Study?) {
study.doHomework() //报错
study.readBooks() //报错
}
?
的意思则是当前参数可为空**,如果可为空的话,则此对象调用的方法必须要保证对象不为空,上面代码没有保证,则报错**,修改如下
Kotlin
fun study(study: Study?) {
if (study != null) {
study.doHomework()
study.readBooks()
}
}
也可借助判空辅助工具
判空辅助工具
?.
其含义是**?
前面对象不为空才执行.
后面的方法**
Kotlin
fun study(study: Study?) {
study?.doHomework()
study?.readBooks()
}
?:
其含义是?
前不为空则返回问号前的值,为空则返回:
后的值
比如
Kotlin
val c = if (a !=null ) {
a
} else {
b
}
借助**?:
**则可简化为
Kotlin
val c = a ?: b
再比如
Kotlin
fun getTextLength(text: String?): Int {
if (text != null) {
return text.length
}
return 0
}
借助**?:
**则可简化为
Kotlin
fun getTextLength(text: String?) = text?.length ?: 0
!!
有些时候我们想要强行通过编译,就需要依靠!!,这时就是程序员来保证安全
Kotlin
fun study(study: Study?) {
//假设此时为空抛出异常,则和java一样
study!!.doHomework()
study!!.readBooks()
}
let函数
let
不是关键字,而是一个函数,提供了函数式API
的编程接口,会将调用者作为参数传递到Lambda
表达式,调用之后会立马执行Lambda
表达式的逻辑
Kotlin
obj.let { it -> //it就是obj
//编写操作
}
比如上面函数
Kotlin
fun study(study: Study?) {
study.doHomework() //报错
study.readBooks() //报错
}
借助**let
**则可改为
Kotlin
fun study(study: Study?) {
//此时靠?.则保证了study肯定不为空,才会执行let函数
study?.let {
//it为study
it.doHomework()
it.readBooks()
}
}
全局判空注意事项
Kotlin
//全局变量
var study: Study? = null
fun study() {
//报错
if (study != null) {
study.readBooks()
study.doHomework()
}
}
因全局变量随时有可能被其他线程修改,即使判空处理也不能保证其没有空指针风险,而**let
则可规避上述问题**
Kotlin
var study: Study? = null
fun study() {
study?.let {
it.doHomework()
it.readBooks()
}
}
内嵌表达式 $
之前我们拼接字符串都是下面这样
Kotlin
var name = "zjm"
var age = 20
println("My name is " + name + ". I am " + age + ".")
//打印结果
//My name is zjm. I am 20.
现在靠着Kotlin
提供的内嵌表达式则不需要拼接,只需要下面这样则可实现
Kotlin
var name = "zjm"
var age = 20
println("My name is $name. I am $age." )
//打印结果
//My name is zjm. I am 20.
内嵌表达式复杂操作
${程序员想要的操作}
Kotlin
var name = "zjm"
var age = 20
println("My name is ${if (1 < 2) "zjm" else "ljn"}. I am $age." )
//打印结果
//My name is zjm. I am 20.
函数的参数默认值
Kotlin
支持函数存在默认值,使用如下
Kotlin
fun main() {
myPrint(1)
myPrint(1, "lalala")
}
fun myPrint(value: Int, str: String = "hello") {
println("num is $value, str is $str")
}
//结果如下
//num is 1, str is hello
//num is 1, str is lalala
若value
想为默认值,则会报错,因为在使用时传入的第一个参数他认为是int
的,传入字符串会类型不匹配
Kotlin
fun main() {
myPrint("zjm")//报错
}
fun myPrint(value: Int = 100, str: String) {
println("num is $value, str is $str")
}
Kotlin
提供了一种键值对传参来解决上述问题
Kotlin
fun main() {
myPrint(str = "zjm") //正确调用
}
fun myPrint(value: Int = 100, str: String) {
println("num is $value, str is $str")
}
回顾之前的主次构造,Student
如下
Kotlin
class Student(name: String, age: Int, val number: String, val grade: Int) : Person(name, age){
constructor(name: String, age: Int, number: String) : this(name, age, number, 0) {
}
...
}
上述的此构造借助参数默认值技巧是可以不写的,将第四个参数默认值为0 即可
Kotlin
class Student(name: String, age: Int, val number: String, val grade: Int = 0) : Person(name, age){
...
}
by lazy
Kotlin 的 by lazy
是一种延迟初始化属性的委托机制,主要用于优化资源加载、减少启动开销,并简化代码逻辑
1. 基本特性与工作原理
-
延迟初始化 :
by lazy
定义的属性仅在首次访问时执行初始化逻辑,后续访问直接返回缓存值。 -
只读属性 :仅适用于
val
(不可变变量),初始化后值不可修改。 -
线程安全 :默认使用
LazyThreadSafetyMode.SYNCHRONIZED
模式,通过双重检查锁(double-checked locking)确保多线程安全// 默认线程安全模式
val database: Database by lazy {
Database.connect("jdbc:mysql://localhost:3306/mydb")
}
Android 中的典型应用场景
-
ViewModel 初始化 :推迟创建直到首次访问,避免
Activity
/Fragment
构造时的额外开销class MainActivity : AppCompatActivity() {
private val viewModel by lazy { ViewModelProvider(this).get(MainViewModel::class.java) }
} -
高开销资源:如数据库连接、文件读取或网络客户端。
-
按需加载视图:仅在需要时初始化复杂 UI 组件(如自定义控件)。
lateinit
Kotlin 的 lateinit
是一种 延迟初始化非空可变属性的机制,主要用于解决对象在声明时无法立即赋值但后续保证会被初始化的问题
可变属性
- 仅适用于
var
变量,不可用于val
(常量)。 - 非空类型限制 :只能用于对象类型(如
String
、View
),不支持原始数据类型 (如Int
、Boolean
)
Android 中的典型应用场景
-
视图绑定与控件初始化
- 在
Activity
/Fragment
的onCreate()
中初始化View
,避免声明时立即加载资源
- 在
Kotlin
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater) // 手动初始化
setContentView(binding.root)
}
}
2.ViewModel 属性赋值
- 在
ViewModel
中声明非空属性,并在Activity
中通过ViewModelProvider
赋值
Kotlin
class MyViewModel : ViewModel() {
lateinit var userData: String // 稍后由 Activity 赋值
}
by lazy与 lateinit
的对比
by lazy
和 lateinit
均用于延迟初始化,但适用场景不同:
特性 | by lazy |
lateinit |
---|---|---|
适用变量类型 | val (只读) |
var (可变) |
初始化时机 | 首次访问时自动初始化 | 需手动显式初始化 |
线程安全 | 默认支持(可配置模式) | 无内置线程安全 |
支持数据类型 | 所有类型(含基本类型) | 仅非空对象类型(不含 Int 等) |
典型场景 | 单例、高开销资源、只读配置 | Android 视图绑定、依赖注入 |
apply
/also
或 let
/run
一、核心选择原则
函数 | 返回值 | 上下文对象引用 | 适用场景 | 典型代码模式 |
---|---|---|---|---|
apply |
对象本身 | this (隐式) |
对象初始化、批量配置属性 | Obj().apply { prop = value } |
also |
对象本身 | it (显式) |
链式调用中插入日志/校验等副作用 | Obj().also { log(it) } |
let |
Lambda 结果 | it (显式) |
可空处理、数据转换 | nullable?.let { it.process() } |
run |
Lambda 结果 | this (隐式) |
对象配置+结果计算 | Obj().run { prop; calculate() } |
关键决策树:
-
是否需要返回对象本身?
-
是 → 选
apply
或also
。 -
否 → 选
let
或run
。
-
二、分场景最佳实践与示例
1. apply
的使用场景
-
核心用途:初始化对象或集中配置属性(类似 Builder 模式)。
-
最佳实践:
-
属性赋值时省略
this
,提升简洁性。 -
避免在
apply
中执行复杂计算或副作用(如日志),应专注属性配置
-
Kotlin
// 初始化 View 并配置属性
val textView = TextView(context).apply {
text = "Hello"
textSize = 16f // 直接访问属性(this 可省略)
setPadding(10, 0, 0, 0)
} // 返回 TextView 对象
2. also
的使用场景
-
核心用途 :在链式调用中添加副作用操作(如日志、校验),不修改对象本身。
-
最佳实践:
-
用
it
显式引用对象,避免与外部作用域混淆。 -
确保操作是"无状态"的(如不打乱对象内部逻辑)
-
Kotlin
// 链式调用中插入日志
val user = User(name = "Alice", age = 25)
.also { println("创建用户: $it") } // 打印日志(不修改 user)
.apply { age += 1 } // 修改属性
.also { require(it.age > 0) { "年龄无效" } } // 数据校验
3. let
的使用场景
-
核心用途 :处理可空对象 或转换数据类型。
-
最佳实践:
-
优先用
?.let
替代if (obj != null)
,代码更简洁。 -
重命名
it
提升可读性(如nullableStr?.let { str -> ... }
)
-
Kotlin
// 可空字符串处理 + 转换
val length: Int = nullableString?.let {
it.trim().length // 非空时计算长度
} ?: 0 // 为空时默认值
4. run
的使用场景
-
核心用途 :在对象上下文中执行计算并返回结果。
-
最佳实践:
-
替代
with
(run
是扩展函数,支持空安全调用?.run
)。 -
适合组合属性访问与外部计算
-
Kotlin
// 计算 View 的宽高比例
val aspectRatio = imageView.run {
measure(0, 0)
measuredWidth.toFloat() / measuredHeight // 返回计算结果
}
delay
核心原理:非阻塞挂起
-
非阻塞 vs 阻塞
-
delay()
:通过协程调度器注册定时任务,挂起当前协程并释放线程资源,期间线程可执行其他任务 -
Thread.sleep()
:直接阻塞线程,线程休眠期间无法执行任何操作
-
-
Android平台 :
delay()
底层使用Handler.postDelayed()
应用场景与代码示例
1. Android异步任务(避免ANR)
Kotlin
// 主线程中安全使用delay
lifecycleScope.launch {
showLoading()
delay(2000) // 非阻塞挂起2秒
val data = fetchData() // 后台获取数据
updateUI(data)
}
delay()
期间UI线程可响应用户操作,避免因阻塞导致ANR
2. 并发网络请求
Kotlin
// 并行执行多个请求
val result1 = async(Dispatchers.IO) {
delay(1000) // 模拟网络延迟
"Data1"
}
val result2 = async(Dispatchers.IO) {
delay(1000)
"Data2"
}
println("${result1.await()} & ${result2.await()}") // 总耗时仅1秒
总结:关键差异速查表
特性 | delay() |
Thread.sleep() |
---|---|---|
阻塞性 | ❌ 非阻塞(挂起协程) | ✅ 阻塞线程 |
线程资源 | 释放线程,供其他任务使用 | 占用线程,资源浪费 |
适用场景 | 协程内异步操作 | 非协程环境的简单同步 |
Android主线程 | 安全(不触发ANR) | 危险(易导致ANR) |
内存开销 | 极低(协程轻量级) | 高(线程堆栈占用大) |
withTimeout
一、delay与withTimeout的配合:超时控制
- 基础超时控制
场景:限制耗时操作(如网络请求)的执行时间,避免无限等待。
代码示例:
Kotlin
try {
val result = withTimeout(1000) { // 1秒超时
delay(800) // 模拟耗时操作
"Success"
}
println(result) // 输出:Success
} catch (e: TimeoutCancellationException) {
println("操作超时")
}
- 说明 :若
delay
超过1秒,抛出TimeoutCancellationException
。
2.超时资源安全清理
-
场景:超时后仍需释放资源(如关闭文件、数据库连接)。
-
代码示例:
Kotlin
withTimeout(800) {
val resource = acquireResource()
try {
delay(1000) // 超时
} finally {
resource.release() // 仍会执行
}
}
说明 :finally
块在超时后仍会执行,确保资源释放
withContext
一、delay
与withContext
的配合:线程切换与结构化并发
- 线程切换 + 延迟操作
-
场景:在后台线程执行耗时操作后延迟返回主线程。
-
代码示例:
Kotlin
suspend fun fetchData(): String = withContext(Dispatchers.IO) {
delay(500) // IO线程模拟网络延迟
"Data"
}
// 主线程调用
CoroutineScope(Dispatchers.Main).launch {
val data = fetchData() // 自动切回主线程
updateUI(data)
}
说明 :withContext
自动切换线程,delay
在指定调度器上挂起
超时策略选择

-
超时控制 →
withTimeout
+delay
+finally
资源清理 -
线程切换 →
withContext
封装IO/计算任务,主线程中delay
实现节流/动画。 -
错误处理 →
try/catch
包裹withTimeout
,结合delay
实现重试策略。 -
性能优化 → 避免在
withContext
同一调度器内多次切换线程(如Dispatchers.IO
→Dispatchers.IO
无实际切换)