写点什么

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

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

关注

评论

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

迁移回归问题新突破,火山语音团队论文被人工智能顶级期刊IEEE TPAMI接收

科技热闻

架构训练营模块1作业

gigifrog

架构训练营

深圳前端培训学习编程的就业方向有哪些?

小谷哥

如何通过C#合并Word文档

在下毛毛雨

C# .net word文档 文件合并

华为云发布CodeArts Req需求管理工具 让需求管理化繁为简

极客天地

架构实战营模块一作业

😊😊😊

作业一

我服了!腾讯专家手敲《Redis源码日志笔记》,字字如金

钟奕礼

Java 程序员 java面试 java编程

前端leetcde算法面试套路之堆

js2030code

JavaScript LeetCode

java架构师培训学习需要多久?

小谷哥

模块一

GeekMLS

构架

用javascript分类刷leetcode3.动态规划(图文视频讲解)

js2030code

JavaScript LeetCode

与Web3支付赛道主要项目相比,Zebec生态潜力相当大

股市老人

PostgreSQL 技术内幕(三)聚集算子

酷克数据HashData

postgresql

Redis数据持久化机制(备份恢复)、缓存淘汰策略、主从同步原理、常见规范与优化详解

C++后台开发

数据库 redis 中间件 后端开发 C++开发

各开发语言DNS缓存配置建议

京东科技开发者

#DNS

2022阿里云研发效能峰会,来啦!

阿里云云效

DevOps 研发效能 开发 数字化转型

惊喜!Alibaba架构师手写《Java一无所知到精通》文档

钟奕礼

Java 程序员 java面试 java编程

面试阿里P6,却被MySQL难倒,二战阿里,挤进天猫团队(Java岗)

钟奕礼

Java 程序员 java面试 java编程

前端leetcde算法面试套路之树

js2030code

JavaScript LeetCode

前端精准测试实践

京东科技开发者

前端 测试 精准测试 webhook 精准分析

阿里技术官珍藏JVM全优笔记,细节满分,吃透写精通没问题

钟奕礼

Java 程序员 java面试 java编程

秒云成功入选《2022爱分析 · 信创厂商全景报告》

MIAOYUN

信创 信创云 金融信创 信创产业

事务相关知识集锦

京东科技开发者

MySQL 数据库 spring mybatis 事务

文盘Rust -- r2d2 实现redis连接池

京东科技开发者

redis 后端 封装 连接池 rust语言

架构训练营模块一作业

Dinfan

架构实战营

大数据培训学习需要注重哪些知识呢

小谷哥

Java语言标识符的命名规范(超详细讲解)

千锋IT教育

web技术分享| 图片上传与图片裁剪结合 vue3

anyRTC开发者

Vue 前端 Web 图片上传 图片裁剪

【PCB技能】脑瓜疼的PCB反复评审难题,解决方法在这!

华秋PCB

工具 PCB PCB设计

专业开发人员需要低代码开发平台吗

力软低代码开发平台

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