HarmonyOS开发者限时福利来啦!最高10w+现金激励等你拿~ 了解详情
写点什么

Kotlin 如何成为我们 Android 开发的主要语言

  • 2016-12-29
  • 本文字数:4966 字

    阅读完需:约 16 分钟

引言

Kotlin 是一个基于 JVM 的新的编程语言,由 JetBrains 开发。JetBrains,作为目前广受欢迎的 Java IDE IntelliJ 的提供商,在 Apache 许可下已经开源其 Kotlin 编程语言。与 Java 相比,Kotlin 的语法更简洁、更具表达性,而且提供了更多的特性,比如,高阶函数、操作符重载、字符串模板。它与 Java 高度可互操作,可以同时用在一个项目中。

Kotlin 的定位非常有特点,它并不像 Scala 那样另起炉灶,Scala 是一切尽量自己来,将类库,尤其是集合类都自己来了一遍。实在不够用了再用 java 的;而 Kotlin 是对现有 Java 的增强,尽量用 Java 的,不够用了再扩展,尤其体现在二者的容器库上,但同时始终保持对 java 的兼容。这种特点导致 Kotlin 的学习曲线极低。这是 Kotlin 官网首页重点强调的:“100% interoperable with Java™”。这意味着什么呢?或者换个问法:我什么时候可以开始在我的项目中引入 Kotlin 呢?我的回答是:现在就可以视你对 kotlin 的掌握程度,逐步引入 kotlin 的代码。

Dima Kovalenko 博客中分享了他们团队使用 Kotlin 开发商业应用程序的心得和经验,并提供了一些参考资料。希望本文能对广大 Android 开发程序员有所启发。

几个月前,我们的团队决定开始新的尝试:完全应用 Kotlin 编程语言开发一个商业应用程序,这是 JetBrains 公司设计并开源的一种新编程语言。以前,我们有过 Kotlin 的经验,但那只是小规模应用:将应用程序的一部分转换到一种新的语言,或者应用在花里胡哨的项目。然而,用新的编程语言来开发商业应用程序,我们遇到了一些困难:

  • 我们深深扎根于基于 Java 的 Android 开发。切换到 Kotlin 相当困难,对于以前没有函数式编程经验的人员而言,尤为困难。
  • 有些东西只是不工作。 Dagger 也没有立即很好的使用。

所有这些问题,都可能会导致项目无法按期交付、并带来应用程序的稳定性问题。

一个人应该有强烈的转型动力。我们的激励是:相信Kotlin 将是 Android 平台开发的颠覆者——这只是一句玩笑话

让我们打开 Kotlin 的参考书,开始开发 Voter 应用程序。Kotlin 是一种与 Java 具有 100%互操作性的 JVM 语言,如果您熟悉 Java,那么学习 Kotlin 就会很容易。然而,如果想充分利用这个编程语言,理解函数式编程概念是至关重要的。

学习函数式编程需要一段时间。所以要有耐心。

至少在学习之初,函数式编程并不容易。我强烈建议使用 Martin Ordersky 的“Scala 中的函数式编程”的课程来学习。Scala 有时势不可挡,但它提供了一个极好的函数式编程思维的概述。你可以把 Kotlin 看成 Scala 的一个更简化的版本。

为什么我们转向 Kotlin 阵营

函数式编程风格

Kotlin 与 Java 是 100%可互操作的。此外,Kotlin 是一种函数式语言。后者允许将表达性的代码编写得更优雅。

1. 纯函数

纯函数(没有副作用的函数)是最重要的函数概念,它允许我们大大降低代码复杂性并消除大多数可变状态。

在 JavaScript、Java 和 C#这些命令式编程语言中,副作用无处不在。这使得调试非常困难,因为变量可以在程序中的任何位置更改。所以当出现一个错误时,由于变量可以在错误的时间更改为错误的值,那么你到哪里去寻找错误呢?到处寻找错误吗?这可不好玩啊!

请注意我们是如何操作数据而不更改其内容的。

复制代码
fun flatTree(tree: TreeNode): List<TreeNode>
= listOf(tree, *tree.children.flatMap(::flatTree).toTypedArray())

2. 高阶函数

高阶函数将函数用作参数,返回函数或将函数作为返回值的函数。

高阶函数无处不在。你只需将函数传递给集合,就能使代码更容易阅读。比如, titles.map {it.toUpperCase()}读取简单的英语,是不是很棒?

让我们设想一种情况,假设要计算不同类型的未读消息的数量。典型的方法是:

复制代码
private fun getUnreadCountFromUsers() {
val conversations = datasource.getConversations()
var count = 0
for (conversation in conversations) {
if (conversation.recipientId != null) {
for (message in conversation.messages) {
if (message.unread) {
count += 1
}
}
}
}
}
private fun getNumberOfUnreadAttachmentsInGroupConversations() {
val conversations = datasource.getConversations()
var count = 0
for (conversation in conversations) {
if (conversation.groupId != null) {
for (message in conversation.messages) {
if (message.unread && message.type == MessageType.ATTACHMENT) {
count += 1
}
}
}
}
}

正如你所看到的,当引入新的需求时,代码变得难以理解、不可收拾。让我们看看如何使用高阶函数来解决这个问题:

复制代码
private fun getNumberOfAttachmentsInGroupConvesationsFun() {
return getCount({conv -> conv.groupId != null}, {it -> it.type == MessageType.ATTACHMENT && it.unread})
}
private fun getUnreadCountFromUsersFun() {
return getCount({conv -> conv.recipientId != null}, {message -> message.unread})
}
private fun getTotalNumberOfMessages() = getCount({true}, {true})
private fun getCount(convFilter: (Conversation) -> Boolean, messageFilter: (Message) -> Boolean) {
datasource.getConversations()
.filter(convFilter)
.flatMap { it.messages }
.filter(messageFilter)
.fold(0, { count, message -> count + 1})
}

我们还可以想象一下用例,假设想将fold函数变量参数化。比方说,计算未读消息的乘积。

使用高阶函数的另一个例子是用简单的高阶函数代替多个监听器:

BillingView : LinearLayout {

var billingChangeListener: (() -> Unit)? = null

}

… // in an activity far, far away
billingView.billingChangeListener { updateUI() }

3. 不变性

不变性使得代码更容易编写,使用和推理代码(类不变性一次建立,然后不变——一劳永逸)。应用程序组件的内部状态将更加一致。Kotlin 通过引入val关键字以及 Kotlin 集合来强制不变性,Kotlin 集合在默认情况下是不可变的。 一旦val或者一个集合被初始化,你就可以确定它的有效性。(有关val关键字的更精确的定义,请参阅文末的更新)。

data class Address(val line1: String, val city: String)

val items = listOf(Address(“242 5th St”, “Los Angeles”), Address(“Dovzhenka St. 5”, “Kiev”))

空安全(Null-safety)

这个语言特性使我们仔细考虑了模型类中字段的可空性。以前,当不确定 DTO 中的字段是否已初始化时,@Nullable 和 @NotNull 的注释就能提供帮助,但也很有限。现在,使用 Kotlin,就能让你准确知道什么字段可以为 null,什么字段被初始化(例如,Dagger 注入的字段),并且你可以对这些字段有严格的控制。结果?几乎没有NullPointerExceptions。(在内部我们管?.叫做“鹅”操作符,因为它看起来像一个鹅的脖子。)

brand?.let { badge.enabled = brand.isNewBadge }
// Can also be written as
badge.enabled = brand?.isNewBadge?:false

Anko

Anko DSL 是一个很了不起的的库,它大大简化了工作视图、线程和 Android 生命周期。据 Github 的描述,Anko 是“令人愉快的 Android 应用程序开发”,事实证明,的确如此。

复制代码
selector(items = listOf("Like", "Dislike") {
when (it) {
0 -> if (!liked) likePost()
else -> if (!disLiked) disLikePost()
}
}
doAsync {
// Long background task
uiThread {
alert(R.string.could_not_log_in) {
yesButton { dismiss() }
cancellable = false
}.show()
}
}

注意,当uiThread在 Activity 内调用时,如果isFinishingtrue,块将不会执行。我们实际上不使用这个功能,因为 RxJava 会处理应用程序中的所有线程,但它是一个很好的功能。

使用 Anko 而不是 XML。虽然 Anko 还没有做好准备取代标准的 Android UI 构建,但有的时候,它非常方便。

复制代码
verticalLayout() {
friendsPanel = friendsPanel.with(friendsData).lparams(width = matchParent)
politicalMapCardView {
setMarker(quizManager.getMarker())
}.lparams(width = matchParent) { topMargin = dip(10) }
cardView() {
verticalLayout() {
topPadding = dip(5)
textView(getString(R.string.register_question))
blueButtonView(text="Register here") {
onClick { browse("https://www.uptech.team") }
}
}
}.lparams(width = matchParent) {
topMargin = dip(10)
bottomMargin = dip(20)
}
}

如您所见,Anko DSL 允许您在 Android 内置视图中使用自定义视图。这一点与标准 XML 相比有很大的优势。

Kotlin Android 扩展:删除 ButterKnife 依赖

复制代码
@Bind(R.id.first_name)
protected EditText firstName;
@Bind(R.id.last_name)
protected EditText lastName;
@Bind(R.id.address_line1)
protected EditText addressLine1;
@Bind(R.id.address_line2)
protected EditText addressLine2;
@Bind(R.id.zip_code)
protected EditText zipCode;
@Bind(R.id.state)
protected TextView state;
@Bind(R.id.state_spinner)
protected HintSpinner stateSpinner;
@Bind(R.id.city)
protected EditText city;
@Bind(R.id.frag_shipping_address_save_btn)
protected Button saveBtn;
@Bind(R.id.agreement)
protected TextView agreement;
@Bind(R.id.email)
protected EditText email;
@Bind(R.id.password)
protected EditText password;
@Bind(R.id.create_account_container)
protected LinearLayout accountContainer;
@Bind(R.id.member_container)
protected LinearLayout memberContainer;
@Bind(R.id.logged_in_title)
protected TextView loggedInTitle;
@Bind(R.id.user_email)
protected TextView userEmail;
@Bind(R.id.sign_out)
protected TextView signOut;
@Bind(R.id.scrollview)
protected ScrollView scrollView;
@Bind(R.id.dummy)
protected EditText dummyView;

上面那段代码读起来无聊吗?我敢打赌你一直滚动而没有阅读。在 Kotlin,你并不需要任何这些东西。您可以通过其 @id XML 参数引用视图属性,这些属性将与 XML 文件中声明的名称相同。更多信息可以在官方文档中找到。

其他整洁的功能

1. 扩展功能和构建器

复制代码
items = StoreInfo().apply { storeItems = fetchItems() }.let { manager.process(it) }
container.apply {
removeAllViews()
items.forEach { addView(ShopItemView(context).withData(it)) }
}
fun ShopItemView.withData(item: StoreItem): ShopItemView {
title = item.title
image = item.image
Brand.findById(item.id)?.let { brandName = it.name }
}

applylet和扩展功能可以轻松地用于创建简洁的构建器。

2. 为初学者快速破解

在最初的前几天,你经常被一个问题难倒:你不知道如何在 Kotlin 中写一个相当简单的 Java 表达式。这里有一个简单的诀窍,就是是在 Java 中编写一段代码,然后将其粘贴到 Kotlin 文件中。感谢 JetBrains 的工程师们,它会自动转换为 Kotlin。 黑客的工作方式就像一个魔术!

3. 摆脱不必要的依赖

Kotlin 替换了许多第三方库,如 ButterKnife、Google Autovalue、Retrolambda、Lombok 和一些 RxJava 代码。

总结

作为一个软件开发团队,我们面临的主要挑战是提供优秀的产品,并有效地完成工作。虽说开始用 Kotlin 有效的开发软件,需要你有函数式编程的背景,但是,投入精力去学习是值得的,能给你巨大的回报。我相信,Kotlin 是常规 Android 开发的一个重大改进,能让我们及时提供错误更少的、更加优秀的应用程序。

更新:val实际上并不意味着“不可变的”,而是“只读”。有关详细信息,请参阅此文章

参考文献

  1. 《Kotlin 参考手册》
  2. 《所以,你要成为一名函数式程序员》
  3. 《为什么函数式编程很重要》
  4. 《Java 使用不可变对象编程的 6 大好处》
  5. 《Anko DSL 对 Android XML-First》
  6. 《将应用转换为纯 Kotlin 的经验教训》
  7. 《结果:应用程序投票选举:99.8% 无故障用户》
  8. 《Kotlin:val 不意味着不可变,而意味着只读》

感谢徐川对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2016-12-29 16:4479906
用户头像

发布了 375 篇内容, 共 186.8 次阅读, 收获喜欢 945 次。

关注

评论

发布
暂无评论
发现更多内容

前端监控系列1| 字节的前端监控SDK是怎样设计的

字节跳动终端技术

前端 监控 sdk

兆骑科创双创服务平台,招商引资、招企引税、招才引智

兆骑科创凤阁

MySQL 原理与优化:原数据锁的应用

老崔说架构

Java面向对象之继承

楠羽

Canvas 低代码拖拽建模,支持离线任务|ModelWhale 版本更新

ModelWhale

数据分析 Jupyter Notebook 课程 离线部署 低代码报告

【等保小知识】过等保一定要买堡垒机吗?堡垒机有什么作用?

行云管家

网络安全 等保 堡垒机 等级保护 过等保

京东承办“创客北京2022” 京东科技参与并推出三大服务举措

京东科技开发者

数字化转型 供应链 企业服务 中小企业

快速实现 CDN 直播

ZEGO即构

CDN 直播 音视频开发

CI/CD | 使用静态代码分析工具,有效补充持续集成

龙智—DevSecOps解决方案

cicd 持续集成 CI/CD

【分布式集群】微服务电商应用系统的集群构建

嚯嚯嚯www

Linux

【8.5-8.12】写作社区精彩技术博文回顾

InfoQ写作社区官方

优质创作周报

熬夜肝了这一份C++开发详细学习路线

C++后台开发

后台开发 C/C++ 后端开发 C++后台开发 C++开发

开源一夏|eTS UI的Text组件怎么设置文本垂直排列

坚果

开源 OpenHarmony 8月月更

如何在 Anolis 8上部署 Nydus 镜像加速方案?

OpenAnolis小助手

Linux 开源 内核 龙蜥操作系统 容器镜像加速

开源一夏 | Spring MVC深度学习

叶秋学长

开源 Spring MVC 8月月更

什么是网络即服务 (NaaS)?

wljslmz

网络技术 8月月更 NaaS 网络即服务

兆骑科创创新人才引进,高端人才引进,项目落地引进

兆骑科创凤阁

一文读懂工业设备预测性维护的概念与实现

PreMaint

企业设备管理 预测性维护 设备健康管理 状态检测 振动监测

2022不容错过的50个“低代码”发展现状、趋势与数据统计

优秀

低代码 无代码

如何开启企业数字化转型?

博文视点Broadview

融云 | 云办公时代,企业通讯录的技术选型

融云 RongCloud

企业 云办公

【LeetCode】用户分组Java题解

Albert

LeetCode 8月月更

你要的 Helm Chart 应用金丝雀发布终于来了!

阿里巴巴云原生

阿里云 开源 云原生 KubeVela

5K字详解Java 注解及其底层原理

了不起的程序猿

Java 编程语言 后端 java程序员 java编程

龙智集团赢得2022年Atlassian与AWS云销售竞赛

龙智—DevSecOps解决方案

云原生

CI与开源代码安全篇 | 龙智邀您共赴GOPS全球运维大会,探索大规模、敏捷、安全、开放式的软件研发与运营之路

龙智—DevSecOps解决方案

GOPS大会 运维‘

RT-Thread记录(四、RT-Thread 时钟节拍和软件定时器)

矜辰所致

RT-Thread 8月月更

ITSM入门指南 | IT团队如何向客户提供端到端的IT服务?

龙智—DevSecOps解决方案

ITSM ITSM软件 ITSM解决方案

一文了解如何托管SVN储存库,以及版本控制的更佳选择

龙智—DevSecOps解决方案

svn 版本控制 版本管理 版本控制系统

机器学习/评分卡常用指标及计算

Joshua

机器学习 AI 信息熵 评分卡

一块GPU训练TB级推荐模型不是梦,OneEmbedding性能一骑绝尘

OneFlow

深度学习 gpu 模型

Kotlin如何成为我们Android开发的主要语言_Android/iOS_刘志勇_InfoQ精选文章