不一样的类型声明
Kotlin 采用的是与 Java 相反的类型声明方式,类型名通常在变量名的后面: val a: String = "I am Kotlin"
为什么采用这种风格呢?Kotlin官方的FAQ的回答是这样的:
我们相信这样可以使得代码的可读性更好。同时,这也有利于使用一些良好的语法特性,比如省略类型声明。Scala的经验表明,这不是一个错误的选择。
所以,类型放在变量后面的其中一个原因是为了类型省略,这个类型省略其实就是类型推导。
增强的类型推导
类型推导是Kotlin在Java的基础上增强的语言特性之一,即编译器可以在不显式声明类型的情况下,自动推导出它所需要的类型,如下:
1 | val string = "I am Kotlin" |
类型推导很大程度上提高了Kotlin这种静态类型语言的开发效率,虽然静态类型语言有很多优点,然而在编码过程中却需要书写大量的类型。
声明函数返回值类型
虽然支持类型推导,但是函数返回值类型必须要显式声明,比如:
fun sum(x: Int, y: Int): Int { return x + y }
此时也许与Java的区别不大,其实Kotlin进一步增强了函数语法,我们可以把 {} 去掉,用等号来定义一个函数:
fun sum(x: Int, y: Int) = x + y
Kotlin支持的这种单行表达式与等号的语法定义的函数,叫做表达式函数体,作为区分,普通的函数声明叫做代码块函数体。但是别高兴太早,我们再来看一段递归程序:
fun foo(n: Int) = if(n == 0) 1 else n * foo(n - 1)
这种情况下,编译器并不能针对递归函数的情况推导类型,因此这里会报错。所以,在一些诸如递归等复杂条件下,及时用表达式定义函数,我们也必须显式声明类型,才能让程序正常工作,代码如下:
fun foo(n: Int): Int = if(n == 0) 1 else n * foo(n - 1)
val 和 var 的使用规则
Kotlin声明变量时,引入了 val 和 var 的概念。var 容易理解,就是变量,在JavaScript 中也有用到,但是 val 是什么呢?如果在 IDEA 中反编译 val 的实现成Java代码就能发现,它是通过 final 这一特性实现的。
优先使用val避免副作用
Kotlin支持一开始不定义 val 变量的取值,随后再进行赋值,然而,因为引用不可变,所以val声明的变量只能被赋值一次,并且声明时不能省略变量类型,如下所示:
1 | fun main(args: Array<String>) { |
由于不可变性,我们可以直到 val 变量在并发环境更安全。
var 的适用场景
既然 val 那么好,为什么要 var 呢?首先,Kotlin 要兼容Java ,这就注定 必须有 var 的存在;其次有一些场景如果不适用 var 就必须得用到 递归 才能实现了,所以var需要存在。
高阶函数和Lambda
Kotlin 天然支持了部分函数式特性,函数式语言的一个典型特征在于函数式头等公民——我们不仅可以像类一样在顶层直接定义一个函数,也可以在函数内部定义一个局部函数!如下所示:
1 | fun foo(x: Int) { |
此外,Kotlin还能直接将函数像普通变量一样传递给另一个函数,或在其他函数中被返回,如何理解这个特性?
抽象和高阶函数
概念东西,略
函数的类型
在Kotlin中,函数类型的格式非常简单,举个例子:(Int) -> Unit
,我们可以发现,Kotlin中的函数类型需要遵循以下几点:
- 通过 -> 符号来组织参数类型和返回值类型,左边是参数类型,右边是返回值类型
- 必须用一个括号来包裹参数类型,如果多个参数,可以用逗号分割,如:
(Int, String?) -> Unit
- 返回值即使是 Unit ,也必须显式声明
此外,Kotlin 还支持为声明参数指定名字:(errCode: Int, errMsg: String?) -> Unit
这还没完,高阶函数还支持返回另一个函数,所以还能这么做:
1 | (Int) -> ((Int) -> Unit) |
方法和成员引用
Kotlin 存在一种特殊的语法,通过两个冒号来四号线对于某个类的方法进行引用。假如有一个CountryTest 类的对象实例 countryTest ,如果要引用它的 isBigEuropeanCountry 方法,就可以这么写:
countryTest::isBigEuropeanCountry
此外,我们还可以直接通过这种语法,来定义一个类的构造方法引用变量:
1 | class Book(val name: String) |
可以发现,getBook 的类型为 (name: String) -> Book 。类似的道理,如果我们要引用某个类中的成员变量,比如Book类中的name,就可以这样引用: Book:name
,以上 Book::name 的类型为 (Book) -> String 。当我们在对Book 类对象的集合应用一些函数式API的时候,就会显得格外有用,比如:
1 | fun main(args: Array<String>) { |
匿名函数
略
Lambda 是语法糖
Kotlin 在JVM 层设计了 Function 类型 (Function0,Function1…Function22)来兼容Java的Lambda表达式,其中后缀数字代表了 Lambda 参数的数量。比如,Function1在源码中就是如下表示的:
1 | interface Function1<in P1, out R>: kotlin.Function<R> { |
可见每个Function 类型都有一个invoke方法,设计Function类型的目的之一就是要兼容Java ,实现在Kotlin 中也能调用Java的Lambda。在 Java 中,实际上不支持把函数作为参数,而是通过函数式接口来实现这一特性。
函数、Lambda和闭包
略
“柯里化”风格、扩展函数
柯里化略
在我们介绍的Lambda的表达式中,还存在一种特殊的语法,如果一个函数只有一个参数,且该参数为函数类型,那么在调用该函数时,外面的括号就可以省略,例子如下:
1 | fun omit(block: () -> Unit) { |
另一项特性 扩展函数,允许我们在不修改已有类的前提下,给它增加新的方法,示例如下:
1 | fun View.invisible() { |
在上述例子中,类型View被称为接收者类型,this对应的是这个类型锁创建的接收者对象,this也能被省略,就像这样:
1 | fun View.invisible() { |
面向表达式编程
现在,罗列下我们已经提及的表达式概念:
- if表达式
- 函数体表达式
- Lambda表达式
- 函数引用表达式
Unit类型:让函数调用皆为表达式
之所有不能说Java中的函数调用皆是表达式,是因为存在特例 void,在Java中如果声明的函数没有返回值,那么它就要用void修饰:
1 | void foo () { |
所以foo就不具有值和类型信息,就不能算作一个表达式。函数式语言在所有的情况下都具有返回类型,所以kotlin引入了 Unit 来替代 void 关键字。如何理解 Unit ?其实与 int 一样,都是一种类型,然而它不代表任何信息,它就是一个单例,它的实例只有一个 ,可以写为 () 。
for循环和范围表达式
在Java中,经常在for来构建循环体:
1 | for (int i = 0; i < 10; i ++) { |
但是kotlin会简单很多:
1 | for (i in 1..10) println(i) |
范围表达式,1..10 这种语法是范围表达式(range) 。
官网的表述是:Range表达式是通过rangeTo 函数实现的,通过 .. 操作符与某种类型的对象组成,除了整形的基本类型外,该类型需要实现 java.lang.Comparable 接口
举个例子,由于 String类实现了 Comparable 接口,字符串之间可以比较大小,所以我们可以创建一个字符串区间,如 "abc".."xyz"
。
另外,kotlin 还提供了步长和倒序以及半开区间:
1 | //步长 |
用 in 来检查成员关系,,在Kotlin中我们可以用 in 关键字来检查一个元素是否是一个区间或者集合中的成员,比如:"a" in listOf ("b" , "c")
,会返回 false ;在 in 之前加上叹号就是相反结果: "a" !in listOf ("b" , "c")
返回true。更多的应用场景如下:
1 | //结合范围表达式 |
中缀表达式
前面见识过 in、step、downTo、until 这些写法,都不需要通过点号,而是用中缀表达式来被调用,从而语法更直观。这是如何实现的呢?看下标准库中类似的方法 to 的设计:
1 | infix fun <A,B> A.to(that: B): Pair<A, B> |
函数可变参数
Java 中采用 “…” 来表示可变参数,Kotlin中通过 varargs 关键字实现可变参数…。需要注意的是,Java 中的可变参数必须是最后一个参数,Ktolin中没有这个限制,但两者都可以在函数体中以数组方式来使用可变参数变量:
1 | fun printLetters(varargs letters: String, count: Int) { |
由于to会返回 Pair 这种键值对的结构数据,因此我们经常会把它与map结合在一起使用,如下:
1 | mapOf( |
字符串的定义和操作
kotlin 中有丰富的API,比如: "abcdefg".filter {c -> c in 'a'..'d'}
//输出 abcd
定义原生字符串
Java 对原生字符串只能通过转义字符的方法支持。然而,在Kotlin中已经支持直接写原生字符串,使用3个引号的方式(“””),体验下:
1 | val rawString = """ |
可以看到非常简洁,如果用Java 来表示会非常复杂,尤其是 Html 代码。
字符串模板
略
字符串判等
Kotlin 中判等性有两种类型:
- 结构相等。 通过 == 来判定两个对象的内容是否相等
- 引用相等。通过 === 来判断两个对象的引用是否一样,与之相反的操作是 !== ,