Kotlin语法学习笔记

Kotlin学习笔记

写在前面

关于Kotlin的学习,还是源于暑假学习安卓开发。当时用的Java,后来听说Google更加推荐Kotlin作为安卓开发的语言,于是购入了郭神的《第一行代码(第三版)》,一边学习安卓开发一边学习Kotlin语法。又因为自己平时用Kotlin太少了,所以顺手把部分语法的用法(我认为用例子来展示更方便学习)整理成md文档,今天顺手上传一下。当然,由于个人水平有限,整理的这些内容难免有疏漏、错误,还请各位看官不吝赐教。

Kotlin按我自己的理解就是Java的新衣,它和Java可以通用,而使用了大量新颖(?)的语法规范。

Kotlin 基础语法

变量

可变声明 变量名 : 变量类型 = 变量值 是变量的完整声明方式,其中不同的部分在不同的地方是可省或不可省是不同的,但是顺序是固定的。可以这样理解:Kotlin第一关注安全性,要求开发者说明变量是val还是var,其次关注类型,最后关注初始化赋值

变量声明

Kotlin支持两种关键字声明,编译时利用它出色的类型推导机制自主判断变量类型

  • val 用于声明不可变的变量,初始化后不能重新赋值,对应final
  • var 用于声明可变变量,初始化后可以被重新赋值,对应非final

如果需要对一个变量延迟赋值,需要显式声明变量类型,如var a : Int

函数定义时使用vararg关键字表示不固定个数的变量

如果既需要声明类型,有需要赋值,使用var a : Int = 10

声明变量时,永远优先使用val,除非不得已,才使用var。这样设计出的程序更加健壮,更符合高质量的编码规范

Kotlin所有的变量在声明时必须进行初始化赋值或者赋类型,不能直接var xxx

数据类型

Kotlin完全抛弃了Java的基本数据类型,全部使用对象数据类型,有Int, Long, Short, Float, Double, Boolean, Char, Byte 注意首字母大写

kotlin中的强制类型转换符为as。a as b表示把a转化为b(相当于(b)a)

字符串内嵌表达式

像PHP那样,可以在字符串中使用"${}"来代换变量值进行字符串拼接。如果只有一个值的话大括号还可以省略

数组
  • 可以使用arrayOf(vararg elements)传入可变数量的参数, 可以在arrayOf后面加上指定泛型(但一般不需要,会自己判断. 而且不加泛型时, 数组可以存放不同类型的元素)
  • 可以使用byte/short/int/long/char/float/double/booleanArrayOf(...)来建立指定类型的数组
  • 可以使用arrayOfNulls(size)来创建指定大小的空数组
  • 可以使用工厂函数Array(size: Int, init: (Int) -> T)来指定方式创建数组. 第一个参数是数组大小, 第三个参数是一个函数, 其接受下标, 返回数组的值
  • 可以使用Int/Short/Long/Float/Double/Boolean/Char/ByteArray建立原始类型数组

函数

函数声明

函数需要关键字fun开头,下面举一个例子

fun largerNumber (param1: Int, param2: Int) : Int{
    return max(param1, param2)   //Kotlin代码不需要分号结尾
}
//以上代码表示传入两个Int类型的参数,函数返回值是Int

//简化1:只有一条语句的函数可以放在一行,中间用等号连接
fun largerNumber (param1: Int, param2: Int) : Int = max(param1, param2)

//简化2:根据类型推导特性,函数的返回值类型Int可以省略
fun largerNumber (param1: Int, param2: Int) = max(param1, param2)

Kotlin函数和Java一样,不需要事先声明或定义(不同于C)

使用这一语法糖,函数变成了形似赋值语句,这也是Kotlin“函数式编程”的一个体现

函数调用
//Kotlin函数支持默认值
fun printParams(num: Int, str: String = "Hello") { //str可以不传入
    println("num is $num, str is $str")
}

//如果想要给第一个参数加默认值,Kotlin支持键值对形式直接调用函数
fun printParams(num: Int = 100, str: String) {
     println("num is $num, str is $str")
}
printParams(str = "hi") //键值对指定调用,不需要考虑传入参数顺序
逻辑控制
if语句

Kotlin的if语句有返回值,每一个分支判断的最后一个值就是返回值,因此有了下面这个极度简化的函数:

fun largerNumber (param1: Int, param2: Int) = if (param1 > param2) param1 else param2
when语句

when语句类似于switch,但是功能强大得多,举例如下

//when的带参数用法,精确匹配
fun getScore(name : String) = when(name) {
    "Tom" -> 86
    "Jim" -> 77
    "Tim" -> 99
    else -> 0
}

//when的带参数用法,类型匹配
fun checkNumberType(num : Number) { //这里Number是一个抽象类,Float、Double、Int等类型是它的子类
    when(num){
        is Int -> println("is Int")      //is 相当于Java里的instanceof
        is Double ->println("is Double") //不加f的小数一律按double处理
        else -> println("cannot check")
    }
}

//when的不带参数用法
fun getScore(name : String) = when {
    name == "Tom" -> 86
    name == "Tim" -> 99
    name.startsWith("A") -> 100
}
  • when语句和if一样,每一个条件分支最后一句是返回值
  • when可以进行带参匹配和不带参匹配,前者在"->"符号前只需要指定结果,后者需要写出完整的表达式,好处是可以随便判断(比如在一个when里对不同的变量进行验证,如果使用传统的if-esle语句的话,代码量爆炸)
while语句

和Java等编程语言完全相同

for语句

在Kotlin中,Java常用的fori被舍弃,而forin大大加强。以下是需要补充的背景知识:

val range = 0..10 //双端区间,表示[0,10]
val range = 0 until 10 //单端区间,表示[0,10)
for (i in 0..10) { /*循环语句*/ }    //i不需要定义或声明,默认自增1
for (i in 0 until 10 step 2) { /*循环语句*/ } //i每轮自增2
for (i in 10 downTo 1) { /*...*/ } //双端降序区间[10,1]

实际上(a…b)是一个Range对象。它有一些方法,比如random()可以随机获取这个区间的一个值

标准函数

标准函数是指Standard.kt文件中定义的函数

  • let 配合?:进行辅助判空处理
  • with 接受两个参数,第一个是任意类型对象,第二个是lambda表达式。with会在lambda表达式中提供第一个参数对象的上下文(省略了"对象名."的部分),并使用lambda表达式最后一行代码作为返回值返回,with不需要实例化对象
  • run 需要实例化对象,也就是需要调用某个对象的run方法。它只接受一个lambda参数,并在其中提供这个对象的上下文,也使用最后一行代码作为返回值返回
  • apply 需要实例化对象,无法指定返回值,最后返回调用对象本身。总的来说with、run、apply都是简化上下文的函数。
  • repeat (n) {…} 将大括号内的函数执行n次
扩展函数

扩展函数表示在不修改某个类的源码的情况下,向这个类添加新的函数,语法结构是

fun ClassName.MethodName(param: type): returnType { body }

扩展函数最好放在一个ClassName.kt中,方便查找用。如此一来它处于顶层函数位置,方便各个源文件使用

扩展函数优势在于可以像使用类内本来就存在的方法一样,更加方便,举例:

//String.kt
fun String.lerrersCount():Int{
    var count = 0
    for (char in this){	 //扩展函数自带上下文,这里的this即指String
        if (char.isLetter()) count++
    }
    return count
}
//在文件中使用扩展函数时
val count = "ABCD1234xyz!@#".letterCount() //直接.letterCount(),如同这一方法本来就是String自带的一样

kotlin自己也有一些自带的扩展函数,比如

  • String类带有.reverse(), .capitalize()等
  • use{ lambda } 保证lambda表达式代码执行完毕后自动关闭外层的流,不需要在try-catch中手动编写finally语句
  • forEachLine { lambda } 用于从文件逐行读取

扩展函数可以被添加到任意类中

高阶函数

如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,那么这个函数就是高阶函数(Kotlin增设“函数类型”)

语法规则为**(参数类型,参数类型,…) -> 返回值类型** 如果函数返回值是空,那么返回值类型写Unit

当函数作为参数时,传入方法为**::函数名**

//也可以改用lambda表达式实现
fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int) : Int {
    val result = operation(num1, num2)
    return result
}

fun plus(num1: Int, num2: Int) : Int {
    return num1 + num2
}

fun minus(num1: Int, num2: Int) : Int {
    return num1 - num2
}

fun main() {
    val num1 = 100
    val num2 = 80
    val result1 = num1AndNum2(num1, num2, ::plus) //高阶函数的适用方式
    val result2 = num1AndNum2(num1, num2, ::minus)
    println("result1 is $result1 and result2 is $result2")
}

Kotlin高阶函数在Java中是没有的。编译器把它转化成字节码的时候实际上是创建了一个匿名类,并实现了一个接口。这就会带来额外的开销。

内联函数

内联函数可完全消除lambda表达式带来的开销,只需要在定义高阶函数时前面加上inline关键字声明

运算符重载

kotlin支持运算符重载,使用关键词operator。和C++重载不同的是,kotlin重载运算符函数名是固定的单词,而不是符号形式的±*/

//重载类的加法
class Money(val value: Int) {
    //对于obj1 + obj2 
    operator fun plus(money: Money): Money{
        val sum = value + money.value
        return Money(sum)
    }
    //对于obj + num
    operator fun plus(money: Int): Money{
        val sum = value + money
        return Money(sum)
    }
}
语法糖表达式实际调用函数(使用的固定函数名)
a + ba.plus(b)
a - ba.minus(b)
a * ba.times(b)
a / ba.div(b)
a % ba.rem(b)
a++a.inc()
a–a.dec()
+aa.unaryPlus()
-aa.unaryMinus()
!aa.not()
a == ba.equals(b)
a > b a < b a >=b a <= ba.compareTo(b)
a…ba.rangeTo(b)
a[b]a.get(b)
a[b] = ca.set(b,c)
a in bb.contains(a)
静态方法

Kotlin极度弱化了静态方法的概念,因为它提供了更好用的语法特性,即单例类

//单例类。单例类里的函数不是严格意义上的静态方法,但是可以使用和静态方法相同的方式来调用(对象来自自动创建)
object Util{
    fun doAction(){
        ...
    }
}

//但是单例类的所有方法都是静态的。如果只希望类中某一部分方法变成静态方法的调用方式,需要使用companion object
class Util{
    fun doAction1(){
        ....
    }
    companion object {
        fun doAction2(){
            ....
        }
    }
}

//但是companion object内的方法并不是静态方法,而是在Util里创建一个伴生类,由Kotlin保证Util类只有一个伴生对象。
//如果确实要定义真正的静态方法,有两种方式
//1.加@JvmStatic注解
class Util{
    fun doAction1(){
        ....
    }
    companion object {
        @JvmStatic //这个注解只能加在单例类或者companion object里的方法上
        fun doAction2(){
            ....
        }
    }
}

//2.使用顶层方法:创建独立的kt文件,然后直接写fun...
//kotlin编译器会将所有的顶层方法都编译成静态方法。在kotlin代码中可以在任意位置直接调用。但如果在java中调用,需要指定文件名。(因为编译器自动根据文件名创建一个同名class供java调用)
infix函数

infix是kotlin一种语法糖,它可以使得函数更贴近英语表达,比如之前mapOf里面的to

infix fun String.beginWith(prefix: String) = startsWith(prefix)
//定义了String的拓展函数beginWith,则A.startsWith(B) 可以写为 A beginWith B

//更加常见的有位运算的语法糖
a xor b  // 等价于 a.xor(b)
a and b | a shl b | a shr b | a ushr b | a and b | //等等. ushr是无符号右移,左端补0,相当于java的">>>"

infix函数有两个严格限制

  • 不能定义为顶层函数,必须是某个类的成员函数,比如使用拓展函数将其添加入某个类
  • infix函数必须且只能接受一个参数,不限制参数类型
几种方法
  • System.currentTimeMillis() 获取系统时间

面向对象

Kotlin实例化对象不需要使用new关键字,直接val a = Person() 即可(举例)

定义内部类时,使用inner class关键字

定义常量的关键字是const,只有在单例类、companion object或者顶层方法中才能使用const关键字

继承与构造函数
//kotlin中默认所有的非抽象类都是不可继承的。如果想让它可继承,需要在class前加open关键字
open class Person {
    //类内定义变量,在“所有变量必须初始化赋值或类型”的前提下,有一些额外规定:
    var name = ""   // 这里是赋值,也可以使用lateinit var name : String 完成赋类型
    var age = 0     // 这里是赋值,不能使用lateinit var age : Int 规定lateinit不能用于八大基本类型
}

//kotlin中继承是 : 符号,和C++相同,而和Java的extends不同;此外Person后面需要加一对括号(Java不需要)
class Student : Person() {
    var sno = ""
    var grade = 0
}

lateinit是“延迟初始化”关键字。在类内定义全局变量时可能需要在多个函数中进行访问,但由于kotlin的安全限制,需要每一处都进行判空处理,并初始化为null(即便是开发者能保证这个变量不会抛出空指针异常)。这一关键字可以避免繁琐的判空处理,但是同时也带来了异常的风险。为了降低这种风险,kotlin提供了如下语法,判断全局变量是否初始化,从而避免空指针并且避免重复初始化

if (!::varable.isInitialized){
    //进行初始化操作
}

以下对Person后的括号进行解释

Kotlin的构造函数分为主构造函数次构造函数

  • 主构造函数

    • 任何一个类只能有一个主构造函数

    • 是最常用的构造函数

    • 主构造函数特点是没有函数体,直接定义在类名的后面,比如class Student(val sno: String, val grade: Int) : Person(){},这样一来对Student实例化的时候必须传入参数。因为构造函数使用的参数不需要重新赋值,所以使用val。注意:主构造函数中声明成val或var的参数将自动成为该类的字段(这样一来类内不要重复定义这个字段),如果这是一个普通的参数,不要加var或val

    • 如果主构造函数需要逻辑代码,可以在类内定义一个init{…}结构体,主构造函数的逻辑写在里面

      class Fraction(str: String) {
          var integer: Int = 0
          var numerator: Int
          var denominator: Int
          init {
              val arr = str.split('/')
              numerator = arr[0].toInt()
              denominator = arr[1].toInt()
          }
      }
      
    • 每个类都有一个默认的不带参数的主构造参数,当然也可以显式指明参数

    • 因为继承特性中子类的创建必须调用父类的构造函数,所以需要显式给Person后面加一个括号,即便Person构造函数不需要参数也要加括号。如果需要参数,那么Student的主构造函数应当写成class Student(val sno: String, val grade: Int, name: String, age : Int) : Person(name,age){}

  • 次构造函数

    • 一个类可以有多个次构造函数,也可以没有

    • 次构造函数使用constructor关键字定义在类的内部

    • //这里sno、grade将成为Student的成员变量
      class Student(val sno: String, val grade: Int, name: String, age: Int) : Person(name, age){
         	//这一个次构造函数将于以 val a = Student("Tim", 19)的方式实例化对象时被调用
          constructor(name: String, age: Int) : this("", 0, name, age) {
              //this将会进一步实例化对象,这一次就有四个参数,会调用主构造函数
          } 
          //这一个次构造函数将于以 val a = Student() 的方式实例化对象时被调用
          constructor() : this("", 0) {
              //this将会进一步实例化对象,这一次有两个参数,将会调用第4行的次构造函数,继而间接调用主构造函数
          }
      }
      
      //以上没有使用默认参数,也可以直接以添加默认参数的方式,可以省略两个次构造函数,并可以以键值对形式直接指定值
      
      • 次构造函数可以用于实例化一个类,与主构造函数区别在于它有函数体

      • 如果一个类同时具有主、次构造函数,那么所有的次构造函数都必须调用主构造函数(在一次实例化中可以只有用到的次构造函数去调用主构造函数,没有用到的就不调用了)

  • 只有次构造函数而没有主构造函数

    • kotlin允许一个类只有次构造函数而没有主构造函数:没有显式定义主构造函数,而定义了次构造函数

    • class Student : Person { //注意这里Person后面没有括号了,因为Student类没有主构造函数
          constructor(name: String, age: Int) : super(name, age){
              //super表示调用父级的构造函数,因为Student自己没有主构造函数,所以需要调用父级的构造函数,不能用this
          }
      }
      
接口

Kotlin的接口和java几乎一样,以下是接口的示例

//新建interface文件,只保存如下代码
interface Study { 
    fun readBooks() //只需要声明函数,使用接口的类强制实现这个函数
    fun doHomework(){ //kotlin中使用接口的类可以不实现这个函数(则使用这里的默认函数),java的话jdk1.8以后支持默认实现
        println("do homework default implementaion")
    }
}

//在定义实现这个接口的类所在的文件中
class Student(name: String, age: Int) : Person(name, age), Study{ //逗号后表示实现接口,接口后面不用加括号(但如果这个类只用来实现一个接口,那么使用冒号
    override fun readBooks(){			
        println(name + " is reading")
    }
}

只要接口中的函数拥有函数体,那么它就是一个具有默认实现的函数,实现接口的类可以不override此方法。

反之,如果接口里这个函数只有函数头,那么实现接口的类必须override这个方法

可见性修饰符

kotlin的四种可见性修饰符:public(默认), protected, private, internal(只对同一模块的类可见)

Java的四种可见性修饰符:public, protected, private, default(默认,同一包路径下的类可见)

  • Kotlin的public和java一样,都表示所有类可见
  • Kotlin的protected表示对当前类和子类可见,Java中protected表示对当前类、子类、同一包下的类可见
  • Kotlin的private和java一样,都表示当前类可见
数据类

在规范的系统架构中,数据类是一种将服务器端或者数据库的数据映射到内存中,为编程逻辑提供数据模型的特殊的类。(也就是把不同类型的对象进行封装,然后提供一些对外的方法)

数据类通常需要重写equals(), hashCode(), toString()等方法。在Java中,构建一个数据类需要大量的模板化代码,而且使用同样的数据构造数据类时的方法也不相同

kotlin定义了data关键字用于声明一个数据类,比如

data class Cellphone (val brand: String, val price: Double) //一个类没有内部代码时可以省略大括号

//在使用时
fun main () {
    val cellphone1 = Cellphone("Huawei", 4000.00)
    val cellphone2 = Cellphone("Huawei", 4000.00)
    println (cellphone1)
    println ("" + (cellphone1 == cellphone2))
}

===========输出结果============
Cellphone(brand-Huawei, price=4000.0)
true
单例类

单例模式是最基础的设计模式之一,用于避免创建重复的对象,当希望某个类在全局最多拥有一个实例时使用单例模式

Java中创建单例类的方法:

public class Singleton {
    private static Singleton instance; //静态成员变量
    private Singleton() {}  //将构造函数私有化,阻止外部调用
    public synchronized static Singleton getInstance() { //静态函数
        if (instance == null) {
            instance  = new Singleton();
        } 
            return instance;
    }
}

而在kotlin中创建单例类很简单:

object Singleton {} //创建了一个空的单例类

object Singleton { //创建了一个具有一个函数的单例类
    fun singletonTest() {
        println ("singletonTest is called")
    }
}

Singleton.singletonTest() //调用单例类的的方法
密封类

当一个类实现某个接口时,如果这个接口用于表示某个操作的结果,那么当某个函数对结果进行判断的时候,往往需要多加一个else来补充条件分支以通过kotlin的编译。即便是这样,如果这个结果某天多了一种情况,也不便于确认所有函数都对这种情况进行判断(这种情况下会进入else(往往是异常处理)。

//原方法:使用接口
interface Result
class Success(val msg: String) : Result
class Failure(val error: Exception) : Result

fun getResultMsg(result: Result) = when (result) { //这里是面向对象的知识,子类 is 父类恒为真(upcasting),所以传入Success或者Failure都满足result:Result的要求
	is Success -> result.msg
    is Failure -> result.error.message
    else -> throw IllegalArgumentException() //编译器强制要求
}

密封类是一个可继承的类,所以实现时需要在名字后加括号,关键字是sealed class。Kotlin会自动检查并该密封类的子类,并强制要求对每一个子类的对应条件全部处理,而不需要使用else。

密封类及其所有的子类只能定义在同一个文件的顶层位置,不能嵌套在其他类中

sealed class Result
class Success(val msg: String) : Result()
class Failure(val error: Exception) : Result()

fun getResultMsg(result: Result) = when (result) {
	is Success -> result.msg
    is Failure -> result.error.message
}
泛型和委托

泛型允许不指定具体类型的情况下进行编程,这样的代码具有更好的扩展性

默认情况下,所有的泛型可以为空,因为不手动指定上界的时候,默认值为Any?

泛型类
//举例
class className<T> {
    fun method(param: T): T{
        return param
    }
}

val myClass = className<Int>()
val result = myClass.method(123)
泛型方法
//举例
class className{
    fun <T> method(param: T): T{
        return param
    }
}

val myClass = className()
val result = className.method<Int>(123)  //这里的<Int>也可以省略

fun <T: Number> method(param: T) : T { } //指定了泛型类型的上界为数字型

委托是一种设计模式:操作对象自己不会去处理某段逻辑,而是把工作委托给另一个辅助对象去处理。

类委托

类委托的核心思想是将一个类的具体实现委托给另一个类去完成。通过在接口声明后面使用关键字by与受委托的辅助对象的名字,可以免去一大堆辅助代码,如:

class MySet<T> (val helperSet: HashSet<T>) : Set<T> by helperSet {
    fun helloWorld() = println("Hello world") //增加新的方法
    override fun isEmpty() = false //演示,重写HashSet里的方法
    //其他方法使用HashSet的方法来实现
}
委托属性

将一个字段的具体实现委托给另一个类完成

class MyClass {
    var p by Delegate() //将p属性的具体实现交给Delegate类去完成,调用和赋值时使用Delegate类的getValue和setValue方法
}

//需要对Delegate类进行具体的实现
//在Delegate类中必须实现getValue和setValue,都要用operator关键词声明
class Delegate {
    var propValue: Any? = null
    operator fun getValue(myClass: MyClass, prop: KProperty<*>): Any?{ 
        return propValue /*可以在什么类使用*/  /*一个操作属性类*/  
    }
    operator fun setValue(myClass: MyClass, prop: KProperty<*>, value: Any?) {
        propValue = value                              /*具体要赋给委托属性的值,需要和getValue返回值类型相同*/
    }
}

Lambda编程

集合的创建与遍历
//以下各类集合推荐[]选取元素
val myList1 = listOf(...) //创建不可变列表
val myList2 = mutableListOf(...) //创建可变列表(val表示myList2本身不可改变,即指向的列表不改变,但列表内容可以增删改

val mySet1 = setOf(...) //创建不可变集合,与列表的区别是它的元素是无序、不重复的 因为底层实现采取的是hash映射
val mySet2 = mutableSetOf(...) //创建可变集合

val myMap1 = mapOf(to, ..) //map特点是键重复时,后来的值将会覆盖之前的 这里的to不是关键字,而是一个infix函数
val myMap2 = mutableMapof(to, ..) //创建可变映射 

for ((key,value) in myMap1) {...} //遍历map的方式
集合的函数式API

lambda函数的完整结构为 { 参数1名 :参数1类型 , 参数2名 :参数2类型 -> 函数体 }

lambda函数可以用做别的函数的参数,它执行时按指定的参数名来执行函数体,并将函数体最后一个语句的返回值作为lambda函数的返回值。函数体没有限定长度,但一般不要太长

//举例,获取list中元素长度最长的那个字符串
val myList = listOf("a", "ab", "abc")

//version1,最朴素的用法
val lambda = {name: String -> name.length} //定义一个lambda函数,它接收一个字符串作为参数,并返回字符串的长度
val maxLengthString = myList.maxBy(lambda) //maxBy是myList一个接受lambda函数的方法,它返回“最大”的元素

//version2,lambda变量不需要定义,可以直接传入
val maxLengthString = myList.maxBy({name: String -> name.length})

//version3,Kotlin规定,lambda参数为函数最后一个参数时,可以将lambda函数移到括号外
val maxLengthString = myList.maxBy() {name: Stirng -> name.length}

//version4,如果lambda参数是函数唯一一个参数,那么函数的括号可以省略
val maxLengthString = myList.maxBy {name: String -> name.length}

//version5,借助Kotlin出色的类型推导机制,一般不需要写出lambda参数的类型
val maxLengthString = myList.maxBy {name -> name.length}

//version6,如果lambda只有一个参数,那么不需要声明参数列表,而是用关键字it直接代替
val maxLengthString = myList.maxBy {it.length}
集合中常用的函数式API总结

使用范式集合.API{lambda函数} 以下例子省略了"集合."部分

  • maxBy{it.length} 返回长度最大的元素
  • map{it.toUpperCase()} 将集合中的每个元素映射成另一个元素,返回这个集合
  • filter{it.length>10} 将集合中元素长度大于10的元素组成新的集合并返回
  • any{it.length>5} 判断集合中是否有长度大于5的元素,返回true或false
  • all{it.lenght>5} 判断集合中是否所有元素长度都大于5,返回true或false

空指针检查

Kotlin为了彻底规避空指针异常,规定所有变量都不为空,并在编译时检查(而不是运行时)。对于那些必须为空的变量设定了“可空类型”。在普通类型后加一个问号’?'表示这个元素是可为空,否则是不可为空。

  • 对于可空类型,调用它的方法时需要使用"?."操作符。该操作符表示此对象为不为空时才执行,否则什么也不做
  • 另一个操作符是"?:",当它左边不为空时返回左边的结果,左边为空时返回右边的结果
  • 当自己已经做了非空处理,但是编译器无法判断出来时,在这个对象后面加上"!!"(非空断言)强制通过编译 (但要提醒自己,因为最自信对象不为空的时候往往是潜在的发生空指针异常的时候hhhh)
  • 标准函数let提供了函数式API的编程接口,并将原始调用对象作为参数传递到lambda表达式中,可以配合?:使用
fun doStudy(study: Study?) {
    study?.let{  //?.是调用可控类型方法的做法,let是立即执行内部函数。
        it.readBooks()  //只有一个参数时可以省略 "标识符->"部分,并用it代替标识符
        it.doHomework() //使用let好处是只需进行一次?.的判断就能执行一系列函数,降低开销
    }
}

此外,let函数还可以处理全局变量的判空问题,因为全局变量可以被其他线程修改,使用if判断null依然会报错

并发

线程

Kotlin的多线程运用lambda函数非常简单,只需要 thread { 代码 }即可

协程

Kotlin中协程技术很有特色,大部分编程语言不具有,可以简单理解为轻量级线程。协程仅在编程语言层面实现不同协程的切换,极大提高了并发编程的运行效率

Kotlin语法糖

  • JavaBean类是一种根据类内字段自动生成getter和setter的方法,Kotlin中可以不使用这一方法而是直接".字段"直接获取或修改值
  • Kotlin的lambda函数在调用时允许将没有用到的参数使用一个下划线代替,但注意不能改变参数位置

参考资料

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

c01dkit

好可怜一博主,都没人打赏>_<

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值