Android开发之Kotlin(一)

1. 变量声明

(1)变量的声明:在Kotlin中声明变量,就是在标识符的前面加上关键字var

(2)常量的声明:

常量和只读变量一旦初始化后就不能再被修改。在Kotlin声明常量是在标识符的前面加上val或const val关键字,它们的区别如下。
① val声明的是运行期常量,常量是在运行时初始化的。
② const val声明的是编译期常量,常量是在编译时初始化,只能用于顶层常量声明或声明对象中的常量声明,而且只能是String或基本数据类型(整数、浮点等)。
给 Java程序员的提示: 编译期常量(const val)相当于Java中public final static所修饰的常量。而运行期常量(val)相当于Java中final所修饰的常量。
问题:使用 var 还是 val ?

原则 如果两种方式都能满足需求情况下,原则上优先考虑使用 val 声明。因为一方面val 声明的变量是只读,一旦初始化后不能修改,这可以避免程序运行过程中错误地修改变量内容;
另一方面在声明引用类型使用val ,对象的引用不会被修改,但是引用内容可以修改,这样会更加安全,也符合函数式编程的技术要求。

val value1:Int =5;
val value2:Int = 65
var value3:Float = 6.0f;
var value4:Float = 9.0F;
var value5:Double = 0.0;
var value6:Double = 99.0;
var totalNum:Double = 0.0;
var maxValueA:Long = 0;
var maxValueB:Long = 10;
var isFirstLoad:Boolean =false;
var aWord:Char = 'a';
var bWord:Char = 'b';
var nullString:String = "";
var abcString:String = "abcde";

在Android中的Activity、Fragment中声明使用时,一般都会加上访问权限限制:

    private val value1:Int =5;
    private val value2:Int = 65
    private var value3:Float = 6.0f;
    private var value4:Float = 9.0F;
    private var value5:Double = 0.0;
    private var value6:Double = 99.0;
    private var totalNum:Double = 0.0;
    private var maxValueA:Long = 0;
    private var maxValueB:Long = 10;
    private var isFirstLoad:Boolean =false;
    private var aWord:Char = 'a';
    private var bWord:Char = 'b';
    private var nullString:String = "";
    private var abcString:String = "abcde";

2. 关于空值

Kotlin语言默认情况下所有的数据类型都是非空类型(Non-Null),声明的变量都是不能接收空值(null)的。但有些场景确实没有数据,例如查询数据库记录时,没有查询出符合条件的数据是很正常的事情。
为此,Kotlin为每一种非空类型提供对应的可空类型(Nullable),就是在非空类型后面加上问号(?)表示可空类型。

var n: Int = 10; //Int 非空类型
n = null; //不可接收空值(null),编译报错
var n: Int? = 10; //Int? 可空类型
n = null; //可以接收空值(null)

Int?是可空类型,它所声明的变量n可以接收空值。可空类型在具体使用时会有一些限制:

①不能直接调用可空类型对象的函数或属性。
②不能把可空类型数据赋值给非空类型变量。
③不能把可空类型数据传递给非空类型参数的函数。

3. 常用的运算符

①安全调用运算符:?.
可空类型变量使用安全调用运算符( ?. )可以调用非空类型的函数或属性。安全调用运算符(?. )会判断可空类型变量是否为空,如果是则不会调用函数或属性,直接返回空值;否则返回调用结果。
②安全转换运算符:as? 和 不安全转换运算符 as
如果类型转换不成功,类型转换操作符通常会抛出一个异常,因此我们称之为不安全的(unsafe)。在 Kotlin 中,不安全的类型转换使用中缀操作符 as。
为了避免抛出异常,你可以使用安全的类型转换操作符 as?,当类型转换失败时,它会返回 null,但不会抛出异常崩溃;
③Elvis运算符:?:
有的时候在可空类型表达式中,当表达式为空值时,并不希望返回默认的空值,而是其他数值。此时可以使用Elvis 运算符( ?: ),也称为空值合并运算符, Elvis 运算符有两个 操作数,
假设有表达式:A ?: B ,如果 A 不为空值则结果为 A ;否则结果为 B 。 Elvis运算符经常与安全调用运算符结合使用
④非空断言:!!
可空类型变量可以使用非空断言运算符( !! )调用非空类型的函数或属性。非空断言运算符(!! )顾名思义就是断言可空类型变量不会为空,调用过程是存在风险的,
如果可空类型变量真的为空,则会抛出空指针异常;如果非则可以正常调用函数或属性。

4. 面向对象的编程

(0) 属性声明 :Kotlin中声明属性的语法格式如下:

var|val 属性名 [ : 数据类型] [= 属性初始化 ] 
[getter访问器] 
[setter访问器]

从上述属性语法可见,属性最基本形式与声明一个变量或常量是一样的。val所声明的属性是只读属性。如果需要还可以重写属性的setter和getter访问器。中括号( [ ] )部分表示可以省略;竖线( | )表示 “ 或关系” ,例如 var|val ,说明可以使用 var 或 val 关键字,两个关键字不能同时出现。属性本身并不真正的保存数据,数据被保存到支持字段( backing field)中,支持字段一般是不可见的,支持字段只能应用在属性访问器(setter和getter访问器)中,通过系统定义好的软关键字field变量访问。

package com.work.section 
// 员工类 
class Employee { 
   var no: Int = 0 // 员工编号属性 
   var job: String? = null // 工作属性 
   var firstName: String = "Tony" 
   var lastName: String = "Guan" 
   var fullName: String //全名
   //重写getter访问器
   get() { 
      return firstName + "." + lastName 
   }
   //重写setter访问器
   set (value) { 
      val name = value.split(".") 
      firstName = name[0]
      lastName = name[1] 
   } 
   var salary: Double = 0.0 // 薪资属性 
       set(value) { 
           if (value >= 0.0) 
           //支持字段是不可见的,但是可以通过Kotlin语言定义的软关键字field访问,通过对field赋值,将salary大于0时的值存储在支持字段中
           field = value  
       } 
}

(1) 委托属性 :Kotlin提供一种委托属性,使用by关键字声明:
by是委托运算符,它后面的Delegate()就是属性myName的委托对象,通过by运算符User类属性myName的setter访问器被委托给Delegate对象的setValue函数,属性myName的getter访问器被委托给Delegate对象的getValue函数。 Delegate对象不必实现任何接口,只需要实现getValue()和setValue()函数即可。而在类Delegate中有通过operator运算符对getValue()和setValue()函数进行了重载。

package com.work
import kotlin.reflect.KProperty 
   
 class User {
     //委托属性,使用by关键字声明
     var myName: String by Delegate()
 }
    
 class Delegate {
     //注意这两个函数前面都有operator关键字修饰,operator所修饰的函数是运算符重载函数
     operator fun getValue(thisRef: Any, property: KProperty<*>): String = property.name
     operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
         println(value)
     }
 }
 
fun main(args: Array<String>) { 
   val user = User() 
   user.myName = "Tom"   //此处会调用Delegate的setValue()函数,输出:Tom
   println(user.myName)  //此处会输出属性名property.name,即输出:myName
}

(2)延迟初始化属性关键字:lateinit
延迟初始化属性关键字只用在类体中的属性字段上,在属性前面添加了关键字lateinit,这样属性就会延时初始化。顾名思义,延时初始化属性就是不必在类实例化时初始化它,可以根据需要在程序运行期初始化,而不是一开始就初始化,从而节省内存。而没有lateinit声明的非可空类型属性必须在类实例化时初始化。延时初始化属性要求:不能是可空类型;只能使用为var声明;lateinit关键字应该放在var之前。

package com.work.section 
// 员工类 
class Employee { 
   ... 
   lateinit var dept: Department // 所在部门属性 
}
 
// 部门类 
class Department { 
     var no: Int = 0 // 部门编号属性 
     var name: String = "" // 部门名称属性 
}
 
 
package com.work.section
fun main(args: Array<String>) {
   //初始化员工实例
   val emp = Employee() 
   ... 
   //延迟初始化员工部门dept属性
   emp.dept = Department()
   println(emp.dept) 
}

(3) 惰性加载属性
惰性加载属性与延迟初始化属性类似,只有第一次访问该属性时才进行初始化。不同的是惰性加载属性使用的lazy函数声明委托属性(即关键字by 后面跟lazy{ }函数(lamda表达式形式)),而延迟初始化属性lateinit关键字修饰属性。还有惰性加载属性必须是val的,而延迟初始化属性必须是var的。
在员工类Employee中声明了的惰性加载属性fullName,by后面是lazy函数,注意lazy不是关键字,而是函数。lazy函数后面跟着的是尾随Lambda表达式。惰性加载属性使用val声明。在员工类Employee中同时也声明了延迟初始化属性dept ,使用关键字 lateinit 。延迟初始化属性使用var声明。

package com.work.section
// 员工类
open class Employee { 
    var no: Int = 0 // 员工编号属性 
    var firstName: String = "Tony" 
    var lastName: String = "Guan" 
    val fullName: String by lazy { 
                    firstName + "." + lastName 
                  }
    lateinit var dept: Department 
}
 
// 部门类 
class Department {  
    var no: Int = 0 // 部门编号属性 
    var name: String = "" // 部门名称属性 
}
 
 
fun main(args: Array<String>) {
    val emp = Employee() 
    println(emp.fullName)//Tony.Guan 
 
    val dept = Department() 
    dept.no = 20 
 
    emp.dept = dept
    println(emp.dept) 
}

(4)可观察属性
另一个使用委托属性是可观察属性,委托对象监听属性的变化,当属性变化时委托对象会被触发。

import kotlin.properties.Delegates //这里需要一个导包
 
class Department {
    var no: Int = 0 // 部门编号属性
    var name: String by Delegates.observable("<无>") {
                p, oldValue, newValue -> println("$oldValue -> $newValue")
    }
}
 
fun main(args: Array<String>) {
    val dept = Department()
    dept.no = 20
    dept.name = "技术部" //输出:<无> -> 技术部
    dept.name = "市场部" //输出:技术部 -> 市场部
}

输出结果为:

com.xw.kotlinforandroid I/System.out: <> -> 技术部
com.xw.kotlinforandroid I/System.out: 技术部 -> 市场部

5. 扩展

(1)扩展函数
在接收类型上扩展函数,具体语法如下:

fun 接收类型.函数名(参数列表) : 返回值类型 { 
    函数体 return 返回值
}

可见扩展函数与普通函数区别是函数名前面加上 “ 接收类型 .” 。接收类型可以是任何Kotlin数据类型,包括基本数据类型和引用类型

package com.work.section 
//基本数据类型扩展
fun Double.interestBy(interestRate: Double): Double {
   return this * interestRate 
}
 
//自定义账户类 
class Account { 
   //账户金额 
   var amount: Double = 0.0 
   //账户名 
   var owner: String = "" 
}
//账户类扩展函数 
fun Account.interestBy(interestRate: Double): Double {
    return this.amount * interestRate 
}
 
fun main(args: Array<String>) { 
 
   val interest1 = 10_000.00.interestBy(0.0668) 
   println("利息1: $interest1") 
 
   val account = Account() 
   val interest2 = account.interestBy(0.0668) 
   println("利息2: $interest2") 
}

package com.work.section
//基本数据类型扩展
fun Double.interestBy(interestRate: Double): Double {
return this * interestRate
}

//自定义账户类
class Account {
//账户金额
var amount: Double = 0.0
//账户名
var owner: String = “”
}
//账户类扩展函数
fun Account.interestBy(interestRate: Double): Double {
return this.amount * interestRate
}

fun main(args: Array) {

val interest1 = 10_000.00.interestBy(0.0668)
println(“利息1: $interest1”)

val account = Account()
val interest2 = account.interestBy(0.0668)
println(“利息2: $interest2”)
}

com.xw.kotlinforandroid I/System.out: 利息1: 668.0
com.xw.kotlinforandroid I/System.out: 利息2: 0.0

(2)扩展属性
在接收类型上扩展属性,具体语法如下:

var|val 接收类型.属性名 [ : 数据类型] 
       [getter访问器] 
       [setter访问器]

扩展属性与普通属性在声明时区别是属性名前面加上“接收类型.”。接收类型可以是任何Kotlin数据类型,包括基本数据类型和引用类型。

package com.work.section 
// 部门类 
class Department { 
   var no: Int = 0 // 部门编号属性 
   var name: String = "" // 部门名称属性 
}
 
   //此处定义Department类的扩展属性,并重写了get()、set()访问器
   var Department.desc: String 
        get() { 
            return "Department [no=${this.no}, name=${this.name}]" 
        }
        set(value) { 
            println(value) //println(field)//编译错误 
        } 
  //此处定义Int类的扩展属性,并重写了get()访问器
   val Int.errorMessage: String 
        get() = when (this) {
             -7 -> "没有数据。" 
             -6 -> "日期没有输入。" 
             -5 -> "内容没有输入。" 
             -4 -> "ID没有输入。" 
             -3 -> "数据访问失败。"
             -2 -> "您的账号最多能插入10条数据。"
             -1 -> "用户不存在,请到http://51work6.com注册。" 
             else -> "" 
         } 
 
fun main(args: Array<String>) { 
 
   val message = (-7).errorMessage 
   println("Error Code: -7 , Error Message: $message") 
 
   val dept = Department() 
   dept.name ="智捷课堂" 
   dept.no = 100
   println(dept.desc) 
}

输出结果:

com.xw.kotlinforandroid I/System.out: Error Code: -7 , Error Message: 没有数据。
com.xw.kotlinforandroid I/System.out: Department [no=100, name=智捷课堂]

(3) “成员优先 ”原则
无论是扩展属性还是扩展函数,如果接收类型成员中已经有相同的属性和函数,那么在调用属性和函数时,始终是调用接收类型的成员属性和函数。这就是“ 成员优先 ” 原则。你可以通过改变成员函数的参数,进行区分调用。

6. 构造函数

函数,用来初始化类的属性,它在创建对象之后自动调用。在 Kotlin 中构造函数有主次之分,主构造函数只能有一个,次构造函数可以有多个。
(1)主构造函数
主构造函数由两部分构成,涉及到两个关键字constructor和init。主构造函数在类头中、类名的后面声明,使用关键字constructor。
①可以将属性与主构造函数的参数合并,也就是将本来可以在类内部声明的属性放在类头部的主构造函数的参数列表中声明,Kotlin编译器会根据主构造函数的参数列表生成相应的属性,这样在函数体中就不需要属性声明了。
②如果所有的属性都在主构造函数中初始化,可以省略init代码块。
③如果主构造函数没有注解(Annotation)或没有可见性修饰符(private、protected、public、internal),constructor关键字可以省略。

    class Rectangle constructor(var width: Int, var height: Int) {
        
        // 矩形宽  放到主构造函数的参数列表中声明
        //var width: Int 
        // 矩形高  放到主构造函数的参数列表中声明
        //var height: Int 
 
        // 矩形面积
        var area: Int 
       
        init {                     //初始化代码块
            area = width * height  // 计算矩形面积
        }
    }

主构造函数省略init代码块声明举例:声明一个User类,它只有两个属性,它们都是在主构造函数中声明的,这样可以省略init代码块。这样类体中没有代码可以省略大括号。

class User constructor(val name: String, var password: String)

主构造函数省略constructor关键字举例:如果主构造函数没有注解(Annotation)或没有可见性修饰符,constructor关键字可以省略。这样主构造函数就很简明了。

class User(val name: String, var password: String)

class User(val name: String, var password: String)

class User private constructor(val name: String, var password: String)

主构造函数与普通函数类似,可以声明带有默认值的参数,这样一来虽然只有一个主构造函数,但调用时可以省略一些参数,类似于多个构造函数重载。

class Animal(val age: Int = 0, val sex: Boolean = false)
 
 
fun main(args: Array<String>) { 
     val animal1 = Animal() 
     val animal2 = Animal(10) 
     val animal3 = Animal(sex = true) 
     val animal4 = Animal(10,true) 
}

(2)次构造函数
由于主构造函数只能有一个,而且初始化时只有 init 代码块,有时候不够灵活,这时可以使用次构造函数。次构造函数是在函数体中声明的,使用关键字constructor 声明,代码如下:

class Rectangle(var width: Int, var height: Int) { 
   // 矩形面积 
   var area: Int 
   
    init {//初始化代码块 
        area = width * height// 计算矩形面积 
    }
 
    //次构造函数
    constructor(width: Int, height: Int, area: Int) : this(width, height) {  
        this.area = area 
    }
 
    //次构造函数
    constructor(area: Int) : this(200, 100) {  //width=200 height=100
        this.area = area 
    } 
}
 
fun main(args: Array<String>) { 
    val rect1 = Rectangle(100, 90) 
    val rect2 = Rectangle(10, 9,900) 
    val rect3 = Rectangle(20000) 
}

上述代码分别声明了两个次构造函数,次构造函数需要调用主构造函数初始化部分属性,次构造函数后面的this(width, height)和this(200, 100)表达式就是调用当前对象的主构造函数。

另外,当属性命名与参数命名有冲突时候,属性可以加上this.前缀,this表示当前对象。
(3)默认构造函数
如果一个非抽象类中根本看不到任何的构造函数,编译器会为其生成一个默认的构造函数,即无参数public 的主构造函数。

//默认构造函数 
class User { 
      // 用户名 
      val username: String? 
      // 用户密码 
      val password: String? 
      init {
          username = null 
          password = null 
      } 
}
//创建User对象 
val user = User()

7. 可见性修饰符使用规则

Kotlin可见性有4种:公有、内部、保护和私有。具体规则如表
在这里插入图片描述

8. 函数式编程简介

(一)、函数式编程简介
1.1 函数式编程(functional programming)
函数式编程是一种编程典范,也就是面向函数的编程。 在函数式编程中一切都是函数。
函数式编程核心概念如下:
① 函数是 “ 一等公民 ” :是指函数与其他数据类型(Int、Double、String等)是一样的,处于平等的地位。函数可以作为其他函数的参数传入,也可以作为其他函数的返回值返回。
② 使用表达式,不用语句:函数式编程关心的输入和输出,即:参数和返回值。在程序中使用表达式可以有返回值,而语句没有。例如:控制结构中的if 和 when 结构都属于表达式。
③ 高阶函数:函数式编程支持高阶函数,所谓高阶函数就是一个函数可以作为另外一个函数的参数或返回值。
④ 无副作用:是指函数执行过程会返回一个结果,不会修改外部变量,这就是 “ 纯函数” ,同样的输入参数一定会有同样的输出结果。
Kotlin 语言支持函数式编程,提供了函数类型、高阶函数和 Lambda 表达式。

1.2 高阶函数 和 函数类型

① 高阶函数的概念:函数式编程的关键是高阶函数的支持。一个函数可以作为另一个函数的参数,或者返回值,那么这个函数就是“高阶函数 ” 。

② 函数类型概念:Kotlin中每一个函数都有一个类型,称为 “ 函数类型 ” ,函数类型作为一种数据类型与数据类型在使用场景没有区别。可以声明变量,也可以作为其他函数的参数或者其他函数的

返回值使用。函数类型就是把函数参数列表中的参数类型保留下来,再加上箭头符号和返回类型,形式如下:

参数列表中的参数类型 -> 返回类型

举例:

package com.work.section 
//定义计算长方形面积函数 
//函数类型: (Double, Double) -> Double 
fun rectangleArea(width: Double, height: Double): Double {  
    return width * height 
}
 
//定义计算三角形面积函数 
//函数类型: (Double, Double) -> Double 
fun triangleArea(bottom: Double, height: Double) = 0.5 * bottom * height 
 
//函数类型: ()->Unit 
fun sayHello() { 
    print("Hello, World") 
}
 
fun main(args: Array<String>) { 
 
   val getArea: (Double, Double) -> Double = ::triangleArea  
 
   //调用函数 
   val area = getArea(50.0, 40.0) 
   print(area) //1000.0 
}

每一个函数都有函数类型,即便是函数列表中没有参数,以及没有返回值的函数也有函数类型,如代码sayHello()函数,sayHello()函数的函数类型是()->Unit。
1.3 函数字面量
函数类型可以声明的变量,那么函数类型变量能够接收什么的数据呢?即函数字面量如何表示的问题,函数字面量可以有三种表示:
① 函数引用。引用到一个已经定义好的,有名字的函数。它可以作为函数字面量。
② 匿名函数。没有名字的函数,即匿名函数,它也可以作为函数字面量。
③ Lambda 表达式。 Lambda 表达式是一种匿名函数,可以作为函数字面量。

package com.work.section 
 //该函数的返回值类型是:函数类型,即(Int, Int) -> Int
 fun calculate(opr: Char): (Int, Int) -> Int { 
     //加法函数 
     fun add(a: Int, b: Int): Int { 
        return a + b 
     }
     //减法函数 
     fun sub(a: Int, b: Int): Int { 
        return a - b 
     }
 
     val result: (Int, Int) -> Int = 
         when (opr) { 
             //函数引用,采用“双冒号加函数名”形式引用,add和sub是两个局部函数,它们的函数引用表示方式是::add和::sub
             '+' -> ::add 
             '-' -> ::sub  
             //匿名函数
             '*' -> { 
                     //乘法匿名函数 
                      fun(a: Int, b: Int): Int {  
                         return (a * b) 
                      } 
              } else -> { a, b -> (a / b) } //除法Lambda表达式  
           }
           return result 
         } 
 
fun main(args: Array<String>) { 
     //f0的类型是函数类型,即(Int, Int) -> Int
     val f0:(Int, Int) -> Int = calculate('+') 
     println(f0(10, 5)) //调用f0变量
 
     //如果表达式返回类型确定了,则可以省略变量后的类型声明
     val f1 = calculate('+')  
     println(f1(10, 5)) //调用f1变量  
     
     //f2的类型是函数类型,即(Int, Int) -> Int  
     val f2 = calculate('-') 
     println(f2(10, 5)) 
 
     //f3的类型是函数类型,即(Int, Int) -> Int
     val f3 = calculate('*') 
     println(f3(10, 5)) 
 
     //f4的类型是函数类型,即(Int, Int) -> Int
     val f4 = calculate('/') 
     println(f4(10, 5)) 
}

其中,calculate函数的返回类型就是(Int, Int) -> Int函数类型,说明calculate是高阶函数。
输出结果:

com.xw.kotlinforandroid I/System.out: --------函数类型输出 加、加、减、乘、除-------
com.xw.kotlinforandroid I/System.out: 15
com.xw.kotlinforandroid I/System.out: 15
com.xw.kotlinforandroid I/System.out: 5
com.xw.kotlinforandroid I/System.out: 50
com.xw.kotlinforandroid I/System.out: 2

1.5 函数作为参数使用
作为高阶函数还可以接收另一个函数作为参数使用。

//定义计算长方形面积函数 
//函数类型: (Double, Double) -> Double 
fun rectangleArea(width: Double, height: Double): Double {  
    return width * height 
}
 
//定义计算三角形面积函数 
//函数类型: (Double, Double) -> Double 
fun triangleArea(bottom: Double, height: Double) = 0.5 * bottom * height 
 
//高阶函数,funcName参数是函数类型:(Double, Double) -> Double
fun getAreaByFunc(funcName: (Double, Double) -> Double, a: Double, b: Double): Double {  
   return funcName(a, b) 
}
 
fun main(args: Array<String>) { 
    //获得计算三角形面积函数 
    var result = getAreaByFunc(::triangleArea, 10.0, 15.0)println("底10 高15,计算三角形面积:$result")//获得计算长方形面积函数 
    result = getAreaByFunc(::rectangleArea, 10.0, 15.0)println("宽10 高15,计算长方形面积:$result")}

运行结果:

1015,三角形面积:75.01015,计算长方形面积:150.0

(二)lambda表达式
2.1 Lambda表达式
Lambda 表达式是一种匿名函数,可以作为表达式、函数参数和函数返回值使用,Lambda 表达式的运算结果是一个函数。
Lambda表达式标准语法格式 :Kotlin中的 Lambda 表达式很灵活,其标准语法格式如下:

{ 参数列表 -> 
        Lambda体 
}

其中, Lambda 表达式的参数列表与函数的参数列表形式类似,但是 Lambda 表达式参数列表前后没有小括号。箭头符号将参数列表与Lambda 体分隔开, Lambda 表达式不需要声明返回类型。
Lambda 表达式可以有返回值,如果没有 return 语句 Lambda 体的最后一个表达式就是Lambda 表达式的返回值,如果有 return 语句返回值是 return 语句后面的表达式。

提示: Lambda 表达式与函数、匿名函数一样都有函数类型,但从 Lambda 表达式的定义中只能看到参数类型,看不到返回类型声明,那是因为返回类型可以通过上下文推导出来。

private fun calculate(opr: Char): (Int, Int) -> Int { 
      return when (opr) { 
          //lambda表达式
          '+' -> { a: Int, b: Int -> a + b } 
          '-' -> { a: Int, b: Int -> a - b }  
          '*' -> { a: Int, b: Int -> a * b }  
          else -> { a: Int, b: Int -> a / b } 
         } 
}
 
fun main(args: Array<String>) { 
      val f1 = calculate('+')  
      println(f1(10, 5)) //调用f1变量  
 
      val f2 = calculate('-') 
      println(f2(10, 5)) 
 
      val f3 = calculate('*') 
      println(f3(10, 5)) 
 
      val f4 = calculate('/') 
      println(f4(10, 5)) 
}

Lambda表达式也是函数类型,可以声明变量,也可以作为其他函数的参数或者返回值使用。

2.2 lambda表达式的简化用法
①参数类型推导简化
类型推导是 Kotlin 的强项, Kotlin 编译器可以根据上下文环境推导出参数类型和返回值类型。以下代码是标准形式的Lambda 表达式:

{ a: Int, b: Int -> a + b }

Kotlin能推导出参数a和b是Int类型,当然返回值也是Int类型。简化形式如下:

{ a, b -> a + b }

② 使用尾随Lambda表达式

Lambda表达式可以作为函数的参数传递,如果Lambda表达式很长,就会影响程序的可读性。如果一个函数的最后一个参数是Lambda表达式,那么这个Lambda表达式可以放在函数括号之后(注意这里所说的lambda表达式是包括花括号"{}"的)。示例代码如下:

fun calculatePrint1(funN: (Int, Int) -> Int) { //参数是函数类型 
    //使用funN参数 
    println("${funN(10, 5)}") 
}
 
//打印计算结果函数 最后一个参数是函数类型:(Int, Int) -> Int 
fun calculatePrint(n1: Int, n2: Int, opr: Char, funN: (Int, Int) -> Int) {
     println("${n1} ${opr} ${n2} = ${funN(n1, n2)}") 
}
 
fun main(args: Array<String>) { 
     calculatePrint(10, 5, '+', { a, b -> a + b })//标准形式 
     calculatePrint(10, 5, '-') { a, b -> a - b }//尾随Lambda表达式形式  
 
     //最后一个参数是lambda表达式:{ a, b -> a + b }
     calculatePrint1({ a, b -> a + b })//标准形式 
     calculatePrint1() { a, b -> a + b }//尾随Lambda表达式形式  
     calculatePrint1 { a, b -> a + b }//尾随Lambda表达式,如果小括号里没有参数可省略小括号 
}

注意:由于calculatePrint1函数只有一个lambda表达式作为参数,它采用了尾随Lambda表达式形式,这样一来它的小括号中就没有参数了,这种情况下可以省略小括号。

③ 省略参数声明

如果Lambda表达式的参数只有一个,并且能够根据上下文环境推导出它的数据类型,那么这个参数声明可以省略,在Lambda体中使用隐式参数it替代Lambda表达式的参数。

fun revreseAndPrint(str: String, funN: (String) -> String) { 
      val result = funN(str) 
      println(result) 
}
fun main(args: Array<String>) { 
      revreseAndPrint("hello", { s -> s.reversed() })//标准形式 
      revreseAndPrint("hello", { it.reversed() })//省略参数,使用隐式参数it  
      
      //由于result1被未指定数据类型,编译器不能推导出来Lambda表达式的参数类型,所以不能使用it。
      val result1 = { a: Int -> println(a) }//不能省略参数声明  
      
      //由于result2被指定了数据类型(Int)->Unit,编译器能推导出 Lambda表达式的参数类型,所以可以使用it。
      val result2:(Int)->Unit = { println(it) }//可以省略参数声明  
      result2(30) //输出结果是30 
}

上述代码num.forEach是使用了forEach函数,它后面的Lambda表达式,如果使用代码 if (it == 10) return -1语句,则会返回最近的函数,即sum函数,不是返回 Lambda表达式forEach。为了返回Lambda表达式则需要在return语句后面加上标签,即“if (it == 10) return@forEach”,@forEach是隐式声明标签,标签名是Lambda表达式所在函数名(forEach)。也可以为Lambda表达式声明显示标签,代码label@是Lambda表达式显示声明标签,代码“ return@label 10 ”是使用显示标签。
输出结果:

com.xw.kotlinforandroid I/System.out: 6
com.xw.kotlinforandroid I/System.out: 6666
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值