Kotlin对整洁语法的支持

常规语法

整洁语法

用到的功能

StringUtil.capitalize(s)

s.capitalize()

扩展函数

1.to(“one”)

1 to “one”

中缀调用

set.add(2)

set+=1

运算符重载

map.get(“key”)

map[“key”]

get方法约定

file.use({f->f.read() } )

file.use{it.read()}

括号外的lambda

sb.append(“yes”) sb.append(“no”)

with(sb){ append(“yes”) \n append(“no”)}

带接收者的lambda

DSL语言分类及特点

通用编程语言: 有一系列足够完善的能力来解决几乎所有能被计算机解决的问题
领域特定语言:专注在特定的任务或者领域上,并放弃的与该领域无关的功能 (外部DSL),而领域特定语言分为外部DSL与内部DSL

DSL更趋向声明式 :语言包括有命令式声明式写法 ,命令式语言描述执行操作所需步骤的确切序列,每个操作实现都被独立化了,而声明式描述了想要的结果并将执行细节留给解释它的引擎,通常让执行更有效率。

外部DSL语言
声明式写法,很难与通用编程语言的宿主应用程序结合起来使用,外部DSL语言自己的语法并不能直接嵌套使用。
内部DSL: 是使用通用编程语言编写程序的一部分,包含了外部DSL声明式和通用语言的语法优点

DSL 的结构

DSL与普通的api之间并没有明确的边界,但是DSL经常会出现一个通常在其他api中不存在的特征:结构或文法

结构:api的前后调用在一个大的结构块中。中间需要维护调用的上下文信息
命令式api:前后调用没有内在的结构,也不需要维护上下文。

比如常见的DSL结构文法:

android{

sourceSets {
main {
java {
srcDir 'src/java' // 指定源码目录
}
resources {
srcDir 'src/resources' //资源目录
}
}
}

defaultconfig{

}

}
构建结构化的API:DSL中带接收者的lambda

先来看一个表达式里面的知识点

/**
* 1.高阶函数
* 2.block: () -> Unit Lambda表达式
* 3.T类型的扩展函数
* 4.泛型函数
* 5.带接收者的Lambda block: T.() -> Unit
*/
inline fun <T> T.apply(block: T.() -> Unit): T {
block()
return this
}
普通函数类型如何转换成扩展函数类型
  1. 函数参数类型 = 函数类型(builderAction:( StringBuilder)-> Unit),将函数类型转换为扩展
    函数类型
  2. 将函数类型签名中的一个参数(类型)移到括号前面,并用一个.将它与其他的( 参
    数)类型分隔开。用 StringBuilder.() -> Unit 代替(StringBuilder ) -> Unit
  3. 这个特殊的类型( StringBuilder )就叫作接收者类型,传递给 lambda 的这个类型的值就叫作接收者对象
fun buildString( builderAction:( StringBuilder)-> Unit ): String { 
val sb = StringBuilder ()
sb.builderAction()
return sb . toString ()
}

函数类型转换成扩展函数类型

fun buildString( builderAction:StringBuilder.()-> Unit ): String { 
val sb = StringBuilder ()
sb.builderAction()
return sb . toString ()
}

扩展函数类型表达式

String.(Int,Int)->Unit

一个扩展函数类型,接收者类型是 String,两个参数类型是 Int ,返回类型是 Unit

当你将一个普通函数类型转换为扩展函数类型时,其调用方式也发生了变化 。 像调用一个扩展函数那样调用 lambda,而不是将对象作为参数传递给 lambda。在 使用普通 lambda 时,我们使用这样的语法将一个 StringBuilder 实例作为 参 数给它 :builderActi on (sb )。但当 你将它改成带接收者的 lambda 时,代码 就变成了 sb .builderAction () 。再次重 申,这里的 builderAction 并不是 StringBuilder 类的方法,它是一个函数类型的参数,但可以用调用扩展函数一 样的语法调用它。

使用 invoke约定构建更灵活的代码块嵌套

invoke约定允许把自定义类型的对象当做函数一样调用。(函数类型对象可以作为函数调用),定义invoke需要使用operator 修饰符进行修饰。入参和返回类型可以是任意受系统支持的类型:

//源自 Functions.kt
public interface Function2<in P1, in P2, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2): R
}

invoke约定调用示例:

class Lambda2 {

fun test() {
val function1= object : Function1<Int, Boolean> {
override fun invoke(p1: Int): Boolean {
println(p1)
return true
}
}
//对象 function1。
function1(1)
}

}
注意:
//这句话会被默认映射为
function1.invoke(1)

函数类型对象调用示例

//function1:(Int)->Boolean  函数对象调用

fun test2(function1:(Int)->Boolean){
function1(1)
}

Lambda,除非是内联的,都是被编译成实现了函数式接口(Functionl 等)的类,而这些接
口定义了具有对应数量参数的 invoke 方法,如上 function1:(Int)->Boolean编译成字节码后会被系统使用function1函数进行替换。

使用invoke约定定义,可以将lambda函数体中抽取的方法的作用域尽可能的缩小,能够在不耦合外部逻辑的情况下实现代码解耦。

例子:

package dsl

data class Issue(val id:Int, val name:String)

class ImportantIssuesPredicate(val name:String):(Issue)->Boolean{
override fun invoke(p1: Issue): Boolean {
return p1.name.equals(name) && p1.isImport()
}

fun Issue.isImport(): Boolean {
return name.equals(name) && id==1000
}

}

fun main() {
val iss1=Issue(1000,"test")
val iss2=Issue(1001,"import")
val importantIssuesPredicate=ImportantIssuesPredicate("test")

for( issu in listOf<Issue>(iss1,iss2).filter(importantIssuesPredicate)){
println(issu.name)
}

}

如上将 (Issue)->Boolean 最为基类。并复写invoke方法,并且定义了Issue.import( )扩展函数,将对比的代码抽取到mportantIssuesPredicate 函数类型对象中。如果不使用nvoke 约定,就需要将判断代码嵌套到for函数中。

DSL中的"invoke" 约定:在Gradle中的声明依赖

下面是开发中常见的依赖配置:

//扁平调用结构,只有一个依赖时可用
dependencies.compileOnly()
//嵌套代码块结构,有多个依赖时可用
dependencies {
chinaImplementation "com.reworld.android:unity-sdk:1.0.0"
}

如上: dependencies 对象是 DependencyHandler类型。

package dsl

class DependencyHandler {

fun compile(dependency:String){
println(dependency)
}
// DependencyHandler作为invoke函数入参的接收者类型。函数体中作为隐式接收者存在。
operator fun invoke(body:DependencyHandler.()->Unit){
body()
}
}

fun main() {
val dependencyHandler=DependencyHandler()
dependencyHandler.compile("123")
dependencyHandler{
compile("123")
compile("123")
compile("123")
}
}

看到main()函数中写法和build.gradle 中脚本写法一致。通过这样设计的好处,有多个依赖就可以使用嵌套结构,一个依赖可以使用扁平调用结构

实践中Dsl用法

  1. 中缀调用链接起来:测试框架中的“should”

infix fun T.should(matcher:Matcher) =matcher.test(this)

infix中缀标示,可以不用写. 进行调用。

​kotlintest​

  1. 在基本类型上定义扩展:处理日期

val yesterday =1.days.ago
val tomorrow =1.days.fromNow

​kxDate​

  1. 为sql设计的内部Dsl

​Exposed框架​

  1. ​Anko​​ :动态创建Android Ui

总结:

内部Dsl 是一种Api设计模式,借助多个方法调用组成的结构,可以使用这种模式来构建更表意的Api,带接收者lambda采用嵌套结构重新定义函数体中的方法如何解析。成员扩展函数依然收到容器的限制。与普通扩展函数具有不同的使用的场景。用作参数的带接收者的lambda,其类型是扩展函数类型,并且这个调用函数在调用lambda时会为它提供一个接收者实例。

​​1.关于Kotlin扩展函数与lambda的上下文

​​2.标识符、修饰符、关键字解释