Android 之 Kotlin

变量

变量的声明

Kotlin使用**var,val**来声明变量,注意:Kotlin不再需要;来结尾

var 可变变量,对应java非final变量

Kotlin 复制代码
var b = 1

**val**不可变变量,对应javafinal变量

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中的选择控制有两种方式。ifwhen

if

Javaif区别不大,实现一个返回最大值的函数

Kotlin 复制代码
fun max(a: Int, b: Int): Int {
    if (a > b) return a
    else return b
}

Kotlin的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实现

Kotlinif语句必须要有else,不然会报错

Kotlin 复制代码
fun getScore(name: String) = if (name == "Tom") "不及格"
else if (name == "Jim") "及格"
else if (name == "Pony") "良好"
else if (name == "Tony") "优秀"
else "名字非法"
 

Kotlin==等价于Javaequals比较的时是对象里的内容, === 等价于Java==,比较的为对象的引用。

when实现(相当于swich)

也必须实现else,否则报错

Kotlin 复制代码
fun getScore(name: String) = when(name) {
    "Tom" -> "不及格"
    "Jim" -> "及格"
    "Pony" -> "良好"
    "Tony" -> "优秀"
    else -> "名字非法"
}

循环语句

Kotlin有两种循环方式,whilefor-inwhilejava中的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类,并声明nameage,创建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类继承PersonKotlin中继承使用**:**,后接父类的构造,为什么需要构造后续讲解

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还有nameage,下面修改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的另外两个构造

三个参数的构造,nameagenumbergrade不传参默认为``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()
}

权限修饰符

JavaKotlin的不同如下表所示:

修饰符 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类似,通常需要实现其getsethashCodeequalstoString等方法

下面实现UserBean,包含idnamepwd属性

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关键字**,上述方法**(hashCodeequalstoString)**无法正常运行,去掉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;
   }
}

发现上面代码既无**hashCodeequalstoString也无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();
}

许多高级语言都支持Lambdajavajdk1.8以后才支持Lamda语法,LamdaKotlin的灵魂所在,此小节对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单抽象方法接口参数,则可使用函数式APIJava单抽象方法接口指的是接口只声明一个方法,若有多个方法则无法使用函数式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()

因**RunnableJava单抽象方法接口**,可对代码进行简化

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(常量)。
  • ​非空类型限制​ :只能用于对象类型(如 StringView),​不支持原始数据类型​ (如 IntBoolean

Android 中的典型应用场景​

  1. ​视图绑定与控件初始化​

    • Activity/FragmentonCreate() 中初始化 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 lazylateinit 均用于延迟初始化,但适用场景不同:

​特性​ by lazy lateinit
​适用变量类型​ val(只读) var(可变)
​初始化时机​ 首次访问时自动初始化 需手动显式初始化
​线程安全​ 默认支持(可配置模式) 无内置线程安全
​支持数据类型​ 所有类型(含基本类型) 仅非空对象类型(不含 Int 等)
​典型场景​ 单例、高开销资源、只读配置 Android 视图绑定、依赖注入

apply/alsolet/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() }

​关键决策树​​:

  1. ​是否需要返回对象本身?​

    • 是 → 选 applyalso

    • 否 → 选 letrun

二、分场景最佳实践与示例
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的使用场景​
  • ​核心用途​ ​:在对象上下文中​​执行计算并返回结果​​。

  • ​最佳实践​​:

    • 替代 withrun是扩展函数,支持空安全调用 ?.run)。

    • 适合组合属性访问与外部计算

Kotlin 复制代码
// 计算 View 的宽高比例
val aspectRatio = imageView.run {
    measure(0, 0)
    measuredWidth.toFloat() / measuredHeight  // 返回计算结果
}

delay

​核心原理:非阻塞挂起​

  1. ​非阻塞 vs 阻塞​

    • delay()​:通过协程调度器注册定时任务,挂起当前协程并释放线程资源,期间线程可执行其他任务

    • Thread.sleep() ​:直接阻塞线程,线程休眠期间无法执行任何操作​​

  2. 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的配合:超时控制
  1. ​​基础超时控制​​
    ​​场景​​:限制耗时操作(如网络请求)的执行时间,避免无限等待。
    ​​代码示例​​:
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

一、delaywithContext的配合:线程切换与结构化并发
  1. ​线程切换 + 延迟操作​
  • ​场景​​:在后台线程执行耗时操作后延迟返回主线程。

  • ​代码示例​​:

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.IODispatchers.IO无实际切换)