前言
在Kotlin介绍:第一部分,我们介绍了基本语法,现在我们可以去看看实际上如何使用Kotlin。在这篇文章中,我们将介绍collections和lambdas表达式,一些方便的扩展函数(apply,let,run和with),null safety(空安全),那下面咱就开始吧。
1、Collections and Lambdas
那么Kotlin collections是什么呢?如果您熟悉Java8,您将会对这些collection方法(java流)和语法十分了解。然而,Kotlin提供了大部分你可能想得到的扩展,让我们一起来看看吧。
listOf(1,2,3)
mutableListOf("a", "b", "c")
setOf(1,2,3)
mutableSetOf("a", "b", "c")
mapOf(1 to "a", 2 to "b", 3 to "c")
mutableMapOf("a" to 1, "b" to 2, "c" to 3)
这些是基础,Kotlin为您提供了方法来创建collections,我在这儿列出了不可变和可变版本的List,Set和Map。Kotlin系列的编程除了默认的不变性外,还来自于Kotlin stdlib的扩展功能。如果您熟悉函数式编程,那么您将熟悉大部分功能。它们是一组辅助函数和更高级的辅助函数,可以为您的集合提供常用操作。有了这些扩展函数(map,flatMap,forEach,fold,reduce,filter,zip,...)很多操作完成起来就很方便。
在我们使用它们之前,我们需要先说一下lambdas表达式。Kotlin标准库的collection扩展功能的优点来自于易使用lambdas表达式,只需使用足够的类型推理来保证编程安全。在Kotlin中有几种方法来定义lambdas函数。
val aList = listOf(1,2,4)
aList.map { elem ->
elem + 1
} // 2,3,5
aList.filter { it != 1} // 2,4
fun folder(a: Int, b: Int) = a + b
aList.reduce(::folder) // 7
// 或者: aList.reduce { a, b -> folder(a, b) }
在第一个例子中,我们定义了Kotlin lambdas的最常见用法。我们可以用角括号(->)来缩写匿名函数,我们可以改变lambdas参数的名称(在这里我们省略了类型定义;我们可以从aList列表中看到它是一个Int),然后我们定义lambda体,不需要使用return语句,最后一行将被返回。
下一个例子进一步说明,甚至可以省略参数定义。在Kotlin中,默认情况下,一个参数lambdas会接收到一个名为it的参数名。没有必要去命名它。请注意,如果过多的使用it,尤其在嵌套函数中,会导致代码非常混乱!
最后一个向我们展示了几个新的概念,首先是一个本地函数,我们引用了::一个双汇语法,本地函数的样式和作用类似于类或全局作用域函数,但还有一个额外功能,它还能访问与函数本身在同一范围定义的变量。引用本地函数的第二种方法我们将它称为内部lambda,就像注释中显示的那样。
正如你所看到的,Kotlin中的lambdas是以直截了当的方式定义的。它们在您的代码中也很明显,并使得高阶函数的使用变得简单。关于Kotlin和lambdas的最好部分是类型推断,当类型不匹配时,它就在你的代码下面出现一条红色的线。通过编译器的这种帮助,您可以将精力放在业务逻辑上,而不是试图找出循环应该遍历多少遍。
有关Kotlin的collection扩展功能的更多信息可以在官方网站API doc中找到
2、Null safety(空安全)
当涉及到可空性,Kotlin编译器会非常严格的剖析您的代码。如果定义一个可能为null的变量,则需要将其定义为可空。那这该怎么写呢?
var nil: String? = null
val notNil: String = "Hi"
var nil = null
这三个变量声明有两个可空值,一个不为null。无效性的共同点是问号;可空变量和函数参数用问号定义。这个问号在Kotlin的null safe起着重要的作用。如果Kotlin编译器在变量声明或函数参数/返回类型中看到这个问号,它将强制您对空检查。如果您主要编写的是Kotlin代码,那您将会从NullPointException解放出来。然而Kotlin与Java高度互操作,当你传入的数据可能为空时。Kotlin会让你处理这个十亿美元的错误。
data class Lad(val name: String, val age: Int)
fun doSomething(laddy: Lad?){
print(laddy.name)
}
如果您尝试这么做,Kotlin会编译器将会给出提示。在android studio中,您将得到文本下方的红色波浪线,它会给出Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Lad?。为了解决这个问题,你别无选择。
fun doSomething(laddy: Lad?){
if(laddy != null){
print(laddy.name)
}
}
fun doSomething(laddy: Lad?){
print(laddy?.name)
}
fun doSomething(laddy: Lad?){
laddy?.name?.let {
print(it)
}
/** 或者
* laddy?.name?.let { name ->
* print(name)
* }
**/
}
第一个例子是之前的写法,正确的非空检查。编译器知道,在完成null检查之后,就可以使用我们的变量,红色波浪线就会从print语句中消失。在第二个例子,我们熟悉的问号再次出现了,但是这一次担任是不同的角色。在这方面,问号会提示If laddy is not null, then take the name property from it。如果laddy为空,那么null将会打印到控制台。
第三个介绍了一个扩展功能,我们可以用它来调用let。如果我们想从我们的函数返回一些东西,我们可以使用elvis作为默认值,以防我们碰到一个null。使用elvis有点像这样:
fun doSomething(laddy: Lad?) = laddy?.name?: "James"
当laddy和name都不为空时,才会返回“James”。
3、扩展功能(Apply, Let, Run, and With)
Kotlin推出了一些扩展功能,可以帮助我们处理。我们看到的第一个let是一个扩展,它将一个lambda作为参数。在上面的例子中,it意味着我们的对象属性name,但仅当laddy和name不为空时有效。let只对存在的东西有用,作为扩展功能,它不能扩展不存在的东西。
Apply是另一个时髦的扩展功能,我们可以在很多情况下使用它,一个常见的用法的就是创建一个需要许多调用的对象,但是没有很好的方法来做到这一点。为了简单起见,我们能想到JavaBean及其getter和seeter。
public class JavaBeanClass {
private String thing;
private String thang;
public String getThing() {
return thing;
}
public void setThing(String thing) {
this.thing = thing;
}
public String getThang() {
return thang;
}
public void setThang(String thang) {
this.thang = thang;
}
}
这看起来有点繁琐,没关系,让我们使用Kotlin看看。
val mrBean = JavaBeanClass().apply {
setThing("Wild")
setThang("erbeest")
}
这就很舒服了,其实在Kotlin中,还可以有其它的写法,与上述相同的代码还可以这么写:
val mrBean = JavaBeanClass().apply {
thing = "Wild"
thang = "erbeest"
}
这样就更简洁了。
接下来我们介绍with,这个家伙类似apply,实际上它不是一个扩展函数,它只是一个函数,接受了两个参数。我们来看一个例子,我们将使用与mrBean之前定义的相同的方法。
with(mrBean) {
thing = "the"
thang = "ain't no"
}
和apply非常相似,你不觉得吗?其实根本不一样,那是因为我们没有做任何事,with返回with块中最后一个表达式的值。这是一个重要的区别,所以让我们看一个更好的例子。
val yo = with(mrBean) {
thang + "thing"
}
print(yo) // ain't nothing
我们继续看下一个操作符run,这是一个很简单的小东西。它是一个扩展函数,它接受一个参数,一个lambda。它只是调用该lambda并返回该lambda的响应。“那么这个家伙有什么用呢?” “你可能会问”。使用它来运行某些东西,当且仅当它被调用的对象不是null(使用它类似于let上面的几行,但在run这种情况下this作为范围的对象)或使用它来调用我们的函数调用并保护我们的lambdas。我们必须记住,做run同样的事情,但with通常更容易使用。
4、类型: Checking, casting, and safety(检查,转换,安全)
在Java世界中,您可能会遇到这样的if检查if (clazz instanceOf SomeClass)程序员希望看到他们是否正确实现其接口或扩展的基类。
在Kotlin中类型推断是非常好的,编译器在编写代码时给出了很多有用的提示。当您需要检查对象是否是某种类型时,您可以使用is关键字。
fun tryAndFailToCompileToGetTheAnswer(plzPassInThirteen: Any): Int {
return plzPassInThirteen + 29
}
fun getTheAnswer(plzPassInThirteen: Any): Int {
if (plzPassInThirteen is Int) {
return plzPassInThirteen + 29
}
return 666
}
println(getTheAnswer(13)) // 42
在上面的代码块中,第一个函数将会失败,并且根本没有实际编译,它会报错,找不到类型匹配。第二个功能修复了:它做了一个简单的is检查,在这一点上,Kotlin智能的将该值转换为Int,因此它可以在if语句中使用。通常当when和is配合使用时,您可以这么写:
fun getTheAnswer(plzPassInThirteen: Any): Int = when(plzPassInThirteen) {
is Int -> plzPassInThirteen + 29
else -> 666
}
println(getTheAnswer(13)) // 42
这个例子与以前看到的if语句是一样的,但这不是更美观吗?
现在我们接触了is和when在一起,现在我们可以绕个弯子谈一谈sealed classes,Kotlin有一个sealed classes的概念,我们可以把它当成一些子类的包装。
sealed class Seal
class SeaLion: Seal()
class Walrus: Seal()
class KissFromARose(val film: String): Seal()
如果我们有这样的结构,一个密封的超类和三个继承的子类,我们可以很好的处理多态和when以及is的组合。
fun getTheAnswer(songOrAnimal: Seal): Unit = when(songOrAnimal) {
is SeaLion -> println("Animal")
is Walrus -> println("Song by Beatles")
}
这是编译不过去的,编译器会告诉我们when中的声明少了哪一个子类,如果我们将KissFromARose添加上就不会出现问题。
fun getTheAnswer(songOrAnimal: Seal): Unit = when(songOrAnimal) {
is SeaLion -> println("Animal")
is Walrus -> println("Song by Beatles")
is KissFromARose -> ("Heidi Klum")
}
println(getTheAnswer(Walrus())) // Song by Beatles
上面的编译就没什么问题,
有时候我们需要类型的转换,在Kotlin中,使用as关键字。当它被赋值时,我们可以假设它被转换为该类型,
val possiblyString: Any = "definitely"
possiblyString.capitalize()
上面的例子是无法编译的,capitalize()会有错误下划线,编译器告诉我们有一个Unresolved reference和resolver type mismatch。这个提示是对的,我们知道Any没有capitalize()方法,修改这个是容易的,我们只要将变量变成String就没问题了。
val possiblyString: Any = "definitely"
possiblyString as String
possiblyString.capitalize()
现在我们已经了解了Kotlin的集合,空安全,类型安全,到这里第二部分的内容也算是告一段落了。