lambda和kotlinDSL

478 阅读9分钟

lambda知识

Lambda介绍

Lambda表达式的在java中本质其实是匿名函数,kotlin中代表的是函数类型的实参,因为在其底层实现中还是通过匿名函数来实现的。但是我们在用的时候不必关心起底层实现。不过Lambda的出现确实是减少了代码量的编写,同时也是代码变得更加简洁明了。 Lambda作为函数式编程的基础,其语法也是相当简单的。这里先通过一段简单的代码演示没让大家了解Lambda表达式的简洁之处。

// 这里举例一个Android中最常见的按钮点击事件的例子

mBtn.setOnClickListener(object : View.OnClickListener{
    override fun onClick(v: View?) {
 Toast.makeText(this,"onClick",Toast.LENGTH_SHORT).show()
 }
})

等价于

// 调用 mBtn.setOnClickListener { Toast.makeText(this,"onClick",Toast.LENGTH_SHORT).show() }

Lambda使用

lambda表达式的特点

  • lambda表达式总是被大括号包裹
  • 参数在->之前申明
  • 函数体在->后面

lambda表达式语法

为了让大家彻底的弄明白Lambda语法,我这里用三种用法来讲解。并且举例为大家说明

1 无参数情况 var 变量名={操作代码}

2 有参数 val/var 变量名 : (参数的类型,参数类型,...) -> 返回值类型 = {参数1,参数2,... -> 操作参数的代码 } 可等价于 // 此种写法:即表达式的返回值类型会根据操作的代码自推导出来。 val/var 变量名 = { 参数1 : 类型,参数2 : 类型, ... -> 操作参数的代码 }

3 lambda表达式作为函数中的参数的时候,这里举一个例子:

  fun test(a : Int, 参数名 : (参数1 : 类型,参数2 : 类型, ... ) -> 表达式返回类型){
        ...
    }

无参数的情况

   // 源代码
   fun test(){ println("无参数") }
  
    // lambda代码
    val test = { println("无参数") }

    // 调用
    test()  => 结果为:无参数
    
     // 源代码
    fun test(a : Int , b : Int) : Int{
        return a + b
    }

有参数的情况,这里举例一个两个参数的例子,目的只为大家演示

    // lambda
    val test : (Int , Int) -> Int = {a , b -> a + b}
    // 或者
    val test = {a : Int , b : Int -> a + b}
    
    // 调用
    test(3,5) => 结果为:8
    
    lambda表达式作为函数中的参数的时候
       // 源代码
    fun test(a : Int , b : Int) : Int{
        return a + b
    }

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

    // 调用
    test(10,sum(3,5)) // 结果为:18

    // lambda
    fun test(a : Int , b : (num1 : Int , num2 : Int) -> Int) : Int{
        return a + b.invoke(3,5)
    }

    // 调用
    test(10,{ num1: Int, num2: Int ->  num1 + num2 })  // 结果为:18

函数类型

都说可以将函数作为变量值传递,那该变量的类型如何定义呢?函数变量的类型统称函数类型,所谓函数类型就是声明该函数的参数类型列表和函数返回值类型。 先看个简单的函数类型:

  () -> Unit

函数类型和lambda一样,使用 -> 作分隔符,但函数类型是将参数类型列表和返回值类型分开,所有函数类型都有一个圆括号括起来的参数类型列表和返回值类型。

一些相对简单的函数类型:

  //无参、无返回值的函数类型(Unit 返回类型不可省略)
() -> Unit
//接收T类型参数、无返回值的函数类型
(T) -> Unit
//接收T类型和A类型参数、无返回值的函数类型(多个参数同理)
(T,A) -> Unit
//接收T类型参数,并且返回R类型值的函数类型
(T) -> R
//接收T类型和A类型参数、并且返回R类型值的函数类型(多个参数同理)
(T,A) -> R

较复杂的函数类型:

  (T,(A,B) -> C) -> R

一看有点复杂,先将(A,B) -> C抽出来,当作一个函数类型Y,Y = (A,B) -> C,整个函数类型就变成(T,Y) -> R。

当显示声明lambda的函数类型时,可以省去lambda参数列表中参数的类型,并且最后一行表达式的返回值类型必须与声明的返回值类型一致:
  val min:(Int,Int) -> Int = { x,y ->
    //只能返回Int类型,最后一句表达式的返回值必须为Int
    //if表达式返回Int
    if (x < y){
        x
    }else{
        y
    }
}

类型别名

typealias alias = (String,(Int,Int) -> String) -> String
typealias alias2 = () -> Unit

Lambda语句简化

data class Person(val age:Int,val name:String)
val persons = listOf(Person(17,"daqi"),Person(20,"Bob"))
//寻找年龄最大的Person对象
//花括号的代码片段代表lambda表达式,作为参数传递到maxBy()方法中。
persons.maxBy( { person: Person -> person.age } )

当lambda表达式作为函数调用的最后一个实参,可以将它放在括号外边:

persons.maxBy() { person: Person -> 
    person.age 
}

当lambda是函数唯一的实参时,还可以将函数的空括号去掉:

persons.maxBy{ person: Person -> 
    person.age 
}

跟局部变量一样,lambda参数的类型可以被推导处理,可以不显式的指定参数类型:

persons.maxBy{ person -> 
    person.age 
}

当lambda表达式中只有一个参数,没有显示指定参数名称,并且这个参数的类型能推导出来时,会生成默认参数名称it

    persons.maxBy{ 
    it.age
}
   

当函数需要两个或以上的lambda实参时,不能把超过一个的lambda放在括号外面,这时使用常规传参语法来实现是最好的选择。

带接收者的lambda表达式

目前讲到的lambda都是普通lambda,lambda中还有一种类型:带接收者的lambda。 带接受者的lambda的类型定义:

A.() -> C 

表示可以在A类型的接收者对象上调用并返回一个C类型值的函数。 带接收者的lambda好处是,在lambda函数体可以无需任何额外的限定符的情况下,直接使用接收者对象的成员(属性或方法),亦可使用this访问接收者对象。 Kotlin的标准库中就有提供带接收者的lambda表达式:with和apply

val stringBuilder = StringBuilder()
val result = with(stringBuilder){
    append("daqi在努力学习Android")
    append("daqi在努力学习Kotlin")
    //最后一个表达式作为返回值返回
    this.toString()
}
//打印结果便是上面添加的字符串
println(result)

with 源碼
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {

}

其lambda的函数类型表示,参数类型和返回值类型可以为不同值,也就是说可以返回与接收者类型不一致的值。

apply函数几乎和with函数一模一样,唯一区别是apply始终返回接收者对象。对with的代码进行重构:

val stringBuilder = StringBuilder().apply {
    append("daqi在努力学习Android")
    append("daqi在努力学习Kotlin")
}
println(stringBuilder.toString())
查看apply函数的定义:
public inline fun <T> T.apply(block: T.() -> Unit): T {
}

Lambda实践

it

it并不是Kotlin中的一个关键字(保留字)。 it是在当一个高阶函数中Lambda表达式的参数只有一个的时候可以使用it来使用此参数。it可表示为单个参数的隐式名称,是Kotlin语言约定的。

这里举例一个语言自带的一个高阶函数filter,此函数的作用是过滤掉不满足条件的值 val arr = arrayOf(1,3,5,7,9) 过滤掉数组中元素小于2的元素,取其第一个打印。这里的it就表示每一个元素。 println(arr.filter { it < 5 }.component1())

下划线(_)

val map = mapOf("key1" to "value1","key2" to "value2","key3" to "value3")

map.forEach{
  key , value -> println(key value)
}

// 不需要key的时候

map.forEach{
    _ , value -> println("$value")
}`

匿名函数

常规函数

fun test(x : Int , y : Int) : Int{   return x+y}

匿名函数

fun(x:Int,y:Int):Int{
    return x+y
}

函数体 一个表达式 可以转换为 单表达式函数

常规函数 : fun test(x : Int , y : Int) : Int = x + y 匿名函数 : fun(x : Int , y : Int) : Int = x + y

从上面的两个例子可以看出,匿名函数与常规函数的区别在于一个有函数名,一个没有。

实例演练:

val test1 = fun(x : Int , y : Int) = x + y  // 当返回值可以自动推断出来的时候,可以省略,和函数一样
val test2 = fun(x : Int , y : Int) : Int = x + y
val test3 = fun(x : Int , y : Int) : Int{
    return x + y
}

println(test1(3,5))
println(test2(4,6))
println(test3(5,7))

输出结果为:

8
10
12

从上面的代码我们可以总结出匿名函数与Lambda表达式的几点区别:

1.匿名函数的参数传值,总是在小括号内部传递。而Lambda表达式传值,可以有省略小括号的简写写法。

2.在一个不带标签的return语句中,匿名函数时返回值是返回自身函数的值,而Lambda表达式的返回值是将包含它的函数中返回。

带接收者的函数字面值

在kotlin中,提供了指定的接受者对象调用Lambda表达式的功能。在函数字面值的函数体中,可以调用该接收者对象上的方法而无需任何额外的限定符。它类似于扩展函数, 它允你在函数体内访问接收者对象的成员。

匿名函数作为接收者类型 匿名函数语法允许你直接指定函数字面值的接收者类型,如果你需要使用带接收者的函数类型声明一个变量,并在之后使用它,这将非常有用。

val iop = fun Int.( other : Int) : Int = this + other
println(2.iop(3))  结果为5

要用Lambda表达式作为接收者类型的前提是接收着类型可以从上下文中推断出来。

apply源码
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}


   tv_button.apply {
            text="123"
            setOnClickListener {  }
        }

闭包

  • 所谓闭包,即是函数中包含函数,这里的函数我们可以包含(Lambda表达式,匿名函数,局部函数,对象表达式)。我们熟知,函数式编程是现在和未来良好的一种编程趋势。故而Kotlin也有这一个特性。
  • 我们熟知,Java是不支持闭包的,Java是一种面向对象的编程语言,在Java中,对象是他的一等公民。函数和变量是二等公民。
  • Kotlin中支持闭包,函数和变量是它的一等公民,而对象则是它的二等公民了。

看一段Java代码

public class TestJava{

    private void test(){
        private void test(){        // 错误,因为Java中不支持函数包含函数

        }
    }

    private void test1(){}          // 正确,Java中的函数只能包含在对象中+
}

实例:看一段Kotlin代码


fun test1(){
    fun test2(){   // 正确,因为Kotlin中可以函数嵌套函数
        
    }
}

什么是DSL?

DSL是domin specific language的缩写,中文名叫做领域特定语言,指的是专注于某个应用程序领域的计算机语言,比如显示网页的HTML、用于数据库处理的SQL、用于检索或者替换文本的正则表达式,它们都是DSL。与DSL相对的就是GPL,GPL是General Purpose Language的简称,即通用编程语言,就是我们非常熟悉的Java、C、Objective-C等等。

DSL分为外部DSL和内部DSL。外部DSL是一种可以独立解析的语言,就像SQL,它专注于数据库的操作;内部DSL是通用语言暴露的用来执行特定任务的API,它利用语言本身的特性,将API以特殊的形式暴露出去,例如Android的Gradle和iOS的依赖管理组件CocosPods,Gradle是基于Groovy的,Groovy是一种通用语言,但是Gradle基于Groovy的语法,构建了自己一套DSL,所以在配置Gradle的时候,必须遵循Groovy的语法,还要遵循Gradle的DSL标准。

Android的Gradle文件


apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

android {
    compileSdkVersion 28

    defaultConfig {
        applicationId "com.tanjiajun.androidgenericframework"
        minSdkVersion rootProject.minSdkVersion
        targetSdkVersion rootProject.targetSdkVersion
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    androidExtensions {
        experimental = true
    }

    dataBinding {
        enabled = true
    }

    packagingOptions {
        pickFirst 'META-INF/kotlinx-io.kotlin_module'
        pickFirst 'META-INF/atomicfu.kotlin_module'
        pickFirst 'META-INF/kotlinx-coroutines-io.kotlin_module'
    }
}


实现原理

这篇文章主要讲的是Kotlin的DSL,在讲之前我们先了解下这几个概念和语法。

扩展函数

Kotlin的扩展函数可以让你作为一个类成员进行调用的函数,但是是定义在这个类的外部。这样可以很方便的扩展一个已经存在的类,为它添加额外的方法。在Kotlin源码中,有大量的扩展函数来扩展java,这样使得Kotlin比java更方便使用,效率更高。通常在java中,我们是以各种XXXUtils的方式来对已经存在的类进行功能的扩展。但是有了扩展函数,我们就能丢弃让人讨厌的XXXUtils方法工具类。下面举个例子,假如我们需要为String类型添加一个返回这个字符串最后一个字符的方法:

fun String.lastChar(): Char = this.get(this.length - 1)

fun main(args: Array<String>) {
    println("Kotlin".lastChar())
}

你只需要在你添加的函数名字之前放置你想要扩展的类或者接口的类型。这个类名叫着接收器类型(receiver type),而你调用的扩展函数的值叫做接收器对象(receiver object)。如下图:

接收器类型是扩展定义的类型,而接收器对象是这个类型的实例。调用方式跟普通的函数调用方式一致:

println("Kotlin".lastChar())

lambda 上面已经讲过

中缀表示法

标有 infix 关键字的函数也可以使用中缀表示法(忽略该调用的点与圆括号)调用。中缀函数必须满足以下要求:

  • 它们必须是成员函数或扩展函数;
  • 它们必须只有一个参数;
  • 其参数不得接受可变数量的参数且不能有默认值。

package foo.bar; //将扩展方法放到包内,作用域将是整个包

infix fun Int.ride(num: Int): Int{
    println("num= $num")
    return 2 * num
}
 
import foo.bar.* //使用时需要导包
 
object MyTest {
    @JvmStatic
    fun main(arg: Array<String>) {
        println(3 ride 2)
    }
}

infix fun Int.plus(x: Int): Int =
        this.plus(x)

// 可以中缀表示法调用函数
1 plus 2

// 等同于这样
1.plus(2)

源码当中
mapOf<String,Any>("name" to "TanJiaJun","age" to 25)
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)




回调处理

JAVA 中

我们实现的步骤一般是这样:

定义一个接口

在接口中定义一些回调方法

定义一个设置回调接口的方法,这个方法的参数是回调接口的实例,一般以匿名对象的形式存在。

EditText etCommonCallbackContent = findViewById(R.id.et_common_callback_content);
etCommonCallbackContent.addTextChangedListener(new TextWatcher() {
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        // no implementation
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        // no implementation
    }

    @Override
    public void afterTextChanged(Editable s) {
        // no implementation
    }
});

Kotlin中的回调实现

findViewById<EditText>(R.id.et_common_callback_content).addTextChangedListener(object :
    TextWatcher {
    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        // no implementation
    }

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        // no implementation
    }

    override fun afterTextChanged(s: Editable?) {
        tvCommonCallbackContent.text = s
    }
})

dialog 弹框实现dsl方式


package me.chen_wei.samples.dialog

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.DialogFragment
import com.example.kotlintestdemo.R


class CustomDialogFragment: DialogFragment() {

    private var titleTv: TextView? = null
    private var messageTv: TextView? = null
    private var leftButton: TextView? = null
    private var rightButton: TextView? = null

    private var leftClicks: (() -> Unit)? = null
    private var rightClicks: (() -> Unit)? = null

    var cancelOutside: Boolean = true

    var title: String? = null
    var message: String? = null
    var leftKey: String? = null
    var leftButtonDismissAfterClick = true
    var rightKey: String? = null
    var rightButtonDismissAfterClick = true

    companion object {
        fun newInstance(): CustomDialogFragment {
            return CustomDialogFragment()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setStyle(DialogFragment.STYLE_NO_TITLE, android.R.style.Theme_Material_Light_Dialog)
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view = inflater?.inflate(R.layout.dialog_custom, container)
        titleTv = view?.findViewById(R.id.title_tv)
        messageTv = view?.findViewById(R.id.message_tv)
        leftButton = view?.findViewById(R.id.left_button)
        rightButton = view?.findViewById(R.id.right_button)

        init()
        return view
    }

    private fun init() {
        dialog?.setCancelable(cancelOutside)

        title?.let { text ->
            titleTv?.visibility = View.VISIBLE
            titleTv?.text = text
        }

        message?.let { text ->
            messageTv?.visibility = View.VISIBLE
            messageTv?.text = text
        }

        leftClicks?.let { onClick ->
            leftButton?.text = leftKey
            leftButton?.visibility = View.VISIBLE
            leftButton?.setOnClickListener {
                onClick()
                if (leftButtonDismissAfterClick) {
                    dismissAllowingStateLoss()
                }
            }
        }

        rightClicks?.let { onClick ->
            rightButton?.text = rightKey
            rightButton?.setOnClickListener {
                onClick()
                if (rightButtonDismissAfterClick) {
                    dismissAllowingStateLoss()
                }
            }
        }
    }

    fun leftClicks(key: String = "取消", dismissAfterClick: Boolean = true, callback: () -> Unit) {
        leftKey = key
        leftButtonDismissAfterClick = dismissAfterClick
        leftClicks = callback
    }

    fun rightClicks(key: String = "确定", dismissAfterClick: Boolean = true, callback: () -> Unit) {
        rightKey = key
        rightButtonDismissAfterClick = dismissAfterClick
        rightClicks = callback
    }
}


// 给添加 activity show方法

inline fun AppCompatActivity.showDialog(settings: CustomDialogFragment.() -> Unit) : CustomDialogFragment {
    val dialog = CustomDialogFragment.newInstance()
    dialog.apply(settings)
    val ft = this.supportFragmentManager.beginTransaction()
    val prev = this.supportFragmentManager.findFragmentByTag("dialog")
    if (prev != null) {
        ft.remove(prev)
    }
    ft.addToBackStack(null)
    dialog.show(ft, "dialog")
    return dialog
}


最后 activity中调用

                showDialog {
                    cancelOutside = false
                    title = "Dialog Fragment"
                    message = "A fragment that displays a dialog window, floating on top of its activity's window. "
                    rightClicks(key = "Yes") {
//                        toast("Right Button Clicked!")
                        Toast.makeText(context, "rightClicks", Toast.LENGTH_SHORT).show()
                    }
                    leftClicks {   Toast.makeText(context, "leftClicks", Toast.LENGTH_SHORT).show() }
                }

Kotlin函数和函数表达式的四种表达方式

fun main(args: Array<String>)() {
var result = add(3,5)
    println(result)
    
    var result2 = add2(3,5)
    println(result2)
 
    var i = {x:Int,y:Int -> x+y}
    println(i(3,5))
 
    var j:(Int,Int) ->Int = {x,y -> x+y}
    var result3 = j(3,5)
    println(result3)
}
fun add(x:Int,y:Int):Int{
    return x+y
}
fun add2(x:Int,y:Int):Int = x+y

fun Test() {
var result = add(3,5)
    println(result)
    
    var result2 = add2(3,5)
    println(result2)
 
    var i = {x:Int,y:Int -> x+y}
    println(i(3,5))
 
    var j:(Int,Int) ->Int = {x,y -> x+y}
    var result3 = j(3,5)
    println(result3)
}
fun add(x:Int,y:Int):Int{
    return x+y
}
fun add2(x:Int,y:Int):Int = x+y