2025上半年,最新 AI实践都在这!20+ 应用案例,任听一场议题就值回票价 了解详情
写点什么

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:4480202
用户头像

发布了 376 篇内容, 共 199.6 次阅读, 收获喜欢 949 次。

关注

评论

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

linux之tar使用技巧

入门小站

Linux

天翼云4.0创新成果发布!科技创新赋能安全普惠

天翼云开发者社区

在KVM中运行苹果操作系统

lyan

读万卷书为何无用?

大数据梦想家

程序员 个人成长 读书感悟

微幕小程序,给市场一个新的想象空间

Geek_99967b

小程序容器 小程序开发

在线HTML转YAML工具

入门小站

工具

亚马逊AWS特约评委揭秘FinClip黑客松获胜秘诀

Geek_99967b

SaaS 小程序容器 小程序开发 小程序组件

NFT DeFi基础设施AFKDAO 完成300万美元融资 Hoo Labs等参投

区块链前沿News

Hoo AFKDAO

大厂裁员登上热搜,谈谈我的3点认知

大数据梦想家

大数据 个人成长 程序人生 裁员

Kafka 核心知识点灵魂 16 问

大数据梦想家

大数据 kafka 八股文

在星云科技,我们使用 Zadig 实现多环境并行发布,上千次周部署

Zadig

DevOps 云原生 CI/CD 软件交付

分布式协议-Paxos

白裤

PAXOS paxos协议 Basic paxos 5月月更

在黑客马拉松中有什么样的收获?

Geek_99967b

SaaS 黑客松 小程序组件

过去一年对我帮助最大的三本书

大数据梦想家

个人成长 程序人生 读书总结

混合办公首选轻薄本,华为MateBook的“三重变身”改变了什么?

脑极体

一文学完Linux常用命令

编程攻略

Linux

在线火星文转简体中文工具

入门小站

工具

Java Core「3」volatile 关键字

Samson

学习笔记 5月月更 Java core

【变量规则,HTML 转义,非法导入名称】flask框架总结(三)

恒山其若陋兮

5月月更

天翼云成为“云优化能力提升计划”首批成员单位!

天翼云开发者社区

天翼云通过DCMM 4级认证

天翼云开发者社区

Flutter的特别之处

Geek_99967b

SaaS 小程序开发 小程序组件

大厂员工过劳死,打工人该如何自救?

大数据梦想家

大数据 程序员 程序人生

第一梯队,增速第一

天翼云开发者社区

模板化的封装,降低业务代码开发

Java 架构 业务

大数据开发面试之26个Spark高频考点

大数据梦想家

大数据 spark 八股文

十、云原生网络架构

穿过生命散发芬芳

网络架构 5月月更

创业、女性、数字化:后COVID-19世界的复苏“铁三角”

脑极体

一起来体验小程序应用的独特魅力

Geek_99967b

SaaS应用 finclip 小程序容器

如何以极客的方式探索小程序容器技术

Geek_99967b

SaaS应用 小程序转app 跨端运行

抖音日均拦截93%诈骗行为:如何打击互联网诈骗行为

石头IT视角

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