本文共 20966 字,大约阅读时间需要 69 分钟。
人与人之间通过语言来交流沟通,互相协作。人与计算机之间怎样“交流沟通”呢?答案是编程语言。一门语言有词、短语、句子、文章等,对应到编程语言中就是关键字、标识符、表达式、源代码文件等。通常一门编程语言的基本构成如下图所示
本章我们学习 Kotlin语言的基础语法。
变量(数据名称)标识一个对象的地址,我们称之为标识符。而具体存放的数据占用内存的大小和存放的形式则由其类型来决定。
在Kotlin中, 所有的变量类型都是引用类型。Kotlin的变量分为 val
(不可变的) 和var
(可变的) 。可以简单理解为:
val
是只读的,仅能一次赋值,后面就不能被重新赋值。var
是可写的,在它生命周期中可以被多次赋值;
使用关键字 val 声明不可变变量
>>> val a:Int = 1>>> a1
另外,我们可以省略后面的类型Int,直接声明如下
>>> val a = 1 // 根据值 1 编译器能够自动推断出 `Int` 类型>>> a1
用val声明的变量不能重新赋值
>>> val a = 1>>> a++error: val cannot be reassigneda++^
使用 var 声明可变变量
>>> var b = 1>>> b = b + 1>>> b2
只要可能,尽量在Kotlin中首选使用val
不变值。因为事实上在程序中大部分地方只需要使用不可变的变量。使用val变量可以带来可预测的行为和线程安全等优点。
变量名就是标识符。标识符是由字母、数字、下划线组成的字符序列,不能以数字开头。下面是合法的变量名
>>> val _x = 1>>> val y = 2>>> val ip_addr = "127.0.0.1">>> _x1>>> y2>>> ip_addr127.0.0.1
跟Java一样,变量名区分大小写。命名遵循驼峰式规范。
通常情况下,编程语言中都有一些具有特殊意义的标识符是不能用作变量名的,这些具备特殊意义的标识符叫做关键字(又称保留字),编译器需要针对这些关键字进行词法分析,这是编译器对源码进行编译的基础步骤之一。
Kotlin中的修饰符关键字主要分为:
类修饰符、访问修饰符、型变修饰符、成员修饰符、参数修饰符、类型修饰符、函数修饰符、属性修饰符等。这些修饰符如下表2-1所示
表2-1 Kotlin中的修饰符
类修饰符
类修饰符 | 说明 |
---|---|
abstract | 抽象类 |
final | 不可被继承final类 |
enum | 枚举类 |
open | 可继承open类 |
annotation | 注解类 |
sealed | 密封类 |
data | 数据类 |
成员修饰符
成员修饰符 | 说明 |
---|---|
override | 重写函数(方法) |
open | 声明函数可被重写 |
final | 声明函数不可被重写 |
abstract | 声明函数为抽象函数 |
lateinit | 延迟初始化 |
访问权限修饰符
访问权限修饰符 | 说明 |
---|---|
private | 私有,仅当前类可访问 |
protected | 当前类以及继承该类的可访问 |
public | 默认值,对外可访问 |
internal | 整个模块内可访问(模块是指一起编译的一组 Kotlin 源代码文件。例如,一个 Maven 工程, 或 Gradle 工程,通过 Ant 任务的一次调用编译的一组文件等) |
协变逆变修饰符
协变逆变修饰符 | 说明 |
---|---|
in | 消费者类型修饰符,out T 等价于 ? extends T |
out | 生产者类型修饰符,in T 等价于 ? super T |
函数修饰符
函数修饰符 | 说明 |
---|---|
tailrec | 尾递归 |
operator | 运算符重载函数 |
infix | 中缀函数。例如,给Int定义扩展中缀函数 infix fun Int.shl(x: Int): Int |
inline | 内联函数 |
external | 外部函数 |
suspend | 挂起协程函数 |
属性修饰符
属性修饰符 | 说明 |
---|---|
const | 常量修饰符 |
参数修饰符
参数修饰符 | 说明 |
---|---|
vararg | 变长参数修饰符 |
noinline | 不内联参数修饰符,有时,只需要将内联函数的部分参数使用内联Lambda,其他的参数不需要内联,可以使用“noinline”关键字修饰。例如:inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) |
crossinline | 当内联函数不是直接在函数体中使用lambda参数,而是通过其他执行上下文。这种情况下可以在参数前使用“crossinline”关键字修饰标识。 |
代码实例如下。
crossinline代码实例:
inline fun f(crossinline body: () -> Unit) { val f = object: Runnable { override fun run() = body() }}
类型修饰符 | 说明 |
---|---|
reified | 具体化类型参数 |
除了上面的修饰符关键字之外,还有一些其他特殊语义的关键字如下表2-2所示
表2-2 Kotlin中的关键字
关键字 | 说明 |
---|---|
package | 包声明 |
as | 类型转换 |
typealias | 类型别名 |
class | 声明类 |
this | 当前对象引用 |
super | 父类对象引用 |
val | 声明不可变变量 |
var | 声明可变变量 |
fun | 声明函数 |
for | for 循环 |
null | 特殊值 null |
true | 真值 |
false | 假值 |
is | 类型判断 |
throw | 抛出异常 |
return | 返回值 |
break | 跳出循环体 |
continue | 继续下一次循环 |
object | 单例类声明 |
if | 逻辑判断if |
else | 逻辑判断, 结合if使用 |
while | while 循环 |
do | do 循环 |
when | 条件判断 |
interface | 接口声明 |
file | 文件 |
field | 成员 |
property | 属性 |
receiver | 接收者 |
param | 参数 |
setparam | 设置参数 |
delegate | 委托 |
import | 导入包 |
where | where条件 |
by | 委托类或属性 |
get | get函数 |
set | set 函数 |
constructor | 构造函数 |
init | 初始化代码块 |
try | 异常捕获 |
catch | 异常捕获,结合try使用 |
finally | 异常最终执行代码块 |
dynamic | 动态的 |
typeof | 类型定义,预留用 |
这些关键字定义在源码 org.jetbrains.kotlin.lexer.KtTokens.java 中。
流程控制语句是编程语言中的核心之一。可分为:
分支语句(
循环语句(if
、when
)for
、while
) 跳转语句 (return
、break
、continue
、throw
)
if-else语句是控制程序流程的最基本的形式,其中else是可选的。
在 Kotlin 中,if 是一个表达式,即它会返回一个值(跟Scala一样)。
代码示例:
package com.easy.kotlinfun main(args: Array) { println(max(1, 2))}fun max(a: Int, b: Int): Int { // 表达式返回值 val max = if (a > b) a else b return max}
另外,if 的分支可以是代码块,最后的表达式作为该块的值:
fun max3(a: Int, b: Int): Int { val max = if (a > b) { print("Max is a") a // 最后的表达式作为该代码块的值 } else { print("Max is b") b // 同上 } return max}
if作为代码块时,最后一行为其返回值。
另外,在Kotlin中没有类似true? 1: 0
这样的三元表达式。对应的写法是使用if else
语句:
if(true) 1 else 0
if-else语句规则:
代码反例:
>>> if("a") 1error: type mismatch: inferred type is String but Boolean was expectedif("a") 1 ^>>> if(1) println(1)error: the integer literal does not conform to the expected type Booleanif(1) ^
>>> if(true) println(1) else println(0)1>>> if(true) { println(1)} else{ println(0)}1
编程实例:用 if - else 语句判断某年份是否是闰年。
fun isLeapYear(year: Int): Boolean { var isLeapYear: Boolean if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) { isLeapYear = true } else { isLeapYear = false } return isLeapYear}fun main(args: Array) { println(isLeapYear(2017)) // false println(isLeapYear(2020)) // true}
when表达式类似于 switch-case 表达式。when会对所有的分支进行检查直到有一个条件满足。但相比switch而言,when语句要更加的强大,灵活。
Kotlin的极简语法表达风格,使得我们对分支检查的代码写起来更加简单直接:
fun casesWhen(obj: Any?) { when (obj) { 0,1,2,3,4,5,6,7,8,9 -> println("${obj} ===> 这是一个0-9之间的数字") "hello" -> println("${obj} ===> 这个是字符串hello") is Char -> println("${obj} ===> 这是一个 Char 类型数据") else -> println("${obj} ===> else类似于Java中的 case-switch 中的 default") }}fun main(args: Array) { casesWhen(1) casesWhen("hello") casesWhen('X') casesWhen(null)}
输出
1 ===> 这是一个0-9之间的数字hello ===> 这个是字符串helloX ===> 这是一个 Char 类型数据null ===> else类似于Java中的 case-switch 中的 default
像 if 一样,when 的每一个分支也可以是一个代码块,它的值是块中最后的表达式的值。
如果其他分支都不满足条件会到 else 分支(类似default)。
如果我们有很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔:
0,1,2,3,4,5,6,7,8,9 -> println("${obj} ===> 这是一个0-9之间的数字")
我们可以用任意表达式(而不只是常量)作为分支条件
fun switch(x: Int) { val s = "123" when (x) { -1, 0 -> print("x == -1 or x == 0") 1 -> print("x == 1") 2 -> print("x == 2") 8 -> print("x is 8") parseInt(s) -> println("x is 123") else -> { // 注意这个块 print("x is neither 1 nor 2") } }}
我们也可以检测一个值在 in 或者不在 !in 一个区间或者集合中:
val x = 1 val validNumbers = arrayOf(1, 2, 3) when (x) { in 1..10 -> print("x is in the range") in validNumbers -> print("x is valid") !in 10..20 -> print("x is outside the range") else -> print("none of the above") }
编程实例: 用when语句写一个阶乘函数。
fun fact(n: Int): Int { var result = 1 when (n) { 0, 1 -> result = 1 else -> result = n * fact(n - 1) } return result}fact(10) // 3628800
for 循环可以对任何提供迭代器(iterator)的对象进行遍历,语法如下:
for (item in collection) { print(item)}
如果想要通过索引遍历一个数组或者一个 list,可以这么做:
for (i in array.indices) { print(array[i])}
或者使用库函数 withIndex
:
for ((index, value) in array.withIndex()) { println("the element at $index is $value")}
另外,范围(Ranges)表达式也可用于循环当中:
if (i in 1..10) { // 等同于1 <= i && i <= 10 println(i) }
简写
(1..10).forEach { print(it) }
其中的操作符形式的 1..10 等价于 1.rangeTo(10) 函数调用 ,由in和!in进行连接。
编程实例: 编写一个 Kotlin 程序在屏幕上输出1!+2!+3!+……+10!的和。
我们使用上面的fact函数,代码实现如下
fun sumFact(n: Int): Int { var sum = 0 for (i in 1..n) { sum += fact(i) } return sum}sumFact(10) // 4037913
while 和 do .. while使用方式跟C、Java语言基本一致。
代码示例
package com.easy.kotlinfun main(args: Array) { var x = 10 while (x > 0) { x-- println(x) } var y = 10 do { y = y + 1 println(y) } while (y < 20) // y的作用域包含此处}
break
和continue
都是用来控制循环结构的,主要是用来停止循环(中断跳转),但是有区别,下面我们分别介绍。
break
用于完全结束一个循环,直接跳出循环体,然后执行循环后面的语句。
问题场景:
打印数字1-10,只要遇到偶数就结束打印。
代码示例:
for (i in 1..10) { println(i) if (i % 2 == 0) { break } } // break to here
输出:
12
continue
是只终止本轮循环,但是还会继续下一轮循环。可以简单理解为,直接在当前语句处中断,跳转到循环入口,执行下一轮循环。而break
则是完全终止循环,跳转到循环出口。
问题场景:
打印1-10中的奇数。
代码示例:
for (i in 1..10) { if (i % 2 == 0) { continue } println(i) }
输出
13579
在Java、C语言中,return语句使我们再常见不过的了。虽然在Scala,Groovy这样的语言中,函数的返回值可以不需要显示用return来指定,但是我们仍然认为,使用return的编码风格更加容易阅读理解 (尤其是在分支流代码块中)。
在Kotlin中,除了表达式的值,有返回值的函数都要求显式使用return
来返回其值。
代码示例
fun sum(a: Int,b: Int): Int{ return a+b // 这里的return不能省略}fun max(a: Int, b: Int): Int { if (a > b){ return a // return不能省略} else{ return b // return不能省略}
我们在Kotlin中,可以直接使用=
符号来直接返回一个函数的值,这样的函数我们称为函数字面量。
代码示例
>>> fun sum(a: Int,b: Int) = a + b>>> fun max(a: Int, b: Int) = if (a > b) a else b>>> sum(1,10)11>>> max(1,2)2>>> val sum=fun(a:Int, b:Int) = a+b>>> sum(kotlin.Int, kotlin.Int) -> kotlin.Int>>> sum(1,1)2
后面的函数体语句有没有大括号 {}
意思完全不同。加了大括号,意义就完全不一样了。
>>> val sumf = fun(a:Int, b:Int) = {a+b}>>> sumf(kotlin.Int, kotlin.Int) -> () -> kotlin.Int>>> sumf(1,1)() -> kotlin.Int>>> sumf(1,1).invoke()2
我们再通过下面的代码示例清晰的看出:
>>> fun sumf(a:Int,b:Int) = {a+b}>>> sumf(1,1)() -> kotlin.Int>>> sumf(1,1).invoke()2>>> fun maxf(a:Int, b:Int) = {if(a>b) a else b}>>> maxf(1,2)() -> kotlin.Int>>> maxf(1,2).invoke()2
可以看出,sumf
,maxf
的返回值是函数类型:
() -> kotlin.Int() -> kotlin.Int
这点跟Scala是不同的。在Scala中,带不带大括号{}
,意思一样:
scala> def maxf(x:Int, y:Int) = { if(x>y) x else y }maxf: (x: Int, y: Int)Intscala> def maxv(x:Int, y:Int) = if(x>y) x else ymaxv: (x: Int, y: Int)Intscala> maxf(1,2)res4: Int = 2scala> maxv(1,2)res6: Int = 2
我们可以看出maxf: (x: Int, y: Int)Int
跟maxv: (x: Int, y: Int)Int
签名是一样的。在这里,Kotlin跟Scala在大括号的使用上,是完全不同的。
然后,调用函数方式是直接调用invoke()
函数:sumf(1,1).invoke()。
kotlin 中 return
语句会从最近的函数或匿名函数中返回,但是在Lambda表达式中遇到return,则直接返回最近的外层函数。例如下面两个函数是不同的:
val intArray = intArrayOf(1, 2, 3, 4, 5) intArray.forEach { if (it == 3) return // 在Lambda表达式中的return 直接返回最近的外层函数 println(it) }
输出:
12
遇到 3 时会直接返回(有点类似循环体中的break
行为)。
而我们给forEach传入一个匿名函数 fun(a: Int) ,这个匿名函数里面的return不会跳出forEach循环,有点像continue的逻辑:
val intArray = intArrayOf(1, 2, 3, 4, 5) intArray.forEach(fun(a: Int) { if (a == 3) return // 从最近的函数中返回 println(a) })
输出
1245
为了显式的指明 return
返回的地址,kotlin 还提供了 @Label
(标签) 来控制返回语句,且看下节分解。
在 Kotlin 中任何表达式都可以用标签(label)来标记。 标签的格式为标识符后跟 @
符号,例如:abc@
、_isOK@
都是有效的标签。我们可以用Label标签来控制 return
、break
或 continue
的跳转(jump)行为。
代码示例:
val intArray = intArrayOf(1, 2, 3, 4, 5) intArray.forEach here@ { if (it == 3) return@here // 指令跳转到 lambda 表达式标签 here@ 处。继续下一个it=4的遍历循环 println(it) }
输出:
1245
我们在 lambda 表达式开头处添加了标签here@
,我们可以这么理解:该标签相当于是记录了Lambda表达式的指令执行入口地址, 然后在表达式内部我们使用return@here
来跳转至Lambda表达式该地址处。这样代码更加易懂。
另外,我们也可以使用隐式标签更方便。 该标签与接收该 lambda 的函数同名。
代码示例
val intArray = intArrayOf(1, 2, 3, 4, 5) intArray.forEach { if (it == 3) return@forEach // 返回到 @forEach 处继续下一个循环 println(it) }
输出:
1245
接收该Lambda表达式的函数是forEach, 所以我们可以直接使用 return@forEach
,来跳转到此处执行下一轮循环。
在 Kotlin 中 throw 是表达式,它的类型是特殊类型 Nothing。 该类型没有值。跟C、Java中的void
意思一样。
>>> Nothing::classclass java.lang.Void
我们在代码中,用 Nothing 来标记无返回的函数:
>>> fun fail(msg:String):Nothing{ throw IllegalArgumentException(msg) }>>> fail("XXXX")java.lang.IllegalArgumentException: XXXX at Line57.fail(Unknown Source)
另外,如果把一个throw表达式的值赋值给一个变量,需要显式声明类型为Nothing
, 代码示例如下
>>> val ex = throw Exception("YYYYYYYY")error: 'Nothing' property type needs to be specified explicitlyval ex = throw Exception("YYYYYYYY") ^>>> val ex:Nothing = throw Exception("YYYYYYYY")java.lang.Exception: YYYYYYYY
另外,因为ex变量是Nothing类型,没有任何值,所以无法当做参数传给函数。
Kotlin 允许我们为自己的类型提供预定义的一组操作符的实现。这些操作符具有固定的符号表示(如 +
或 *
)和固定的优先级。这些操作符的符号定义如下:
KtSingleValueToken LBRACKET = new KtSingleValueToken("LBRACKET", "["); KtSingleValueToken RBRACKET = new KtSingleValueToken("RBRACKET", "]"); KtSingleValueToken LBRACE = new KtSingleValueToken("LBRACE", "{"); KtSingleValueToken RBRACE = new KtSingleValueToken("RBRACE", "}"); KtSingleValueToken LPAR = new KtSingleValueToken("LPAR", "("); KtSingleValueToken RPAR = new KtSingleValueToken("RPAR", ")"); KtSingleValueToken DOT = new KtSingleValueToken("DOT", "."); KtSingleValueToken PLUSPLUS = new KtSingleValueToken("PLUSPLUS", "++"); KtSingleValueToken MINUSMINUS = new KtSingleValueToken("MINUSMINUS", "--"); KtSingleValueToken MUL = new KtSingleValueToken("MUL", "*"); KtSingleValueToken PLUS = new KtSingleValueToken("PLUS", "+"); KtSingleValueToken MINUS = new KtSingleValueToken("MINUS", "-"); KtSingleValueToken EXCL = new KtSingleValueToken("EXCL", "!"); KtSingleValueToken DIV = new KtSingleValueToken("DIV", "/"); KtSingleValueToken PERC = new KtSingleValueToken("PERC", "%"); KtSingleValueToken LT = new KtSingleValueToken("LT", "<"); KtSingleValueToken GT = new KtSingleValueToken("GT", ">"); KtSingleValueToken LTEQ = new KtSingleValueToken("LTEQ", "<="); KtSingleValueToken GTEQ = new KtSingleValueToken("GTEQ", ">="); KtSingleValueToken EQEQEQ = new KtSingleValueToken("EQEQEQ", "==="); KtSingleValueToken ARROW = new KtSingleValueToken("ARROW", "->"); KtSingleValueToken DOUBLE_ARROW = new KtSingleValueToken("DOUBLE_ARROW", "=>"); KtSingleValueToken EXCLEQEQEQ = new KtSingleValueToken("EXCLEQEQEQ", "!=="); KtSingleValueToken EQEQ = new KtSingleValueToken("EQEQ", "=="); KtSingleValueToken EXCLEQ = new KtSingleValueToken("EXCLEQ", "!="); KtSingleValueToken EXCLEXCL = new KtSingleValueToken("EXCLEXCL", "!!"); KtSingleValueToken ANDAND = new KtSingleValueToken("ANDAND", "&&"); KtSingleValueToken OROR = new KtSingleValueToken("OROR", "||"); KtSingleValueToken SAFE_ACCESS = new KtSingleValueToken("SAFE_ACCESS", "?."); KtSingleValueToken ELVIS = new KtSingleValueToken("ELVIS", "?:"); KtSingleValueToken QUEST = new KtSingleValueToken("QUEST", "?"); KtSingleValueToken COLONCOLON = new KtSingleValueToken("COLONCOLON", "::"); KtSingleValueToken COLON = new KtSingleValueToken("COLON", ":"); KtSingleValueToken SEMICOLON = new KtSingleValueToken("SEMICOLON", ";"); KtSingleValueToken DOUBLE_SEMICOLON = new KtSingleValueToken("DOUBLE_SEMICOLON", ";;"); KtSingleValueToken RANGE = new KtSingleValueToken("RANGE", ".."); KtSingleValueToken EQ = new KtSingleValueToken("EQ", "="); KtSingleValueToken MULTEQ = new KtSingleValueToken("MULTEQ", "*="); KtSingleValueToken DIVEQ = new KtSingleValueToken("DIVEQ", "/="); KtSingleValueToken PERCEQ = new KtSingleValueToken("PERCEQ", "%="); KtSingleValueToken PLUSEQ = new KtSingleValueToken("PLUSEQ", "+="); KtSingleValueToken MINUSEQ = new KtSingleValueToken("MINUSEQ", "-="); KtKeywordToken NOT_IN = KtKeywordToken.keyword("NOT_IN", "!in"); KtKeywordToken NOT_IS = KtKeywordToken.keyword("NOT_IS", "!is"); KtSingleValueToken HASH = new KtSingleValueToken("HASH", "#"); KtSingleValueToken AT = new KtSingleValueToken("AT", "@"); KtSingleValueToken COMMA = new KtSingleValueToken("COMMA", ",");
Kotlin中操作符的优先级(Precedence)如下表所示
表2-3 操作符的优先级
优先级 | 标题 | 符号 |
---|---|---|
最高 | 后缀(Postfix ) | ++ , -- , . , ?. , ? |
前缀(Prefix) | - , + , ++ , -- , ! , @ | |
右手类型运算(Type RHS,right-hand side class type (RHS) ) | : , as , as? | |
乘除取余(Multiplicative) | * , / , % | |
加减(Additive ) | + , - | |
区间范围(Range) | .. | |
Infix函数 | 例如,给Int 定义扩展 infix fun Int.shl(x: Int): Int {...} ,这样调用 1 shl 2 ,等同于1.shl(2) | |
Elvis操作符 | ?: | |
命名检查符(Named checks) | in , !in , is , !is | |
比较大小(Comparison) | < , > , <= , >= | |
相等性判断(Equality) | == , != , === , !== | |
与 (Conjunction) | && | |
或 (Disjunction) | || | |
最低 | 赋值(Assignment) | = , += , -= , *= , /= , %= |
为实现这些的操作符,Kotlin为二元操作符左侧的类型和一元操作符的参数类型,提供了相应的函数或扩展函数。重载操作符的函数需要用 operator
修饰符标记。中缀操作符的函数使用infix
修饰符标记。
一元操作符(unary operation) 有前缀操作符、递增和递减操作符等。
前缀操作符放在操作数的前面。它们分别如表2-4所示
表2-4 前缀操作符
表达式 | 翻译为 |
---|---|
+a | a.unaryPlus() |
-a | a.unaryMinus() |
!a | a.not() |
以下是重载一元减运算符的示例:
package com.easy.kotlindata class Point(val x: Int, val y: Int)operator fun Point.unaryMinus() = Point(-x, -y)
测试代码:
package com.easy.kotlinimport org.junit.Testimport org.junit.runner.RunWithimport org.junit.runners.JUnit4@RunWith(JUnit4::class)class OperatorDemoTest { @Test fun testPointUnaryMinus() { val p = Point(1, 1) val np = -p println(np) //Point(x=-1, y=-1) }}
表2-5 递增和递减操作符
表达式 | 翻译为 |
---|---|
a++ | a.inc() 返回值是a |
a-- | a.dec() 返回值是a |
++a | a.inc() 返回值是a+1 |
--a | a.dec() 返回值是a-1 |
inc()
和 dec()
函数必须返回一个值,它用于赋值给使用
++
或 --
操作的变量。 Kotlin中的二元操作符有算术运算符、索引访问操作符、调用操作符、计算并赋值操作符、相等与不等操作符、Elvis 操作符、比较操作符、中缀操作符等。下面我们分别作介绍。
表2-6 算术运算符
表达式 | 翻译为 |
---|---|
a + b | a.plus(b) |
a - b | a.minus(b) |
a * b | a.times(b) |
a / b | a.div(b) |
a % b | a.rem(b) 、 a.mod(b) |
a..b | a.rangeTo(b) |
代码示例
>>> val a=10>>> val b=3>>> a+b13>>> a-b7>>> a/b3>>> a%b1>>> a..b10..3>>> b..a3..10
+
运算符重载先用代码举个例子:
>>> ""+11>>> 1+""error: none of the following functions can be called with the arguments supplied: public final operator fun plus(other: Byte): Int defined in kotlin.Intpublic final operator fun plus(other: Double): Double defined in kotlin.Intpublic final operator fun plus(other: Float): Float defined in kotlin.Intpublic final operator fun plus(other: Int): Int defined in kotlin.Intpublic final operator fun plus(other: Long): Long defined in kotlin.Intpublic final operator fun plus(other: Short): Int defined in kotlin.Int1+"" ^
从上面的示例,我们可以看出,在Kotlin中1+""
是不允许的(这地方,相比Scala,写这样的Kotlin代码就显得不大友好),只能显式调用toString
来相加:
>>> 1.toString()+""1
+
运算符下面我们使用一个计数类 Counter 重载的 +
运算符来增加index的计数值。
代码示例
data class Counter(var index: Int)operator fun Counter.plus(increment: Int): Counter { return Counter(index + increment)}
测试类
package com.easy.kotlinimport org.junit.Testimport org.junit.runner.RunWithimport org.junit.runners.JUnit4@RunWith(JUnit4::class)class OperatorDemoTest @Test fun testCounterIndexPlus() { val c = Counter(1) val cplus = c + 10 println(cplus) //Counter(index=11) }}
in
操作符表2-7 in操作符
表达式 | 翻译为 |
---|---|
a in b | b.contains(a) |
a !in b | !b.contains(a) |
in操作符等价于函数contains 。
表2-8 索引访问操作符操作符
表达式 | 翻译为 |
---|---|
a[i] | a.get(i) |
a[i] = b | a.set(i, b) |
方括号转换为调用带有适当数量参数的 get
和 set
。
表2-9 调用操作符
表达式 | 翻译为 |
---|---|
a() | a.invoke() |
a(i) | a.invoke(i) |
圆括号转换为调用带有适当数量参数的 invoke
。
表2-10 计算并赋值操作符
表达式 | 翻译为 |
---|---|
a += b | a.plusAssign(b) |
a -= b | a.minusAssign(b) |
a *= b | a.timesAssign(b) |
a /= b | a.divAssign(b) |
a %= b | a.modAssign(b) |
对于赋值操作,例如 a += b
,编译器会试着生成 a = a + b
的代码(这里包含类型检查:a + b
的类型必须是 a
的子类型)。
Kotlin 中有两种类型的相等性:
===
!==
(两个引用指向同一对象)==
!=
( 使用equals()
判断)表2-11 相等与不等操作符
表达式 | 翻译为 |
---|---|
a == b | a?.equals(b) ?: (b === null) |
a != b | !(a?.equals(b) ?: (b === null)) |
这个 ==
操作符有些特殊:它被翻译成一个复杂的表达式,用于筛选 null
值。
意思是:如果 a 不是 null 则调用 equals(Any?)
函数并返回其值;否则(即 a === null
)就计算 b === null
的值并返回。
当与 null 显式比较时,a == null
会被自动转换为 a=== null
注意:===
和 !==
不可重载。
?:
在Kotin中,Elvis操作符特定是跟null比较。也就是说
y = x?:0
等价于
val y = if(x!==null) x else 0
主要用来作null
安全性检查。
Elvis操作符 ?:
是一个二元运算符,如果第一个操作数为真,则返回第一个操作数,否则将计算并返回其第二个操作数。它是三元条件运算符的变体。命名灵感来自猫王的发型风格。
Kotlin中没有这样的三元运算符 true?1:0
,取而代之的是if(true) 1 else 0
。而Elvis操作符算是精简版的三元运算符。
我们在Java中使用的三元运算符的语法,你通常要重复变量两次, 示例:
String name = "Elvis Presley";String displayName = (name != null) ? name : "Unknown";
取而代之,你可以使用Elvis操作符
String name = "Elvis Presley";String displayName = name?:"Unknown"
我们可以看出,用Elvis操作符(?:)可以把带有默认值的if/else结构写的及其短小。用Elvis操作符不用检查null(避免了NullPointerException
),也不用重复变量。
这个Elvis操作符功能在Spring 表达式语言 (SpEL)中提供。
在Kotlin中当然就没有理由不支持这个特性。
代码示例:
>>> val x = null>>> val y = x?:0>>> y0>>> val x = false>>> val y = x?:0>>> yfalse>>> val x = "">>> val y = x?:0>>> y>>> val x = "abc">>> val y = x?:0>>> yabc
表2-12 比较操作符
表达式 | 翻译为 |
---|---|
a > b | a.compareTo(b) > 0 |
a < b | a.compareTo(b) < 0 |
a >= b | a.compareTo(b) >= 0 |
a <= b | a.compareTo(b) <= 0 |
所有的比较都转换为对 compareTo
的调用,这个函数需要返回 Int
值
我们可以通过自定义infix函数来实现中缀操作符。
代码示例
data class Person(val name: String, val age: Int)infix fun Person.grow(years: Int): Person { return Person(name, age + years)}
测试代码
package com.easy.kotlinimport org.junit.Testimport org.junit.runner.RunWithimport org.junit.runners.JUnit4@RunWith(JUnit4::class)class InfixFunctionDemoTest { @Test fun testInfixFuntion() { val person = Person("Jack", 20) println(person.grow(2)) println(person grow 2) }}
输出
Person(name=Jack, age=22)Person(name=Jack, age=22)
我们在*.kt
源文件开头声明package
命名空间。例如在PackageDemo.kt源代码中,我们按照如下方式声明包
package com.easy.kotlinfun what(){ // 包级函数 println("This is WHAT ?")}fun main(args:Array){ // 一个包下面只能有一个main函数 println("Hello,World!")}class Motorbike{ // 包里面的类 fun drive(){ println("Drive The Motorbike ...") }}
Kotlin中的目录与包的结构无需匹配,源代码文件可以在文件系统中的任意位置。
如果一个测试类PackageDemoTest跟PackageDemo在同一个包下面,我们就不需要单独去import 类和包级函数,可以在代码里直接调用
package com.easy.kotlinimport org.junit.Testimport org.junit.runner.RunWithimport org.junit.runners.JUnit4@RunWith(JUnit4::class)class PackageDemoTest { @Test fun testWhat() { what() } @Test fun testDriveMotorbike(){ val motorbike = Motorbike() motorbike.drive() }}
其中,what()
函数跟PackageDemoTest
类在同一个包命名空间下,可以直接调用,不需要 import
。Motorbike
类跟PackageDemoTest
类同理分析。
如果不在同一个package下面,我们就需要import对应的类和函数。例如,我们在 src/test/kotlin
目录下新建一个package com.easy.kotlin.test
, 使用package com.easy.kotlin
下面的类和函数,示例如下
package com.easy.kotlin.testimport com.easy.kotlin.Motorbike // 导入类Motorbikeimport com.easy.kotlin.what // 导入包级函数whatimport org.junit.Testimport org.junit.runner.RunWithimport org.junit.runners.JUnit4@RunWith(JUnit4::class)class PackageDemoTest { @Test fun testWhat() { what() } @Test fun testDriveMotorbike() { val motorbike = Motorbike() motorbike.drive() }}
Kotlin会会默认导入一些基础包到每个 Kotlin 文件中:
kotlin.*kotlin.annotation.*kotlin.collections.*kotlin.comparisons.* (自 1.1 起)kotlin.io.*kotlin.ranges.*kotlin.sequences.*kotlin.text.*
根据目标平台还会导入额外的包:
JVM:
java.lang.*kotlin.jvm.*
JS:
kotlin.js.*
转载地址:http://aofva.baihongyu.com/