介绍Kotlin第二部分(翻译篇)

前言

Kotlin介绍:第一部分,我们介绍了基本语法,现在我们可以去看看实际上如何使用Kotlin。在这篇文章中,我们将介绍collectionslambdas表达式,一些方便的扩展函数(applyletrunwith),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的扩展功能。如果您熟悉函数式编程,那么您将熟悉大部分功能。它们是一组辅助函数和更高级的辅助函数,可以为您的集合提供常用操作。有了这些扩展函数(mapflatMapforEachfoldreducefilterzip,...)很多操作完成起来就很方便。 在我们使用它们之前,我们需要先说一下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语句是一样的,但这不是更美观吗?

现在我们接触了iswhen在一起,现在我们可以绕个弯子谈一谈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的集合空安全类型安全,到这里第二部分的内容也算是告一段落了。

快乐工作,享受编程!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏一个会写诗的程序员的博客

第3章 Kotlin 可空类型与类型系统第3章 Kotlin 可空类型与类型系统

我们在编程语言中使用类型的目的是为了让编译器能够确定类型所关联的对象需要分配多少空间。

722
来自专栏磐创AI技术团队的专栏

2018 年最常见的 Python 面试题 & 答案

https://data-flair.training/blogs/python-tutorial/

481
来自专栏程序员的知识天地

JavaScript学习笔记

【如果大家对程序员,web前端感兴趣,想要学习的,关注一下小编吧。加群:731771211。免费赠送web前端系统的学习资料!!前端学习必备公众号ID:mtbc...

682
来自专栏aCloudDeveloper

从一个集合中查找最大最小的N个元素——Python heapq 堆数据结构

Top N问题在搜索引擎、推荐系统领域应用很广, 如果用我们较为常见的语言,如C、C++、Java等,代码量至少也得五行,但是用Python的话,只用一个函数就...

19410
来自专栏学海无涯

14.闭包

791
来自专栏静晴轩

JavaScript 之 this 详解

JavaScript作为一种脚本语言身份的存在,因此被很多人认为是简单易学的。然而情况恰恰相反,JavaScript支持函数式编程、闭包、基于原型的继承等高级功...

3275
来自专栏达摩兵的技术空间

es6入门

es6作为最新的js语言版本,有很多特性是不得不晓的。下面将语法中常用的分析出来,对应到基本对象类型的会在对象里描述。

752
来自专栏LIN_ZONE

javascript基础重点

1.在javascript中使用 == 比较,会自动转换数据类型再比较,有时候会 得到非常诡异的结果;一般情况下使用 === 比较,它不会自动转换数据类型,如果...

762
来自专栏浪淘沙

实训day04--二维数组,面向对象

2018.06.07 1.方法的签名 cn.edu360.function.Demo1.add(int ,int)

753
来自专栏鸿的学习笔记

python的迭代器和生成器

迭代是数据处理的基础,迭代可以理解为是一种惰性求值。在python里迭代器和生成器是一回事,使用的是yield关键字。

471

扫码关注云+社区