人與人之間經過語言來交流溝通,互相協做。人與計算機之間怎樣「交流溝通」呢?答案是編程語言。一門語言有詞、短語、句子、文章等,對應到編程語言中就是關鍵字、標識符、表達式、源代碼文件等。一般一門編程語言的基本構成以下圖所示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.*