6个能让你的Kotlin代码库更有意思的“魔法糖”

语法糖会导致分号的悲剧。—— Alan J. Perlis
我们不断地失去一些东西。其中一些东西相对来说会更重要,现在重新拣起来还不算太晚。Kotlin 语言为程序员的生活带来了大量新的概念和特性,它们在日常开发中使用起来会很困难。我在生产环境中使用了两年 Kotlin 之后,才感受到它带来的快乐和满足。这是怎么发生的?原因就在那些小小的语法糖中。
我会在本文中与你分析我最喜欢的 Kotlin 语法糖,它们是在我需要写简洁而鲁棒 Android 应用程序组件时发现的。为了让这篇文章读起来更轻松,我把它分成三个部分。在这第一部分中,你会看到密封类和 when() 控制流函数。愉快的开始吧!
<strong>拥抱“模式匹配”的密封类</strong>
最近我的工作中有机会使用 Swift。我不仅要审核代码,还要将其中一些组件翻译成 Kotlin 实现。我读的代码越多,就越感到惊讶。最对我来说,最吸引人的特性是枚举。可惜 Kotlin 的枚举并不太灵活,我不得不挖掘合适的替代品: 密封类 。
密封类在编程界并不是什么新鲜玩意儿。事实上,密封类是一个非常知名的语言概念。Kotlin 引入了 sealed 关键字,它可用于类声明,表示对类层次结构的限制。某个值可以是有限类型中的一个,但它不能是其它类型。简单地说,你可以使用密封类来代替枚举,甚至做更多事情。
来看看下面的示例代码。

sealed class Response  data class Success(val body: String): Response()  data class Error(val code: Int, val message: String): Response()  object Timeout: Response() 
乍一看,这些代码除只是声明了一些简单的继承关系,但步步深入,就会提示一个谅人的真相。为 Response 类添加的 sealed 关键字到底起到了什么作用呢?提示这个问题最好的方法是使用 IntelliJ IDEA Kotlin Bytecode 工具。
<a href="http://undefined" target="_blank" style="color: rgb(255, 66, 0); transition: all 0.5s ease; text-decoration-line: none;"><img src="http://s5.51cto.com/oss/201712/15/9cbc97b2d24d4c6f22311c842370b48a.png-wh_651x-s_2128252922.png" alt="6个能让你的Kotlin代码库更有意思的“魔法糖”" title="6个能让你的Kotlin代码库更有意思的“魔法糖”" height="auto" width="auto" style="border: none;"/></a>
第一 步。查看 Kotlin 字节码 (Kotlin Bytecode)
<a href="http://undefined" target="_blank" style="color: rgb(255, 66, 0); transition: all 0.5s ease; text-decoration-line: none;"><img src="http://s3.51cto.com/oss/201712/15/a92cfe7d2f99c6216a80eb3c07dfc01c.png" alt="6个能让你的Kotlin代码库更有意思的“魔法糖”" title="6个能让你的Kotlin代码库更有意思的“魔法糖”" height="auto" width="auto" style="border: none;"/></a>
<strong>第二步。将 Kotlin 字节码反编译成 Java 代码</strong>
经过这样非常简单地翻译,你可以看到 Kotlin 代码对应的 Java 代码呈现。

public abstract class Response {    private Response() {    }     // $FF: synthetic method    public Response(DefaultConstructorMarker $constructor_marker) {       this();    } } 
你可能已经猜到了,密封类专们用于继承,所以它们是抽象的。不过他们变得与枚举相似的?在这里,Kotlin 编译器做了大量的工作,让你可以在 when() 函数中将 Response 的子类用作分支。此外,Kotlin 提供了很大的灵活性来允许对密封类的继承结构可以被当作数据声明甚至对象来使用。

fun sugar(response: Response) = when (response) {     is Success -> ...     is Error -> ...     Timeout -> ... } 
它不仅提供了非常彻底的表达式,还提供了自动类型转换,因此你可以在不需要额外的转换的情况下使用 Response 实例。

fun sugar(response: Response) = when (response) {     is Success -> println(response.body)     is Error -> println("${response.code} ${response.message}")     Timeout -> println(response.javaClass.simpleName) } 
你能想象一下,如果没有一个 sealed 的功能,或者根本没有 Kotlin ,它可能看起来是那么的丑陋和复杂?如果你忘记了 Java 语言的一些特性,请再次使用 IntelliJ IDEA Kotlin Bytecode ,但要坐下来使用 - 这可能会让你晕倒。

public final void sugar(@NotNull Response response) {    Intrinsics.checkParameterIsNotNull(response, "response");       String var3;    if (response instanceof Success) {       var3 = ((Success)response).getBody();       System.out.println(var3);    } else if (response instanceof Error) {       var3 = "" + ((Error)response).getCode() + ' ' + ((Error)response).getMessage();       System.out.println(var3);    } else {       if (!Intrinsics.areEqual(response, Timeout.INSTANCE)) {          throw new NoWhenBranchMatchedException();       }        var3 = response.getClass().getSimpleName();       System.out.println(var3);    } } 
总结一下,我很高兴在这种情况下使用 sealed 关键字,因为它让我以类似于 Swift 的方式塑造我的 Kotlin 代码。
<strong>使用 when()函数来排列</strong>
由于你已经看到了 when()在 sealed 类中的用法,我决定再分享更多强大的功能。 想象一下,你必须实现一个接受两个 enums 并产生一个不可变状态的函数。

enum class Employee {     DEV_LEAD,     SENIOR_ENGINEER,     REGULAR_ENGINEER,     JUNIOR_ENGINEER }  enum class Contract {     PROBATION,     PERMANENT,     CONTRACTOR, } 
enum class Employee 描述了在公司 XYZ 中可以找到的所有角色, enum class Contract 包含所有类型的雇佣合同。 基于这两个 enums ,你应该返回一个正确的 SafariBookAccess 。 而且,你的函数必须产生给定 enum 的所有排列的状态。 第一步,我们来创建状态生成函数的签名。

fun access(employee: Employee,            contract: Contract): SafariBookAccess 
现在是时候定义 SafariBooksAccess 结构体了,因为你已了解 sealed 关键字,这是使用它最适合的时机。封装 SafariBookAccess 并不是必须的,但它是封装不同情景下的 SafariBookAccess 的不同状态的好方式。

sealed class SafariBookAccess  data class Granted(val expirationDate: DateTime) : SafariBookAccess()  data class NotGranted(val error: AssertionError) : SafariBookAccess()  data class Blocked(val message: String) : SafariBookAccess() 
那么隐藏在 access() 函数后面的主要意图是什么?全排列!让我们罗列下。

fun access(employee: Employee,            contract: Contract): SafariBookAccess {     return when (employee) {         SENIOR_ENGINEER -> when (contract) {             PROBATION -> NotGranted(AssertionError("Access not allowed on probation contract."))             PERMANENT -> Granted(DateTime())             CONTRACTOR -> Granted(DateTime())         }         REGULAR_ENGINEER -> when (contract) {             PROBATION -> NotGranted(AssertionError("Access not allowed on probation contract."))             PERMANENT -> Granted(DateTime())             CONTRACTOR -> Blocked("Access blocked for $contract.")         }         JUNIOR_ENGINEER -> when (contract) {             PROBATION -> NotGranted(AssertionError("Access not allowed on probation contract."))             PERMANENT -> Blocked("Access blocked for $contract.")             CONTRACTOR -> Blocked("Access blocked for $contract.")         }         else -> throw AssertionError()     } } 
这个代码很完美,但你能让它更像 Kotlin 吗?当你每天对同事的 PR/MR 进行审查时会有什么建议吗?你可能会写一些这样的评论:

<li style="padding: 0px 0px 0px 10px; margin: 0px 0px 0px -20px; list-style-type: none; background: url(&quot;../images/icon.png&quot;) 0px 13px no-repeat; line-height: 30px;">
    太多 when() 函数。使用 Pair 来避免嵌套。
</li>
<li style="padding: 0px 0px 0px 10px; margin: 0px 0px 0px -20px; list-style-type: none; background: url(&quot;../images/icon.png&quot;) 0px 13px no-repeat; line-height: 30px;">
    改变枚举参数的顺序,定义 Pair() 对象来让它更易读。
</li>
<li style="padding: 0px 0px 0px 10px; margin: 0px 0px 0px -20px; list-style-type: none; background: url(&quot;../images/icon.png&quot;) 0px 13px no-repeat; line-height: 30px;">
    合并重复的 return。
</li>
<li style="padding: 0px 0px 0px 10px; margin: 0px 0px 0px -20px; list-style-type: none; background: url(&quot;../images/icon.png&quot;) 0px 13px no-repeat; line-height: 30px;">
    改为一个表达式函数。
</li>
fun access(contract: Contract,            employee: Employee) = when (Pair(contract, employee)) {     Pair(PROBATION, SENIOR_ENGINEER),     Pair(PROBATION, REGULAR_ENGINEER),     Pair(PROBATION, JUNIOR_ENGINEER) -> NotGranted(AssertionError("Access not allowed on probation contract."))     Pair(PERMANENT, SENIOR_ENGINEER),     Pair(PERMANENT, REGULAR_ENGINEER),     Pair(PERMANENT, JUNIOR_ENGINEER),     Pair(CONTRACTOR, SENIOR_ENGINEER) -> Granted(DateTime(1))     Pair(CONTRACTOR, REGULAR_ENGINEER),     Pair(CONTRACTOR, JUNIOR_ENGINEER) -> Blocked("Access for junior contractors is blocked.")     else -> throw AssertionError("Unsupported case of $employee and $contract") } 
现在它看起来更整洁,但 Kotlin 还有语法糖可以完全省略对 Pair 的定义。棒!

fun access(contract: Contract,            employee: Employee) = when (contract to employee) {     PROBATION to SENIOR_ENGINEER,     PROBATION to REGULAR_ENGINEER -> NotGranted(AssertionError("Access not allowed on probation contract."))     PERMANENT to SENIOR_ENGINEER,     PERMANENT to REGULAR_ENGINEER,     PERMANENT to JUNIOR_ENGINEER,     CONTRACTOR to SENIOR_ENGINEER -> Granted(DateTime(1))     CONTRACTOR to REGULAR_ENGINEER,     PROBATION to JUNIOR_ENGINEER,     CONTRACTOR to JUNIOR_ENGINEER -> Blocked("Access for junior contractors is blocked.")     else -> throw AssertionError("Unsupported case of $employee and $contract") } 
这个结构让我的生活变得轻松,也让 Kotlin 代码读写变得容易,我希望你也觉得这很有用。但它是不是不能用于三元组呢?答案是肯定的。

Triple(enum1, enum2, enum3) == enum1 to enum2 to enum3 
以上就是第 1 部分的全部内容,如果你仍然很有兴趣,请继续阅读第 2 部分。干杯!

第二部分链接地址:www.hongshulin001.com

<br/>
时间: 2024-08-03 16:17:02

6个能让你的Kotlin代码库更有意思的“魔法糖”的相关文章

从单个代码库和多个数据库创建多个Drupal站点

系统管理员或开发人员有许多理由选择采用一种多站点配置在同一个服务器上托管多个站点.例如以下这 种配置: 使您能够构建一个高效的开发环境 允许您在公开站点之前暂存并测试它 允许在同一个站点上运行不同软件 支持跨多个站点共享单个代码库 利用共享的托管主机 允许在同一个服务器上运行类似的安全(基于安全套接字层的 HTTP [HTTPS])和标准 (HTTP) 站点 简化服务器管理 但是,不是所有这些理由都可以通过 Drupal 的多站点特性得到最好的处理.本文将重点介绍一种战略: 如何使 Drupal

树形菜单求助 ,不知道从何下手。求大咖们帮帮讲讲思路 要是有代码就更好了。

问题描述 树形菜单求助 ,不知道从何下手.求大咖们帮帮讲讲思路 要是有代码就更好了. 左边为国家的省份,子菜单为省份的市级单位,右边卫市级单位的区.数据是从数据库区的.括号内的数字为选中的个数.从数据库去出来的数据类似于 id:01, name:黑龙江省,市区的LIST. 在前台页面怎遍历这个树形菜单还可以联动 求助大咖们 图片地址:http://a.hiphotos.bdimg.com/album/s%3D1000%3Bq%3D90/sign=3cccf467caef7609380b9d9f1

Android开发神器(里面有各种UI特效和android代码库实例)

http://www.23code.com/tui-jian-an-zhuo-kai-fa-shen-qi-li-mian-you-ge-zhong-ui-te-xiao-he-shi-li/ 网上有很多开源的安卓代码库很好用,对于刚学习安卓的童鞋亦或者老鸟都是很好的学习对象. 我平时有关注开源代码的习惯,这么多年也搜集了不少精彩的源码. 到后来发现自己手机里装的都是几百个demo app,删又不舍得删,不删又占资源, 于是就准备搞了个23code应用,里面汇集了我精心整理的应用,此后每周应该都

《Web前端开发最佳实践》——1.3 规范的Web前端代码:更易维护、更高性能和更安全

1.3 规范的Web前端代码:更易维护.更高性能和更安全 规范的代码,这是所有软件开发中对代码的基本要求,前端开发也是一样的,要求编写规范的HTML.CSS和JavaScript代码. 什么样的前端代码才能称得上规范的代码?探讨这个问题之前,首先需要强调的是规范不是标准,不是放之四海而皆准的,不同的项目中的代码规范是有可能有差异的,比如命名,有些项目规定HTML标签的id必须要以控件的缩写名作为前缀,如按钮的id名以"btn"作为前缀,有些只是规定命名有意义就可以.再比如有些项目规定J

android git repo-repo 与 git的基本操作以及建立代码库

问题描述 repo 与 git的基本操作以及建立代码库 我从供应商那里拿到了一套repo的代码库,现在要把它放到自己的服务器上,让其他人可以进行开发,现在的问题是不知道怎样在服务器上建立这个库?怎么更改url?希望能提供一些思路或者资料,谢谢!

webservice接口开发-Webservice开发单点登录接口怎么实现,有具体代码实例更好

问题描述 Webservice开发单点登录接口怎么实现,有具体代码实例更好 现在要实现一个单点登录功能的接口,请问该如何实现这个功能?使用webservice开发的接口来实现它. 解决方案 既然是两个工程,那就一个做客户端,一个做服务端了,服务端可以把服务发布出来,客户端进行调用,就可以了啊,分布式开发.. 解决方案二: 你去东软帝国这个网站看看,里面有一个,不值得是不是基于webservice的

开源项目成熟度分析工具-利用github api获取代码库的信息

1.github api        github api是http形式的api,功能还是比较丰富的,博主因为项目的原因主要用到的是提取project信息这项功能,返回的数据是JSON格式. api页:https://developer.github.com/v3/ Options: (H) means HTTP/HTTPS only, (F) means FTP only --anyauth Pick "any" authentication method (H) -a, --ap

[sikuli]-导入代码库和Jar

  # an example - choose your own naming # on Windows myScriptPath = "c:\\someDirectory\\myLibrary" # on Mac/Linux myScriptPath = "/someDirectory/myLibrary" # all systems if not myScriptPath in sys.path: sys.path.append(myScriptPath) #

svn checkout 报错代码库文件不存在

问题描述 svn checkout 报错代码库文件不存在 我在centos系统yum安装了svn服务器,版本是1.6的 本地是mac系统,自带svn,版本是1.7 checkout时候报错,svn: E170000: URL 'svn://123.57.69.204/opt/svn/repositories' doesn't exist 求大神指教. 解决方案 BAT自动从SVN上checkout最新的代码 解决方案二: BAT自动从SVN上checkout最新的代码