Bootstrap

Kotlin入门学习(非常详细),从零基础入门到精通

目录

kotlin的历史

Kotlin的工作原理

语言类型

编译型

解释型

Java的语言类型

Kotlin的运行原理

创建Kotlin项目

​编辑

语法

变量

变量的声明

基本类型

函数

函数的声明

无参无返回值

有参有返回值

声明技巧

if语句

if

when

if实现

when实现

循环语句

类和对象

类的创建和对象的初始化

继承

构造

主构造

次构造

无主构造

接口

接口的定义

接口的继承

权限修饰符

数据类和单例类

数据类

单例类

Lambda

集合的创建和遍历

List

Set

Map

Lambda的使用

Java函数式API的使用

空指针检查机制

判空辅助工具

?.

?:

!!

let函数

内嵌表达式

函数的参数默认值



kotlin的历史

Kotlin由JetBrains公司开发设计,2011年公布第一版,2012年开源。

2016年发布1.0正式版,并且JetBrains在IDEA加入对Kotlin的支持,安卓自此又有新的选择。

2019年谷歌宣布Kotlin成为安卓第一开发语言,安卓程序员由java转Kotlin已经迫在眉睫。

Kotlin的工作原理

语言分为解释型和编译型两种

语言类型

编译型

编译器直接将源代码一次性编译成二进制文件,计算机可直接执行,例如C,C++。

优点:一次编译,即可运行,运行期不需要编译,运行效率高。

缺点:不同操作系统需要不同的机器码,且修改代码需要真个模块重新编译

解释型

程序运行时,解释器会将源码一行一行实时解析成二进制再执行。例如JS,Python。

优点:平台兼容性好,安装对应的虚拟机即可运行。

缺点:运行时需要解释执行,效率较低。

Java的语言类型

java准确来说属于混合型语言,但更偏向于解释型。

编译:java存在JIT和AOT,JIT即时编译将可将热点代码直接编译成机器码,AOT预先编译可再安装时把代码编译成机器码

解释:java运行时需编译成class文件,java虚拟机再解释执行.class。

Kotlin的运行原理

java虚拟机只认class文件, 虚拟机不会关心class时java文件编译来的,还是其他文件编译来的。那此时我们创造一套自己的语法规则,再做一个对应的编译器,,则可让我们的语言跑在java虚拟机上。Kotlin则是此原理,运行前会先编译成class,再供java虚拟机运行。

创建Kotlin项目

打开android studio,在选择语言时,选择Kotlin

在包下创建kotlin文件


创建File命名为HelloWorld :New Kotlin Class/File->File

敲入下面代码则可运行打印Hello World!

package com.hbsd.demo

fun main() {
    println("Hello World!")
}


运行结果如下:

下面进入语法学习

语法

变量
变量的声明

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

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

var b = 1
        val不可变变量,对应java的final变量

val a = 1
        两种变量并未声明类型,这是因为Kotlin存在类型推导机制,上述的a,b会默认为Int。假设想声明具体类型,则需下面的方式

var c: Int = 1

基本类型

Kotlin不再存在基本类型,将全部使用对象类型

Java基本类型Kotlin对象类型 对象类型说明
int    Int    整型
long   Long    长整型
short    Short    短整型
float   Float   单精度浮点型
double    Double    双精度浮点型
boolean    Boolean    布尔型
char    Char    字符型
byte    Byte    字节型


var和val的本质区别
打开Kotlin对应的Java文件

再点击下方按钮

则可查看对应的Java文件

public final class HelloWorldKt {
    private static final int a = 1;
    private static int b = 2;
    private static int c = 10;
    
    ...
}


发现val a对应的为final,var b和var c 对应的为非final

Kotlin此设计的原因则是防止非final的滥用,若一个变量永远不被修改则有必要给其加上final,使其他人看代码时更好理解。

后期我们写代码时则可先使用val,若真的需要修改再改为var

函数

函数的声明
无参无返回值
fun test() {
}

有参有返回值

参数的类型需要写在形参名后面中间使用:连接多个参数使用,分割",“返回值使用”:"拼接

fun add(a: Int, b: Int): Int {
    return a + b
}

声明技巧

当函数体只有一行代码时可直接使用下面方式声明方法

fun add (a: Int, b: Int): Int = a + b 
Kotlin存在类型推导,返回值类型也可省略

fun add (a: Int, b: Int) = a + b
函数的调用
fun main() {
    test()
    println(add(1, 2))
}

//运行结果
//test
//3

if语句

Kotlin中的选择控制有两种方式。if和when

if

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

fun max(a: Int, b: Int): Int {
    if (a > b) return a
    else return b
}


Kotlin的if可以包含返回值,if语句的最后一行会作为返回值返回

fun max(a: Int, b: Int): Int {
    return if (a > b) a else b
}


上述我们说过一行代码可省略返回值

fun max(a: Int, b: Int) = if (a > b) a else b


查看对应的Java文件,其上述实现都与下面代码等价

public static final int max(int a, int b) {
   return a > b ? a : b;
}

when


实现一个查询成绩的函数,用户传入名字,返回成绩级别

if实现

Kotlin的if语句必须要有else,不然会报错

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

也必须实现else,否则报错

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


when支持参数检查

fun checkNumber(num: Number) {
    when (num) {
        is Int -> println("Int")
        is Double -> println("Double")
        else -> println("others")
    }
}


when也可不传递形参

使用Boolean使when更加灵活

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


-> 后不仅可以只执行一行代码,可以多行,看一个比较复杂的例子:

fun getScore(name: String) = when {
    //若name以Tom开头则命中此分支
    name.startsWith("Tom") -> {
        //处理
        println("你好,我是Tom开头的同学")
        "不及格"
    }
    name == "Jim" -> "及格"
    name == "Pony" -> "良好"
    name == "Tony" -> "优秀"
    else -> "名字非法"
}

循环语句

Kotlin有两种循环方式,while和for-in,while与java中的while没有区别,for-in是对Java for-each的加强,Kotlin舍弃了for-i的写法

while不再赘述,在学习for-in之前需要明确一个概念-区间

val range = 0..10 //区间代表[0,10]


for-in需借助区间来使用

fun main() {
    val range = 0..10
    for (i in range) { //也可直接for (i in 0..10)
        println(i)
    }
    //输出结果为 从0打印到10
}


0..10 代表双闭区间,如果想使用左闭右开呢,需要借助until关键字

fun main() {
    for (i in 0 until 10) {
        println(i)
    }
    //输出结果为 从0打印到9
}


上述实现是逐步进行相当于i++,Kotlin也支持跳步

fun main() {
    for (i in  0 until 10 step 2) {
        println(i)
    }
    //输出结果为0,2,4,6,8
}

上述实现都是升序,Kotlin也可降序循环

fun main() {
    for (i in  10 downTo 1) {
        println(i)
    }
    //输出结果为10 - 1
}

for-in不仅可对区间进行遍历,还可对集合进行遍历,后续在集合处进行展示。

类和对象

类的创建和对象的初始化

在创建页面选择Class创建:New Kotlin Class/File -> Class

创建Person类,并声明name,age,创建printInfo方法

class Person {
    var name = ""
    var age = 0
    fun printInfo() {
        println(name +"'s age is " + age)
    }
}


在main方法中声明一个Person对象并调用printInfo方法

fun main() {
    val person = Person()
    person.name = "zjm"
    person.age = 20
    person.printInfo()
}
//结果如下zjm's age is 20
继承

声明Student类继承Person,Kotlin中继承使用**:**,后接父类的构造,为什么需要构造后续讲解

class Student : Person(){ //此时Person报错
    var number = ""
    var grade = 0
    fun study() {
        println(name + "is studying")
    }
}


Person类当前不可继承,查看Person对应的java文件

public final class Person {
    ...
}

Person类为final不可被继承,因此需借助open关键字

只需在Person类前加上open

open class Person {
    ...
}

此时Person的java文件变为

public class Person {
    ...
}

此时Student将不再报错

构造

构造分为主构造和此构造

主构造

主构造直接写在类后面

修改Student类

class Student(val number: String, val grade: Int) : Person(){
    ...
}

在创建Student对象时,如下创建

val student = Student("1234", 90)

因之前Person还有name和age,下面修改Person类的主构造

open class Person(val name: String, val age: Int) {
    ...
}

此时Student报错,因为继承Person时,后边使用的是Person()无参构造,上面我们修改了Person的构造,则不存在无参构造了。

再修改Student

class Student(name: String,  age: Int, val number: String, val grade: Int) : Person(name, age){
       ...
}

此时不在报错,声明方式如下

val student = Student("zjm", 20, "1234", 90)

在构造时需要进行特殊处理怎么办,Kotlin提供了init结构体,主构造的逻辑可在init中处理

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

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) {
    }
    ...
}

创建如下:

fun main() {
    val student1 = Student("zjm", 20, "123", 90)
    val student2 = Student("zjm", 20, "123")
    val student3 = Student()
}
无主构造

若类不使用主构造,则后续继承类也不需要使用构造即可去掉继承类的(),次构造可以调用父类构造super进行初始化,但是次构造的参数在其他地方无法引用

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中的接口定义类似

interface Study {
    fun study()
    fun readBooks()
    fun doHomework()
}
接口的继承

继承接口只需在后用","拼接,需实现Study声明的全部函数

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以后也支持此功能,方法有默认实现则继承类无需必须实现此方法

interface Study {
    fun study() {
        println("study")
    }
    fun readBooks()
    fun doHomework()
}

权限修饰符

Java和Kotlin的不同如下表所示:

修饰符    Java    Kotlin
public    所有类可见    所有类可见(默认)
private    当前类可见   当前类可见
protected    当前类,子类,同包下类可见    当前类,子类可见
default    同包下类可见(默认)    
internal    无   同模块下的类可见

internal 表示“只在模块内部可见”一个模块就是一组一起编译的kotlin文件

这有可能是一个IDEA模块、一个Eclipse项目、一个Maven或Gradle项目或者一组使用调用Ant任务进行编译的文件。
Kotlin引入internal,摒弃了default

使用:

类上

public open class Person(val name: String, val age: Int){...}

变量上

internal val value = 1

方法上

internal fun test() {
    
}

数据类和单例类

数据类

数据类则只处理数据相关,与Java Bean类似,通常需要实现其get,set,hashCode,equals,toString等方法

下面实现UserBean,包含id,name,pwd属性

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编写此类将变得非常简单,新建一个kt文件,选择如下:New Kotlin Class/File ->Data Class

一行代码即可搞定,Kotlin会自动实现上述方法。

data class UserBean(val id: String, val name: String, val pwd: String)


若无data关键字,上述方法(hashCode,equals,toString)无法正常运行,去掉data查看Kotlin对应的java文件:

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文件如下:

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使用最广的单例模式的实现如下:

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: New Kotlin Class/File->Object

生成代码如下:

object Singleton {
    fun test() {
        ...
    }
}

其对应的java文件如下,和上述使用最多的java单例实现类似

public final class Singleton {
   @NotNull
   public static final Singleton INSTANCE;

   public final void test() {
   }

   private Singleton() {
   }

   static {
      Singleton var0 = new Singleton();
      INSTANCE = var0;
   }
}

使用如下:

fun main() {
    Singleton.test() //对应的java代码为Singleton.INSTANCE.test();
}

Lambda


许多高级语言都支持Lambda,java在jdk1.8以后才支持Lamda语法,Lamda是Kotlin的灵魂所在,此小节对Lambda的基础进行学习,并借助集合练习。

集合的创建和遍历

List
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
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表达式的语法结构

{参数名1: 参数类型, 参数名2:参数类型 -> 函数体}

Kotlin的list提供了maxByOrNull函数,返回当前list中xx最大的元素,XX是我们定义的条件,可能为长度,可能是别的,我们拿长度举例。

若不使用maxBy,实现如下

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

fun main() {
    val list = listOf<String>("a", "aba", "aabb", "a")
    var lambda = {str: String -> str.length}
    var maxStr = list.maxByOrNull(lambda)
    println(maxStr)
}

直接当成参数也可传递

var maxStr = list.maxByOrNull({str: String -> str.length})

若Lambda为方法的最后一个参数,则可将{}提到外面

var maxStr = list.maxByOrNull() {str: String -> str.length}

若有且仅有一个参数且是Lambda,则可去掉()

var maxStr = list.maxByOrNull {str: String -> str.length}

Kotlin拥有出色的类型推导机制,Lambda参数过多时可省略参数类型

var maxStr = list.maxByOrNull {str -> str.length}

若Lambda只有一个参数,则可用it替代参数名

var maxStr = list.maxByOrNull {it.length}

集合还有许多此类函数

创建list,后续操作都由此list转换

val list = listOf<String>("a", "aba", "aabb", "a")

map 映射,返回新集合,将集合中的元素映射成另一个值

val newList = list.map { it.toUpperCase() }//将集合中的元素都准换成大写

filter过滤,返回新集合,将集合中的元素进行筛选

val newList = list.filter { it.length > 3 }//筛选出长度大于3的元素

any返回Boolean,集合中是否存在元素满足Lambda的条件,有则返回true,无则false

val isAny = list.any {it.length > 10} //返回false

all返回Boolean,集合中元素是否全部满足满足Lambda的条件,有则返回true,无则false

val isAll = list.all {it.length > 0} //返回true

Lambda的简单使用到这就结束了

Java函数式API的使用

Kotlin调用Java方法,若该方法接收一个Java单抽象方法接口参数,则可使用函数式API。Java单抽象方法接口指的是接口只声明一个方法,若有多个方法则无法使用函数式API。

Java单抽象方法接口例如Runnable

public interface Runnable {
    void run();
}

在Java中启动一个线程如下:

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("test");
    }
}).start();

Kotlin启动线程如下:

Kotlin摒弃了new,若想声明匿名内部类必须使用object

Thread(object : Runnable {
    override fun run() {
        println("test")
    }
}).start()

因Runnable是Java单抽象方法接口,可对代码进行简化

Thread(Runnable {
        println("test")
    }).start()

Runnable接口只用一个方法,使用Lambda也不会有歧义,Kotlin知道此Lambda一定实现的为run函数,借用Lambda进一步简化:

Thread({
    println("test")
}).start()

又因Thread只需一个参数Runnable参数,则可省略()

Thread {
    println("test")
}.start()

与上类似的,click也使用上述方法

button.setOnClickListener { println("test") }

这种方式可极大缩减代码量

空指针检查机制

国外统计程序出现最多的异常为空指针异常,Kotlin存在编译时检查系统帮助我们发现空指针异常。

查看下面Java代码

public void doStudy(Study study) {
    study.doHomework();
    study.readBooks();
}

上述代码时存在空指针风险的,传入null,则程序崩溃,对其进行改进

public void doStudy(Study study) {
    if (study != null) {
        study.doHomework();
        study.readBooks();
    }
}

对于Kotlin来讲任何参数和变量不能为空

fun study(study: Study) {
    study.doHomework()
    study.readBooks()
}

fun main() {
    study(null) //报错
    study(Student()) //正确
}

Kotlin把空指针异常的检查提前到了编译期,若空指针则编译期就会崩溃,避免在运行期出现问题

若我们有特殊的需求可能需要传递null参数,参数则按照下面声明

fun study(study: Study?) {
    study.doHomework() //报错
    study.readBooks()    //报错
}

?的意思则是当前参数可为空,如果可为空的话,则此对象调用的方法必须要保证对象不为空,上面代码没有保证,则报错,修改如下

fun study(study: Study?) {
    if (study != null) {
        study.doHomework()
        study.readBooks()
    }
}

也可借助判空辅助工具

判空辅助工具
?.

其含义是?前面对象不为空才执行.后面的方法

fun study(study: Study?) {
    study?.doHomework()
    study?.readBooks()
}
?:

其含义是?前不为空则返回问号前的值,为空则返回:后的值

比如

val c = if (a !=null ) {
    a
} else {
    b
}

借助?:则可简化为

val c = a ?: b

再比如

fun getTextLength(text: String?): Int {
    if (text != null) {
        return text.length
    }
    return 0
}

借助?: 则可简化为

fun getTextLength(text: String?) = text?.length ?: 0
!!

有些时候我们想要强行通过编译,就需要依靠!!,这时就是程序员来保证安全

fun study(study: Study?) {
    //假设此时为空抛出异常,则和java一样
    study!!.doHomework()
    study!!.readBooks()
}

let函数

let不是关键字,而是一个函数,提供了函数式API的编程接口,会将调用者作为参数传递到Lambda表达式,调用之后会立马执行Lambda表达式的逻辑

obj.let { it -> //it就是obj
    //编写操作
}

比如上面函数

fun study(study: Study?) {
    study.doHomework()  //报错
    study.readBooks()    //报错
}

借助let则可改为

fun study(study: Study?) {
    //此时靠?.则保证了study肯定不为空,才会执行let函数
    study?.let {
        //it为study
        it.doHomework()
        it.readBooks()
    }
}

全局判空注意事项

//全局变量
var study: Study? = null
fun study() {
    //报错
    if (study != null) {
        study.readBooks()
        study.doHomework()
    }
}

因全局变量随时有可能被其他线程修改,即使判空处理也不能保证其没有空指针风险,而let则可规避上述问题

var study: Study? = null
fun study() {
    study?.let {
        it.doHomework()
        it.readBooks()
    }
}

内嵌表达式

之前我们拼接字符串都是下面这样

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 $name. I am $age." )
//打印结果
//My name is zjm. I am 20.

内嵌表达式还支持复杂的操作

${程序员想要的操作}

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支持函数存在默认值,使用如下

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的,传入字符串会类型不匹配

fun main() {
    myPrint("zjm")//报错
}

fun myPrint(value: Int = 100, str: String) {
    println("num is $value, str is $str")
}

Kotlin提供了一种键值对传参来解决上述问题

fun main() {
    myPrint(str = "zjm") //正确调用
}

fun myPrint(value: Int = 100, str: String) {
    println("num is $value, str is $str")
}

回顾之前的主次构造,Student如下

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 即可

class Student(name: String,  age: Int, val number: String, val grade: Int = 0) : Person(name, age){
    ...
}

;