介绍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 条评论
登录 后参与评论

相关文章

来自专栏Charlie's Road

Swift算法俱乐部:Swift栈(Stack)数据结构

翻译自raywenderlich网站iOS教程Swift Algorithm Club系列

12720
来自专栏恰童鞋骚年

正则表达式30分钟入门教程--deerchao

原文地址:http://www.cnblogs.com/deerchao/archive/2006/08/24/zhengzhe30fengzhongjiaoc...

29440
来自专栏携程技术中心

干货 | Kotlin超棒的语言特性

20540
来自专栏个人随笔

Java 持久化操作之 --io流与序列化

1)File类操作文件的属性 1.File类的常用方法 ? 1. 文件的绝对完整路径:getAbsolutePath() 文件名:getName() 文件相对路...

30490
来自专栏余林丰

Java中net.sf.json包关于JSON与对象互转的坑

  在Web开发过程中离不开数据的交互,这就需要规定交互数据的相关格式,以便数据在客户端与服务器之间进行传递。数据的格式通常有2种:1、xml;2、JSON。通...

42350
来自专栏天天

Airbnb JavaScript Style Guide

const foo = 1; let bar = foo; bar = 9; console.log(foo, bar); // => 1, 9

20520
来自专栏Coding迪斯尼

reactjs自制Monkey语言编译器:解析组合表达式,ifelse语句块和间套函数调用

8030
来自专栏向治洪

Kotlin 是如何避免空指针问题的

在谈Kotlin的优势的时候,大家都会想到空指针安全这一点,那么Kotlin又是如何避免这些问题的呢?下面从Kotlin的一些语法规则上给出介绍。 可空类型 默...

21770
来自专栏owent

VC和GCC成员函数指针实现的研究(二)

调用的时候主要看(c.*vptr2)()的代码。因为(c.vptr1)()生成的和单继承一样。而由于它们最终都转向vcall,所以vptr2的时候调整了虚表指针...

10420
来自专栏PHP在线

分钟学会正则表达式(译)

正则表达式(“regexes”)即增强查找/字符串替换操作。当在文本编辑器中编辑文字时,正则表达式经常用于: 检查文本是否包含一个给定的模式 查找任何匹配的模式...

389130

扫码关注云+社区

领取腾讯云代金券