人与人之间经过语言来交流沟通,互相协做。人与计算机之间怎样“交流沟通”呢?答案是编程语言。一门语言有词、短语、句子、文章等,对应到编程语言中就是关键字、标识符、表达式、源代码文件等。一般一门编程语言的基本构成以下图所示java
本章咱们学习 Kotlin语言的基础语法。es6
变量(数据名称)标识一个对象的地址,咱们称之为标识符。而具体存放的数据占用内存的大小和存放的形式则由其类型来决定。编程
在Kotlin中, 全部的变量类型都是引用类型。Kotlin的变量分为 val
(不可变的) 和var
(可变的) 。能够简单理解为:数组
val
是只读的,仅能一次赋值,后面就不能被从新赋值。var
是可写的,在它生命周期中能够被屡次赋值;安全
使用关键字 val 声明不可变变量jvm
>>> val a:Int = 1 >>> a 1
另外,咱们能够省略后面的类型Int,直接声明以下编程语言
>>> val a = 1 // 根据值 1 编译器可以自动推断出 `Int` 类型 >>> a 1
用val声明的变量不能从新赋值ide
>>> val a = 1 >>> a++ error: val cannot be reassigned a++ ^
使用 var 声明可变变量函数
>>> var b = 1 >>> b = b + 1 >>> b 2
只要可能,尽可能在Kotlin中首选使用val
不变值。由于事实上在程序中大部分地方只须要使用不可变的变量。使用val变量能够带来可预测的行为和线程安全等优势。学习
变量名就是标识符。标识符是由字母、数字、下划线组成的字符序列,不能以数字开头。下面是合法的变量名
>>> val _x = 1 >>> val y = 2 >>> val ip_addr = "127.0.0.1" >>> _x 1 >>> y 2 >>> ip_addr 127.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.kotlin fun main(args: Array<String>) { 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") 1 error: type mismatch: inferred type is String but Boolean was expected if("a") 1 ^ >>> if(1) println(1) error: the integer literal does not conform to the expected type Boolean if(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<String>) { 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<String>) { casesWhen(1) casesWhen("hello") casesWhen('X') casesWhen(null) }
输出
1 ===> 这是一个0-9之间的数字 hello ===> 这个是字符串hello X ===> 这是一个 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") }
编程实例: 编写一个 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.kotlin fun main(args: Array<String>) { var x = 10 while (x > 0) { x-- println(x) } var y = 10 do { y = y + 1 println(y) } while (y < 20) // y的做用域包含此处 }
### 2.3.5 break 和 continue
break
和continue
都是用来控制循环结构的,主要是用来中止循环(中断跳转),可是有区别,下面咱们分别介绍。
break
用于彻底结束一个循环,直接跳出循环体,而后执行循环后面的语句。
问题场景:
打印数字1-10,只要遇到偶数就结束打印。
代码示例:
for (i in 1..10) { println(i) if (i % 2 == 0) { break } } // break to here
输出:
1 2
continue
是只终止本轮循环,可是还会继续下一轮循环。能够简单理解为,直接在当前语句处中断,跳转到循环入口,执行下一轮循环。而break
则是彻底终止循环,跳转到循环出口。
问题场景:
打印1-10中的奇数。
代码示例:
for (i in 1..10) { if (i % 2 == 0) { continue } println(i) }
输出
1 3 5 7 9
在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)Int scala> def maxv(x:Int, y:Int) = if(x>y) x else y maxv: (x: Int, y: Int)Int scala> maxf(1,2) res4: Int = 2 scala> 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) }
输出:
1 2
遇到 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) })
输出
1 2 4 5
为了显式的指明 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) }
输出:
1 2 4 5
咱们在 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) }
输出:
1 2 4 5
接收该Lambda表达式的函数是forEach, 因此咱们能够直接使用 return@forEach
,来跳转到此处执行下一轮循环。
在 Kotlin 中 throw 是表达式,它的类型是特殊类型 Nothing。 该类型没有值。跟C、Java中的void
意思同样。
>>> Nothing::class class 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 explicitly val 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) | - , + , ++ , -- , ! , labelDefinition @ |
|||
右手类型运算(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.kotlin data class Point(val x: Int, val y: Int) operator fun Point.unaryMinus() = Point(-x, -y)
测试代码:
package com.easy.kotlin import org.junit.Test import org.junit.runner.RunWith import 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+b 13 >>> a-b 7 >>> a/b 3 >>> a%b 1 >>> a..b 10..3 >>> b..a 3..10
+
运算符重载先用代码举个例子:
>>> ""+1 1 >>> 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.Int public final operator fun plus(other: Double): Double defined in kotlin.Int public final operator fun plus(other: Float): Float defined in kotlin.Int public final operator fun plus(other: Int): Int defined in kotlin.Int public final operator fun plus(other: Long): Long defined in kotlin.Int public final operator fun plus(other: Short): Int defined in kotlin.Int 1+"" ^
从上面的示例,咱们能够看出,在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.kotlin import org.junit.Test import org.junit.runner.RunWith import 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 >>> y 0 >>> val x = false >>> val y = x?:0 >>> y false >>> val x = "" >>> val y = x?:0 >>> y >>> val x = "abc" >>> val y = x?:0 >>> y abc
表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.kotlin import org.junit.Test import org.junit.runner.RunWith import 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.kotlin fun what(){ // 包级函数 println("This is WHAT ?") } fun main(args:Array<String>){ // 一个包下面只能有一个main函数 println("Hello,World!") } class Motorbike{ // 包里面的类 fun drive(){ println("Drive The Motorbike ...") } }
Kotlin中的目录与包的结构无需匹配,源代码文件能够在文件系统中的任意位置。
若是一个测试类PackageDemoTest跟PackageDemo在同一个包下面,咱们就不须要单独去import 类和包级函数,能够在代码里直接调用
package com.easy.kotlin import org.junit.Test import org.junit.runner.RunWith import 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.test import com.easy.kotlin.Motorbike // 导入类Motorbike import com.easy.kotlin.what // 导入包级函数what import org.junit.Test import org.junit.runner.RunWith import 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.*