写点什么

Kotlin 之 DSL,java 面试写代码

用户头像
极客good
关注
发布于: 刚刚

前言


=================================================================


入职新公司后发现 kotlin 的语法知识跟不上,因为公司开发团队的习惯问题,原先的公司还是 java+kt 混编开发的模式,许多同事基本不用 kt,一些不常规的语法应用很少接触使用,还存在使用错误的情况,所以现在要补一些 ktolin 语法使用的知识,而 DSL 就是第一个点


一、What’s the DSL?


================================================================================


先上一小段可爱的代码:


ConstraintLayout {


layout_width = wrap_content


layout_height = wrap_content


background_res = R.drawable.ic1


ImageView {


layout_width = 22


layout_height = 22


top_toTopOf = parent_id


end_toEndOf = parent_id


margin_top = 9


margin_end = 5


padding = 5


scaleType = scale_fit_xy


src = R.drawable.ic2


}


TextView {


layout_id = "tv1"


layout_width = wrap_content


layout_height = wrap_content


textColor = "#ff3f4658"


textSize = 16f


textRes = R.string.tv1


center_horizontal = true


top_toBottomOf = "iv"


margin_top = 3


}


TextView {


layout_id = "tv2"


layout_width = wrap_content


layout_height = wrap_content


textColor = "#ffffff"


textSize = 12f


center_horizontal = true


top_toBottomOf = "tv1"


margin_top = 8


text = "deo"


}


}


土包子的我第一次看到这样的定义布局方式并没有感觉有啥新奇,不就是动态设置控件嘛,并没有什么高级的地方,反而觉得不如 XML 直观且无法预览,但一仔细看就不对了,按照常规的写法应该是从父布局开始逐个初始化对象,然后通过 layouparmars 设置布局属性添加到相应容器中,这种写法是什么情况,貌似直接嵌套还直接定义了控件属性,和 xml 的语法很像但貌似更简便(XML 也可以看做是 DSL)。此时只有一个字可以形容



通过偷偷摸摸的百度后知道这就是传说中的 DSL 构建的布局方式,这个东西是有了解过的,但没深入的看过(看完就忘),kt 的部分语法感觉过于难以阅读所以一直抗拒(其实是菜看不懂),靠着 apply、let、with 苟延残喘的使用着,还感觉自己 kt 用的很 nice。



这么陌生且高大上的词汇看的第一眼就感觉很难,自然反应的 Google 一下:



给出的解释就是*领域专用语言***。 词汇太专业,依然不知道是啥。。。查的时候还看到另一个词:GPL ,有必要了解一下,General Purpose Language 翻译过来就是通用语言,如 Java、OC、C 等等,这个好理解脑子一下就知道是啥了。这些语言使用很广泛,像 java 能 写 android 也能写后端,业务代码都是用这些语言写的。


对比一下 GPL,DSL 的针对性更强,只适用于特定的功能方向,就像文言文和白话文,一个用于作文记事,一个用于交际用语,举个可能不恰当的栗子,墓志铭就有特定的格式,第一他只能写给死人,第二有固定的格式,其他的像啥三言、五言、七言的,不是很准确但就是那个意思吧。




二、初探


===================================================================


1.DSL 常见的应用




简单的了解过后感觉 DSL 更像是一种自己构建出来快捷的语法格式或者说结构,像最上面的布局代码其实最后做的工作应该和普通的写法(控件都最后都要初始化)是一样的,但对于项目来说大量频繁的重写就会省力很多且代码格式更好,当然好处不止于此。很多东西都见过也用过只是不知道原来这么 D 伊奥。


第一个众所周知的 HTML 就是典型的 DSL,首先她不算是编程语言,它定义了特殊的网页结构标示不同的含义,还一个特点他不能像 java 那样写出复杂的逻辑,它们都是命令式的,瞬间清醒,此时 DSL 和 GPL 的区别通透了。


其他的一些我们接触过的包括 Gradle、SQL,按照上面的特点,SQL 语句能写逻辑吗?Gradle 语法能写复杂逻辑吗?



他们的语句还有些共同点,仔细的回忆一下 SQL、Gradle 语句,是不是容易忘记,容易不认识,容易写错,可能一年没写过一条 SQL 的人大把抓,但他们都比较有特定的结构且只能用于实现特定功能,因为语法和常规的语言编写格式不同所以容易忘记。SQL 编写语句管理数据,Gradle 配置项目用于构建,HTML 用于显示网页文本内容,而不像 Java 或者其他编程语言一样,没有特定的限制,想在哪用在哪用。


三.koltin 中的 DSL


============================================================================


要实现开头的布局方式需要怎么做呢?因为 SDK 也没定义这种规范,kt 也没有直接提供,所以只能内部创造,手动满足。


在 kotlin 中实现 DSL 构建要靠两样东西:


.扩展函数;


.带接收者的 Lambda 表达式;


先说扩展函数,这个还是比较简单的。


1.扩展函数




fun String.customOperation(append:String) :String{


return this+append


}


上面写了个 String 的扩展函数,自定义了一个操作,对接受者拼接了 append 字符串,在其他地方可以把 customOperation 当做系统的 api 直接调用。扩展函数的作用也一目鸟然,可以封装频率使用高的操作代码,可以像使用原生 API 一样使用扩展方法。


再看一下字节码:


// access flags 0x19


public final static customOperation(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;


@Lorg/jetbrains/annotation


【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


s/NotNull;() // invisible


// annotable parameter count: 2 (visible)


// annotable parameter count: 2 (invisible)


@Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0


@Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 1


L0


ALOAD 0


LDC "customOperation"


INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V


ALOAD 1


LDC "append"


INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V


L1


LINENUMBER 11 L1


NEW java/lang/StringBuilder


DUP


INVOKESPECIAL java/lang/StringBuilder.<init> ()V


ALOAD 0


INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;


ALOAD 1


INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;


INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;


ARETURN


L2


LOCALVARIABLE customOperation Ljava/lang/String; L0 L2 0


LOCALVARIABLE append Ljava/lang/String; L0 L2 1


MAXSTACK = 2


MAXLOCALS = 2


@Lkotlin/Metadata;(mv={1, 4, 0}, bv={1, 0, 3}, k=2, d1={"\u0000\n\n\u0000\n\u0002\u0010\u000e\n\u0002\u0008\u0002\u001a\u0012\u0010\u0000\u001a\u00020\u0001*\u00020\u00012\u0006\u0010\u0002\u001a\u00020\u0001\u00a8\u0006\u0003"}, d2={"customOperation", "", "append", "My_Application.app"})


// compiled from: Extends.kt


}


static 圈一下,一目了然,扩展方法被编译成静态方法,(Ljava/lang/String;Ljava/lang/String;) 圈一下,咦?怎么两个 String 呢?



2.带接收者的 Lambda 表达式




带接受者的 Lambda 是啥样子,首先函数式编程 Lambda 是 kt 的一大特色(简洁避免语法噪音),但高阶函数看起来就很累,阅读性感觉不太友好,过于简化(主要还是菜),rxjava、协程一起用的时候就更懵逼了,lowB 的代码不香吗?



要知道啥是高阶函数,简单无脑的理解就是函数嵌套函数,在 kt 中 lambda 是可以当做参数进行传递的



fun createString(build:(StringBuilder)->Unit):String {


val stringBuilder=StringBuilder()


build(stringBuilder)


return stringBuilder.toString()


}


如果想要通过 StringBuild 去创建一个自定义的拼接字符串可以用上面的方法,比如:



这就是 lambda 作为参数传递的一个简单栗子,但是在调用 createString 的时候,在表达式中需要显示的调用 it,这个 it 就是 StringBuilder,而这个表达式的调用者是期望或者我们已经指定了就是一个 StringBuilder,所以可以避免这种显示调用的现象,在声明表达式的时候为它指定调用者,所以修改 createString 传入的表达式:


fun createString(build:StringBuilder.()->Unit):String {


val stringBuilder=StringBuilder()


build(stringBuilder)


return stringBuilder.toString()


}


这个时候可以看到调用函数的地方报错了



可以看到编译的提示,表达式的参数提示由 it 变成了 this,这个时候我们直接可以使用 StringBuilder 的方法而不需要显示的使用 it



在上面的方法中 build:StringBuilder.()->Unit 就是一个带接受者的 Lambda 表达式,它的声明语法格式是:


调用者类型.(参数类型)->返回值类型


3.用 DSL 构建布局




最上面的 ConstraintLayout 布局其实就是基于 DSL 构建的动态布局,哪具体如何实现的呢?就拿 ConstraintLayout 来说,


首先要定义一种声明方式来初始化对象,所以可以写一个基于 Context 的扩展函数


fun Context.ConstraintLayout(): ConstraintLayout {


val constraintLayout =


if (style != null) ConstraintLayout(


ContextThemeWrapper(this, style)


) else ConstraintLayout(this)


return constraintLayout


}


这种布局容器都可以这么声明,但对于宽高、背景、方向等等的一些属性也需要处理,对于每个布局都要设置的宽高属性如何定义呢?还是利用扩展函数来实现


inline var View.layout_width: Number


get() {


return 0


}


set(value) {


val w = if (value.dp > 0) value.dp else value.toInt()


val h = layoutParams?.height ?: 0


updateLayoutParams<ViewGroup.LayoutParams> {


width = w


height = h


}


}


这里已经不是扩展函数了 ,应该说是扩展属性,layout_width 是自定义的属性,这里和 XML 中的属性定义一样的。同理对于高度可以这么写:


inline var View.layout_height: Number


get() {


return 0


}


set(value) {


val w = layoutParams?.width ?: 0


val h = if (value.dp > 0) value.dp else value.toInt()


updateLayoutParams<ViewGroup.LayoutParams> {


width = w


height = h


}

用户头像

极客good

关注

还未添加个人签名 2021.03.18 加入

还未添加个人简介

评论

发布
暂无评论
Kotlin之DSL,java面试写代码