前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >干货 | Kotlin超棒的语言特性

干货 | Kotlin超棒的语言特性

作者头像
携程技术
发布2018-07-05 16:37:46
1.4K0
发布2018-07-05 16:37:46
举报
文章被收录于专栏:携程技术携程技术

作者简介

何伦,携程度假BU移动端资深研发经理,负责iOS、Android平台上跟团游产品预订流程的前端页面的研发工作。对新技术有着浓厚的兴趣。

自从2017年Google宣布Kotlin成为Android官方开发语言之后,Kotlin受到广大Android开发者的追捧。其强大的安全性,简洁性和与Java的互操作性,为开发者带来了耳目一新的开发体验,也极大提升了Android原生代码的开发效率。

不过大部分开发者对Kotlin的使用,仍然局限于把Java代码逻辑按照Kotlin语法进行转换的层面,其实Kotlin和Java虽然具有很强的互操作性,但本质上还是两种完全不同设计思想的语言。

本文在假定读者有一定Kotlin开发基础的前提下,详细讲解一些具有Kotlin特色的实用的语言特性,帮助开发者能够写出更加“具有Kotlin风格”的代码。这些语言特性包括空安全、Elvis表达式、简洁字符串等等。

01更加安全的指针操作

在Kotlin中,一切皆是对象。不存在int, double等关键字,只存在Int, Double等类。

所有的对象都通过一个指针所持有,而指针只有两种类型:var 表示指针可变,val表示指针不可变。为了获得更好的空安全,Kotlin中所有的对象都明确指明可空或者非空属性,即这个对象是否可能为null。

对于可空类型的对象,直接调用其方法,在编译阶段就会报错。这样就杜绝了空指针异常NullPointerException的可能性。

如上图,编译器会报错

Error:Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String

02?表达式和Elvis表达式

Kotlin特有的?表达式和Elvis表达式可以在确保安全的情况下,写出更加简洁的代码。比如我们在Android页面开发中常见的删除子控件操作,用Java来写是这样的:

为了获得更加安全的代码,我们不得不加上很多if else 判断语句,来确保不会产生空指针异常。但Kotlin的?操作符可以非常简洁地实现上述逻辑:

那么这个?表达式的内在逻辑是什么呢?以上述代码为例,若view == null,则后续调用均不会走到,整个表达式直接返回null,也不会抛出异常。也就是说,?表达式中,只要某个操作对象为null,则整个表达式直接返回null。

除了?表达式,Kotlin还有个大杀器叫Elvis表达式,即?: 表达式,这两个表达式加在一起可以以超简洁的形式表述一个复杂逻辑。

以上面表达式为例,我们以红线把它划分成两个部分。若前面部分为null,则整个表达式返回值等于c的值,否则等于前面部分的值。把它翻译成Java代码,是这样的

同样等同于这样

即Elvis表达式的含义在于为整个 ?表达式托底,即若整个表达式已经为null的情况下,Elvis表达式能够让这个表达式有个自定义的默认值。这样进一步保证了空安全,同时代码也不失简洁性。

03 更简洁的字符串

同Java一样,Kotlin也可以用字面量对字符串对象进行初始化,但Kotlin有个特别的地方是使用了三引号”””来方便长篇字符串的书写。而且这种方法还不需要使用转义符。做到了字符串的所见即所得。

同时,Kotlin还引入了字符串模板,可以在字符串中直接访问变量和使用表达式:

04 强大的when语句

Kotlin中没有switch操作符,而是使用when语句来替代。同样的,when 将它的参数和所有的分支条件顺序比较,直到某个分支满足条件。如果其他分支都不满足条件将会进入 else 分支。

但功能上when语句要强大得多。首先第一点是,我们可以用任意表达式(而不只是常量)作为分支条件,这点switch就做不到。如下述代码,前面三个分支条件分别是:1、变量在[1, 10]区间内, 2、变量x不在[10, 20]区间内,3、变量x是一个字符串。这个表达式用switch语句基本无法实现,只能用if else 链来实现。

说起if else 链,我们可以直接用when语句把它给替换掉:

05对象比较

Java的 == 操作符是比较引用值,但Kotlin 的 == 操作符是比较内容, === 才是比较引用值。基于这点,我们可以写出逻辑更简洁合理的代码:

上述代码可以直接用when语句实现

06Nullable Receiver

NullableReceiver我将其翻译成“可空接收者”,要理解接收者这个概念,我们先了解一下Kotlin中一个重要特性:扩展。Kotlin能够扩展一个类的新功能,这个扩展是无痕的,即我们无需继承该类或使用像装饰者的设计模式,同时这个扩展对使用者来说也是透明的,即使用者在使用该类扩展功能时,就像使用这个类自身的功能一样的。

声明一个扩展函数,我们需要用一个接收者类型,也就是被扩展的类型来作为他的前缀,以下述代码为例:

上述代码为 MutableList<Int> 添加一个swap 函数, 我们可以对任意 MutableList<Int> 调用该函数了:

其中MutableList<Int>就是这个扩展函数的接收者。值得注意的是,Kotlin允许这个接收者为null,这样我们可以写出一些在Java里面看似不可思议的代码。比如我们要把一个对象转换成字符串,在Kotlin中可以直接这么写:

上述代码先定义了一个空指针对象,然后调用toString方法,会不会Crash?其实不会发生Crash,答案就在“可空接收者”,也就是Nullable Receiver,我们可以看下这个扩展函数的定义:

扩展函数是可以拿到接收者对象的指针的,即this指针。从这个方法的定义我们可以看到,这个方法是对Any类进行扩展,而接收者类型后面加了个?号,所以准确来说,是对Any?类进行扩展。我们看到,扩展函数一开始就对接收者进行判空,若为null,则直接返回 “null” 字符串。所以无论对于什么对象,调用toString方法不会发生Crash.

07 关键字object

前面说过,Kotlin中一切皆为对象,object在Kotlin中是一个关键字,笼统来说是代表“对象”,在不同场景中有不同用法。

第一个是对象表达式,可以直接创建一个继承自某个(或某些)类型的匿名类的对象,而无须先创建这个对象的类。这一点跟Java是类似的:

第二,对象字面量。这个特性将数字字面量,字符串字面量扩展到一般性对象中了。对应的场景是如果我们只需要“一个对象而已”,并不需要特殊超类型。典型的场景是在某些地方,比如函数内部,我们需要零碎地使用一些一次性的对象时,非常有用。

第三,对象声明。这个特性类似于Java中的单例模式,但我们不需要写单例模式的样板代码即可以实现。

请注意上述代码是声明了一个对象,而不是类,而我们想要使用这个对象,直接引用其名称即可:

08有趣的冒号

从语法上来看,Kotlin大量使用了冒号(:)这一符号,我们可以总结一下,这个冒号在Kotlin中究竟代表什么。

考虑下面四种场景:

  • 在变量定义中,代表变量的类型
  • 在类定义中,代表基类的类型
  • 在函数定义中,代表函数返回值的类型
  • 在匿名对象中,代表对象的类型

笼统来说,Kotlin的设计者应该就是想用冒号来笼统表示类型这一概念。

09 可观察属性

可观察属性,本质就是观察者模式,在Java中也可以实现这个设计模式,但Kotlin实现观察者模式不需要样板代码。在谈Kotlin的可观察属性前,先看下Kotlin里面的委托。同样的,委托也是一种设计模式,它的结构如下图所示:

Kotlin在语言级别支持它,不需要任何样板代码。Kotlin可以使用by关键字把子类的所有公有成员都委托给指定对象来实现基类的接口:

上述代码中,Base是一个接口,BaseImpl是它的一个实现类,通过by b语句就可以把Derived类中的所有公有成员全部委托给b对象来实现。我们在创建Derived类时,在构造器中直接传入一个BaseImpl的实例,那么调用Derived的方法等同于调用BaseImpl的实例的方法,访问Derived的属性也等同于访问BaseImpl的实例的属性。

回到可观察属性这个概念,Kotlin通过 Delegates.observable()实现可观察属性:

上述代码中,name是一个属性,改变它的值都会自动回调{ kProperty, oldName, newName -> }这个lambda表达式。简单来说,我们可以监听name这个属性的变化。

可观察属性有什么用处呢?ListView中有一个经典的Crash:在数据长度与Adapter中的Cell的长度不一致时,会报IllegalStateException异常。这个异常的根本原因是修改了数据之后,没有调用notifyDataSetChanged,导致ListView没有及时刷新。如果我们把数据做成可观察属性,在观察回调方法中直接刷新ListView,可以杜绝这个问题。

10 函数类型

Kotlin中一切皆是对象,函数也不例外。在Kotlin中,函数本身也是对象,可以拥有类型并实例化。Kotlin 使用类似 (Int) -> String 的一系列函数类型来处理函数的声明,比如我们常见的点击回调函数:

箭头表示法是右结合的,(Int) -> (Int) -> Unit 等价于(Int) ->((Int) -> Unit),但不等于 ((Int) -> (Int)) -> Unit。可以通过使用类型别名给函数类型起一个别称:

函数对象最大的作用是可以轻易地实现回调,而不需要像Java那样通过代理类才可以做到。我们以ScrollView滑动的回调为例,看一下使用Java编写一份Callback需要花费多大成本。对于主调方,即MyScrollView类而言,首先我们需要一个Callback的接口(OnScrollCallback),这个接口里面有一个待实现的onScroll方法。然后需要一个属性来保存回调对象。最后在View滑动的时候,我们调用这个回调对象的onScroll以实现回调。

对于被调方,即MyScrollView的使用者而言,我们需要一个实现OnScrollCallback接口的对象。然后设置成MyScrollView的回调对象,才能够实现滑动回调。

我们只是实现一个简单的回调而已,为什么还要这么复杂呢?本质上是因为Java里面函数并不是对象,所以要实现回调,必须要实现一个代理类来包装这个函数,否则我们无法传递这个函数给主调方。

Kotlin实现回调就是完全不一样的方式了,因为Kotlin的函数也是对象,所以我们直接把函数对象传递给主调方即可。

看一下上面的代码,就是这么简单!

再介绍下如何将函数类型实例化,有几种常见方式:

一是使用函数字面值的代码块,比如lambda 表达式 { a, b -> a + b },或者匿名函数fun(s: String): Int { return s.toIntOrNull()?: 0 }

二是使用已有声明的可调用引用,包括顶层、局部、成员、扩展函数 ::isOdd String::toInt,或者顶层、成员、扩展属性 List<Int>::size,或者是构造函数 ::Regex

三是使用实现函数类型接口的自定义类的实例

四是编译器推断

11 工具

对于初学Kotlin的开发者而言,编译器提供了贴心的小工具,甚至可以直接把Java代码转换成Kotlin代码。直接把Java代码拷贝到.kt文件中,编译器会弹出如下提示:

Kotlin与Java是100%兼容的,因为它最终会编译成Java字节码,我们可以通过 Android Studio工具看到编译的bytecode:

我们还可以把编译出来的Java字节码反编译成Java代码,这样可以窥探Kotlin的实现机理:

事实上,Kotlin优秀的语言特性绝对不止本文提到的这几种,还有很多,比如函数默认参数、扩展属性、懒初始化、局部函数、数据类,等等。欢迎大家在学习的过程中一起交流。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-06-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 携程技术中心 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 02?表达式和Elvis表达式
  • 05对象比较
  • 06Nullable Receiver
  • 07 关键字object
  • 08有趣的冒号
  • 10 函数类型
  • 11 工具
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档