写点什么

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

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

关注

评论

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

【Python金融-001】如何快速计算股票的收益?1行代码,高效做T

程序员晚枫

Python 金融 股票

Spring Boot 开发离不开这些注解,快来学习啦!

Java你猿哥

spring Spring Boot ssm spring aop java框架

Nautilus Chain:我们将支持EIP6969

西柚子

以后我准备告别String.format()了,因为它不够香!

Java你猿哥

Java 编程 string 格式化 format

2023年互联网Java工程师高级面试八股文汇总(1260道题目附解析)

架构师之道

Java 面试

深度学习入门系列(一):一文看懂 MNIST

U2647

神经网络 机器学习 深度学习 keras

阿里内部总结的微服务笔记,从入门到精通小白也能学的会

Java你猿哥

分布式 微服务 微服务架构 Spring Cloud ssm

简化Mybatis分页操作,让我们来聊聊 PageHelper 及实现原理

Java你猿哥

Java mybatis ssm PageHelper

推进开源法律知识普及|2023开放原子全球开源峰会开源法律与合规分论坛即将启幕

开放原子开源基金会

开源 法律与合规

专业笔记工具:Keep It 激活版

真大的脸盆

Mac Mac 软件 笔记工具

大神在民间!碰巧在Github发现一个Java面试高分Guide,已跳槽涨20K

做梦都在改BUG

Java java面试 Java八股文 Java面试题 Java面试八股文

Github上获赞59.8K的面试神技—1658页《Java面试突击核心讲》

做梦都在改BUG

Java java面试 Java八股文 Java面试题 Java面试八股文

JavaScript深度剖析之变量、函数提升:从表面到本质

Immerse

北美 2023 被裁员的感悟

HoneyMoose

SpringBoot异步线程,父子线程数据传递的5种姿势

Java你猿哥

Java Spring Boot 线程 ssm 异步

来自大佬的洗礼!全网独家的SpringCloud Alibaba学习笔记,太全了

做梦都在改BUG

Java 架构 微服务 Spring Cloud

真香!阿里最新产出分布式进阶实战手册,涵盖分布式架构所有操作

Java你猿哥

Java 架构 Spring Cloud Spring Boot ssm

2023金三银四Java开发岗热门面试题总结

小小怪下士

Java 程序员 面试 金三银四

文心一言 VS 讯飞星火 VS chatgpt (29)-- 算法导论5.2 1题

福大大架构师每日一题

ChatGPT 文心一言

C语言编程-共用体

芯动大师

6 月 优质更文活动

Nautilus Chain:我们将支持EIP6969

大瞿科技

原来user.dir竟然会影响classpath的值

Java你猿哥

Java tomcat classpath ssm

全网首推!头条大佬手码的164页Elasticsearch核心学习手册,我服了

做梦都在改BUG

Java 搜索引擎 elasticsearch ES

阿里架构师JVM基础到调优笔记,吃透阿里P6小case

做梦都在改BUG

Java 性能优化 性能调优

以技术实践赋能开源安全|2023开放原子全球开源峰会开源安全技术与实践分论坛即将启幕

开放原子开源基金会

开源 开源软件供应链 开源安全技术

Nautilus Chain:我们将支持EIP6969

BlockChain先知

Generative AI 新世界 | 大型语言模型(LLMs)概述

亚马逊云科技 (Amazon Web Services)

机器学习 tensorflow 开源 PyTorch Amazon SageMaker

Nautilus Chain:我们将支持EIP6969

股市老人

不愧是牛客网爆火的1658 页《Java 面试突击核心讲》,面面俱到太全了

采菊东篱下

Java 程序员 面试

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