點擊這裏 > 去京東商城購買閱讀
點擊這裏 > 去天貓商城購買閱讀
掌握基礎,持續練習html
學習任何東西,都是一個由表及裏的過程。學習一門編程語言也同樣。對於一門編程語言來講,「表」 就是基本詞彙(關鍵字、標識符等)、句子(表達式)和語法。java
每一門編程語言的學習內容都會涉及: 運行環境、基礎數據類型(數字、字符串、數組、集合、映射字典等) 、表達式、 流程控制 、類、方法(函數)
等等,不一樣的語言會借鑑其餘的語言特性,同時也會有各自的特性。這樣咱們就能夠經過對比學習來加深理解。另外,咱們還經過大量實踐深刻理解,達到熟練使用。python
所謂「紙上得來終覺淺,絕知此事要躬行」是也。下面讓咱們開始吧。git
咱們先來舉個例子。好比說,程序員A寫了一個類叫 JSON , 程序員B也寫了一個類叫 JSON。而後,咱們在寫代碼的時候,想要同時使用這兩個類,該怎麼區分呢?程序員
一個答案是使用目錄命名空間。對應在Java中,就是使用package
來組織類,以確保類名的惟一性。上面說的例子,A寫的類放到package com.abc.fastjson
中, B寫的類就放到 package com.bbc.jackjson
中。這樣咱們在代碼中,就能夠根據命名空間來分別使用這兩個類。調用示例以下es6
com.abc.fastjson.JSON.toJSONString() com.bbc.jackjson.JSON.parseJSONObject()
在Kotlin中也沿襲了Java的 package
這個概念,同時作了一些擴展。github
咱們能夠在*.kt
文件開頭聲明package
命名空間。例如在PackageDemo.kt源代碼中,咱們按照以下方式聲明包算法
package com.easy.kotlin /** * Created by jack on 2017/6/8. */ fun what(){ println("This is WHAT ?") } class Motorbike{ fun drive(){ println("Drive The Motorbike ...") } } fun main(args:Array<String>){ println("Hello,World!") }
包的聲明處於源文件頂部。這裏,咱們聲明瞭包 com.easy.kotlin
, 裏面定義了包級函數 what()
, 同時定義了一個類 Motorbike
。另外,目錄與包的結構無需匹配:源代碼能夠在文件系統的任意位置。編程
咱們怎麼使用這些類和函數呢?咱們寫一個Junit 測試類來示例說明。json
首先,咱們使用標準Gradle工程目錄,對應的測試代碼放在test目錄下。具體目錄結構以下
咱們在測試源代碼目錄 src/test/kotlin
下面新建一個包,跟src/main/kotlin
在同一個 package com.easy.kotlin
。而後,在此包下面新建一個測試類PackageDemoTest
package com.easy.kotlin import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 /** * Created by jack on 2017/6/8. */ @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 import com.easy.kotlin.what import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 /** * Created by jack on 2017/6/8. */ @RunWith(JUnit4::class) class PackageDemoTest { @Test fun testWhat() { what() } @Test fun testDriveMotorbike() { val motorbike = Motorbike() motorbike.drive() } }
咱們使用import com.easy.kotlin.Motorbike
導入類,直接使用import com.easy.kotlin.what
導入包級函數。
上面咱們使用JUnit4測試框架。在build.gradle
中的依賴是
testCompile group: 'junit', name: 'junit', version: '4.12'
右擊測試類,點擊執行
運行結果
另外,若是咱們不定義package命令空間,則默認在根級目錄。例如直接在src/main/kotlin
源代碼目錄下面新建 DefaultPackageDemo.kt 類
import java.util.* /** * Created by jack on 2017/6/8. */ fun now() { println("Now Date is: " + Date()) } class Car{ fun drive(){ println("Drive The Car ... ") } }
若是,咱們一樣在src/test/kotlin
目錄下面新建測試類DefaultPackageDemoTest
import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 /** * Created by jack on 2017/6/8. */ @RunWith(JUnit4::class) class DefaultPackageDemoTest { @Test fun testDefaultPackage() { now() val car = Car() car.drive() } }
咱們不須要import now()
函數和 Car
類,能夠直接調用。若是咱們在 src/test/kotlin/com/easy/kotlin/PackageDemoTest.kt
測試類裏面調用now()
函數和 Car
類, 咱們按照下面的方式import
import now import Car
PackageDemoTest.kt完整測試代碼以下
package com.easy.kotlin import now import Car import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 /** * Created by jack on 2017/6/8. */ @RunWith(JUnit4::class) class PackageDemoTest { @Test fun testWhat() { what() } @Test fun testDriveMotorbike(){ val motorbike = Motorbike() motorbike.drive() } @Test fun testDefaultPackage() { now() val car = Car() car.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.*
本小節示例工程源代碼:https://github.com/EasyKotlin...
首先,在Kotlin中, 一切都是對象。因此,全部變量也都是對象(也就是說,任何變量都是根據引用類型來使用的)。
Kotlin的變量分爲 var
(可變的) 和 val
(不可變的)。
能夠簡單理解爲:
var
是可寫的,在它生命週期中能夠被屢次賦值;而
val
是隻讀的,僅能一次賦值,後面就不能被從新賦值。
代碼示例
package com.easy.kotlin import java.util.* /** * Created by jack on 2017/6/8. */ class VariableVSValue { fun declareVar() { var a = 1 a = 2 println(a) println(a::class) println(a::class.java) var x = 5 // 自動推斷出 `Int` 類型 x += 1 println("x = $x") } fun declareVal() { val b = "a" //b = "b" //編譯器會報錯: Val cannot be reassigned println(b) println(b::class) println(b::class.java) val c: Int = 1 // 當即賦值 val d = 2 // 自動推斷出 `Int` 類型 val e: Int // 若是沒有初始值類型不能省略 e = 3 // 明確賦值 println("c = $c, d = $d, e = $e") } }
咱們知道,在Java中也分可變與不可變(final)。在Kotlin中,更簡潔的、更經常使用的場景是:只要可能,儘可能在Kotlin中首選使用val
不變值。由於事實上在程序中大部分地方使用不可變的變量,可帶來不少益處,如:可預測的行爲和線程安全。
在Kotlin中大部分狀況你不須要說明你使用對象的類型,編譯器能夠直接推斷出它的類型。代碼示例
fun typeInference(){ val str = "abc" println(str) println(str is String) println(str::class) println(str::class.java) // abc // true // class java.lang.String (Kotlin reflection is not available) // class java.lang.String val d = Date() println(d) println(d is Date) println(d::class) println(d::class.java) // Fri Jun 09 00:06:33 CST 2017 // true // class java.util.Date (Kotlin reflection is not available) // class java.util.Date val bool = true println(bool) println(bool::class) println(bool::class.java) // true // boolean (Kotlin reflection is not available) // boolean val array = arrayOf(1,2,3) println(array) println(array is Array) println(array::class) println(array::class.java) // [Ljava.lang.Integer;@7b5eadd8 // true // class [Ljava.lang.Integer; (Kotlin reflection is not available) // class [Ljava.lang.Integer; }
因此,咱們只須要依據要產生的變量類型填寫var或val,其類型一般可以被推斷出來。編譯器可以檢測到其類型,自動地完成類型轉換。固然,咱們也能夠明確地指定變量類型。
可是,類型推斷不是全部的。例如,整型變量Int不能賦值Long變量。下面的代碼不能經過編譯:
fun Int2Long(){ val x:Int = 10 val y:Long = x // Type mismatch }
咱們須要顯式地調用對應的類型轉換函數進行轉換:
fun Int2Long(){ val x:Int = 10 // val y:Long = x // Type mismatch val y: Long = x.toLong() }
is
運算符進行類型檢測is
運算符檢測一個表達式是否某類型的一個實例。
若是一個不可變的局部變量或屬性已經判斷出爲某類型,那麼檢測後的分支中能夠直接看成該類型使用,無需顯式轉換:
fun getLength(obj: Any): Int? { var result = 0 if (obj is String) { // `obj` 在該條件分支內自動轉換成 `String` println(obj::class) //class java.lang.String result = obj.length println(result) } // 在離開類型檢測分支後,`obj` 仍然是 `Any` 類型 println(obj::class) // class java.lang.Object return result }
測試類以下
@Test fun testGetLength() { val obj = "abcdef" val len = variableVSValue.getLength(obj) Assert.assertTrue(len == 6) val obj2:Any = Any() variableVSValue.getLength(obj2) }
原始字符串(raw string)由三重引號(""")分隔(這個跟python同樣)。原始字符串能夠包含換行符和任何其餘字符。
package com.easy.kotlin /** * Created by jack on 2017/6/9. */ fun main(args: Array<String>) { val rawString = """ fun helloWorld(val name : String) { println("Hello, world!") } """ println(rawString) }
字符串能夠包含模板表達式。模板表達式以美圓符號($)開始。
val fooTemplateString = "$rawString has ${rawString.length} characters" println(fooTemplateString)
流程控制語句是編程語言中的核心之一。可分爲:
分支語句(
if
、when
)循環語句(
for
、while
)和跳轉語句 (
return
、break
、continue
、throw
)等。
if-else語句是控制程序流程的最基本的形式,其中else是可選的。
在 Kotlin 中,if 是一個表達式,即它會返回一個值(跟Scala同樣)。
代碼示例:
package com.easy.kotlin /** * Created by jack on 2017/6/9. */ 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 // return if (a > b) a else b } fun max1(a: Int, b: Int): Int { // 傳統用法 var max1 = a if (a < b) max1 = b return max1 } fun max2(a: Int, b: Int): Int { // With else var max2: Int if (a > b) { max2 = a } else { max2 = b } return max2 }
另外,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 表達式只有一個分支, 或者分支的結果是 Unit , 它的值就是 Unit 。
示例代碼
>>> val x = if(1==1) true >>> x kotlin.Unit >>> val y = if(1==1) true else false >>> y true
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
以上規則跟Java、C語言基本相同。
when表達式相似於 switch-case 表達式。when會對全部的分支進行檢查直到有一個條件知足。但相比switch而言,when語句要更加的強大,靈活。
Kotlin的極簡語法表達風格,使得咱們對分支檢查的代碼寫起來更加簡單直接:
fun cases(obj: Any) { when (obj) { 1 -> print("第一項") "hello" -> print("這個是字符串hello") is Long -> print("這是一個Long類型數據") !is String -> print("這不是String類型的數據") else -> print("else相似於Java中的default") } }
像 if 同樣,when 的每個分支也能夠是一個代碼塊,它的值是塊中最後的表達式的值。
若是其餘分支都不知足條件會到 else 分支(相似default)。
若是咱們有不少分支須要用相同的方式處理,則能夠把多個分支條件放在一塊兒,用逗號分隔:
fun switch(x: Any) { when (x) { -1, 0 -> print("x == -1 or x == 0") 1 -> print("x == 1") 2 -> print("x == 2") else -> { // 注意這個塊 print("x is neither 1 nor 2") } } }
咱們能夠用任意表達式(而不僅是常量)做爲分支條件
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") }
Kotlin的for循環跟現代的程序設計語言基本相同。
for 循環能夠對任何提供迭代器(iterator)的對象進行遍歷,語法以下:
for (item in collection) { print(item) }
循環體能夠是一個代碼塊。
for (i in intArray) { ... }
代碼示例
/** * For loop iterates through anything that provides an iterator. * See http://kotlinlang.org/docs/reference/control-flow.html#for-loops */ fun main(args: Array<String>) { for (arg in args) println(arg) // or println() for (i in args.indices) println(args[i]) }
若是你想要經過索引遍歷一個數組或者一個 list,你能夠這麼作:
for (i in array.indices) { print(array[i]) }
或者你能夠用庫函數 withIndex
:
for ((index, value) in array.withIndex()) { println("the element at $index is $value") }
while 和 do .. while使用方式跟C、Java語言基本一致。
代碼示例
package com.easy.kotlin /** * Created by jack on 2017/6/9. */ 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的做用域包含此處 }
### 3.5.5 break 和 continue
break
和continue
都是用來控制循環結構的,主要是用來中止循環(中斷跳轉)。
咱們在寫代碼的時候,常常會遇到在某種條件出現的時候,就直接提早終止循環。而不是等到循環條件爲false
時才終止。這個時候,咱們就可使用break
結束循環。break
用於徹底結束一個循環,直接跳出循環體,而後執行循環後面的語句。
問題場景:
打印數字1~10,只要遇到偶數,就結束打印。
代碼示例:
fun breakDemo_1() { for (i in 1..10) { println(i) if (i % 2 == 0) { break } } // break to here }
測試代碼:
@Test fun testBreakDemo_1(){ breakDemo_1() }
輸出:
1 2
continue
是隻終止本輪循環,可是還會繼續下一輪循環。能夠簡單理解爲,直接在當前語句處中斷,跳轉到循環入口,執行下一輪循環。而break
則是徹底終止循環,跳轉到循環出口。
問題場景:
打印數字0~10,可是不打印偶數。
代碼示例:
fun continueDemo() { for (i in 1..10) { if (i % 2 == 0) { continue } println(i) } }
測試代碼
@Test fun testContinueDemo() { continueDemo() }
輸出
1 3 5 7 9
在Java、C語言中,return語句使咱們再常見不過的了。雖然在Scala,Groovy這樣的語言中,函數的返回值能夠不須要顯示用return來指定,可是咱們仍然認爲,使用return的編碼風格更加容易閱讀理解。
在Kotlin中,除了表達式的值,有返回值的函數都要求顯式使用return
來返回其值。
代碼示例
fun sum(a: Int,b: Int): Int{ return a+b } fun max(a: Int, b: Int): Int { if (a > b) return a else return b}
咱們在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 error: function invocation 'invoke()' expected sumf(1,1).invoke ^ >>> 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()
函數。經過REPL的編譯錯誤提示信息,咱們也能夠看出,在Kotlin中,調用無參函數也是要加上括號()
的。
kotlin 中 return
語句會從最近的函數或匿名函數中返回,可是在Lambda表達式中遇到return,則直接返回最近的外層函數。例以下面兩個函數是不一樣的:
fun returnDemo_1() { println(" START " + ::returnDemo_1.name) val intArray = intArrayOf(1, 2, 3, 4, 5) intArray.forEach { if (it == 3) return println(it) } println(" END " + ::returnDemo_2.name) } //1 //2 fun returnDemo_2() { println(" START " + ::returnDemo_2.name) val intArray = intArrayOf(1, 2, 3, 4, 5) intArray.forEach(fun(a: Int) { if (a == 3) return println(a) }) println(" END " + ::returnDemo_2.name) } //1 //2 //4 //5
returnDemo_1
在遇到 3 時會直接返回(有點相似循環體中的break
行爲)。最後輸出
1 2
returnDemo_2
遇到 3 時會跳過它繼續執行(有點相似循環體中的continue
行爲)。最後輸出
1 2 4 5
在returnDemo_2
中,咱們用一個匿名函數替代 lambda 表達式。 匿名函數內部的 return 語句將從該匿名函數自身返回。
在Kotlin中,這是匿名函數和 lambda 表達式行爲不一致的地方。固然,爲了顯式的指明 return
返回的地址,爲此 kotlin 還提供了 @Label
(標籤) 來控制返回語句,且看下節分解。
在 Kotlin 中任何表達式均可以用標籤(label)來標記。 標籤的格式爲標識符後跟 @
符號,例如:abc@
、jarOfLove@
都是有效的標籤。咱們能夠用Label標籤來控制 return
、break
或 continue
的跳轉(jump)行爲。
Kotlin 的函數是能夠被嵌套的。它有函數字面量、局部函數等。 有了標籤限制的 return,咱們就能夠從外層函數返回了。例如,從 lambda 表達式中返回,returnDemo_2()
咱們能夠顯示指定lambda 表達式中的return地址是其入口處。
代碼示例:
fun returnDemo_3() { println(" START " + ::returnDemo_3.name) val intArray = intArrayOf(1, 2, 3, 4, 5) intArray.forEach here@ { if (it == 3) return@here // 指令跳轉到 lambda 表達式標籤 here@ 處。繼續下一個it=4的遍歷循環 println(it) } println(" END " + ::returnDemo_3.name) } //1 //2 //4 //5
咱們在 lambda 表達式開頭處添加了標籤here@
,咱們能夠這麼理解:該標籤至關因而記錄了Lambda表達式的指令執行入口地址, 而後在表達式內部咱們使用return@here
來跳轉至Lambda表達式該地址處。
另外,咱們也可使用隱式標籤更方便。 該標籤與接收該 lambda 的函數同名。
代碼示例
fun returnDemo_4() { println(" START " + ::returnDemo_4.name) val intArray = intArrayOf(1, 2, 3, 4, 5) intArray.forEach { if (it == 3) return@forEach // 從 lambda 表達式 @forEach 中返回。 println(it) } println(" END " + ::returnDemo_4.name) }
接收該Lambda表達式的函數是forEach, 因此咱們能夠直接使用 return@forEach
,來跳轉到此處執行下一輪循環。
一般當咱們在循環體中使用break,是跳出最近外層的循環:
fun breakDemo_1() { println("--------------- breakDemo_1 ---------------") for (outer in 1..5) { println("outer=" + outer) for (inner in 1..10) { println("inner=" + inner) if (inner % 2 == 0) { break } } } }
輸出
--------------- breakDemo_1 --------------- outer=1 inner=1 inner=2 outer=2 inner=1 inner=2 outer=3 inner=1 inner=2 outer=4 inner=1 inner=2 outer=5 inner=1 inner=2
當咱們想直接跳轉到外層for循環,這個時候咱們就可使用標籤了。
代碼示例
fun breakDemo_2() { println("--------------- breakDemo_2 ---------------") outer@ for (outer in 1..5) for (inner in 1..10) { println("inner=" + inner) println("outer=" + outer) if (inner % 2 == 0) { break@outer } } }
輸出
--------------- breakDemo_2 --------------- inner=1 outer=1 inner=2 outer=1
有時候,爲了代碼可讀性,咱們能夠用標籤來顯式地指出循環體的跳轉地址,好比說在breakDemo_1()
中,咱們能夠用標籤來指明內層循環的跳轉地址:
fun breakDemo_3() { println("--------------- breakDemo_3 ---------------") for (outer in 1..5) inner@ for (inner in 1..10) { println("inner=" + inner) println("outer=" + outer) if (inner % 2 == 0) { break@inner } } }
在 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類型,沒有任何值,因此沒法當作參數傳給函數:
>>> println(ex) error: overload resolution ambiguity: @InlineOnly public inline fun println(message: Any?): Unit defined in kotlin.io @InlineOnly public inline fun println(message: Boolean): Unit defined in kotlin.io @InlineOnly public inline fun println(message: Byte): Unit defined in kotlin.io @InlineOnly public inline fun println(message: Char): Unit defined in kotlin.io @InlineOnly public inline fun println(message: CharArray): Unit defined in kotlin.io @InlineOnly public inline fun println(message: Double): Unit defined in kotlin.io @InlineOnly public inline fun println(message: Float): Unit defined in kotlin.io @InlineOnly public inline fun println(message: Int): Unit defined in kotlin.io @InlineOnly public inline fun println(message: Long): Unit defined in kotlin.io @InlineOnly public inline fun println(message: Short): Unit defined in kotlin.io println(ex) ^ >>> ex exception: org.jetbrains.kotlin.codegen.CompilationException: Back-end (JVM) Internal error: Unregistered script: class Line62 Cause: Unregistered script: class Line62 File being compiled and position: (1,1) in /line64.kts PsiElement: ex The root cause was thrown at: ScriptContext.java:86 ...
正如 Java 和 JavaScript,Kotlin 支持行註釋及塊註釋。
// 這是一個行註釋 /* 這是一個多行的 塊註釋。 */
與 Java 不一樣的是,Kotlin 的塊註釋能夠嵌套。就是說,你能夠這樣註釋:
/** * hhhh * /** * fff * /** * ggggg * */ * */ * * abc * */ fun main(args:Array<String>){ val f = Functions() println(f.fvoid1()) println(f.fvoid2()) println(f.sum1(1,1)) println(f.sum2(1,1)) }
咱們知道,任何一門編程語言都會有一些本身專用的關鍵字、符號以及規定的語法規則等等。程序員們使用這些基礎詞彙和語法規則來表達算法步驟,也就是寫代碼的過程。
詞法分析是編譯器對源碼進行編譯的基礎步驟之一。詞法分析是將源程序讀入的字符序列,按照必定的規則轉換成詞法單元(Token)序列的過程。詞法單元是語言中具備獨立意義的最小單元,包括修飾符、關鍵字、常數、運算符、邊界符等等。
在Kotlin源碼工程中的kotlin/grammar/src/modifiers.grm文件中,描述了Kotlin語言的修飾符,咱們在此做簡要註釋說明:
/** ## Modifiers */ modifiers : (modifier | annotations)* ; typeModifiers : (suspendModifier | annotations)* ; modifier : classModifier : accessModifier : varianceAnnotation : memberModifier : parameterModifier : typeParameterModifier : functionModifier : propertyModifier ; classModifier 類修飾符 : "abstract" 抽象類 : "final" 不可被繼承final類 : "enum" 枚舉類 : "open" 可繼承open類 : "annotation" 註解類 : "sealed" 密封類 : "data" 數據類 ; memberModifier : "override" 重寫函數 : "open" 可被重寫 : "final" 不可被重寫 : "abstract" 抽象函數 : "lateinit" 後期初始化 ; accessModifier 訪問權限控制, 默認是public : "private" : "protected" : "public" : "internal" 整個模塊內(模塊(module)是指一塊兒編譯的一組 Kotlin 源代碼文件: 例如,一個 IntelliJ IDEA 模塊,一個 Maven 工程, 或 Gradle 工程,經過 Ant 任務的一次調用編譯的一組文件等)可訪問 ; varianceAnnotation 泛型可變性 : "in" : "out" ; parameterModifier : "noinline" : "crossinline" : "vararg" 變長參數 ; typeParameterModifier : "reified" ; functionModifier : "tailrec" 尾遞歸 : "operator" : "infix" : "inline" : "external" : suspendModifier ; propertyModifier : "const" ; suspendModifier : "suspend" ;
這些修飾符的完整定義,在kotlin/compiler/frontend/src/org/jetbrains/kotlin/lexer/KtTokens.java源碼中:
## 3.7.2 關鍵字(保留字) 其中,對應的關鍵字以下:
KtKeywordToken PACKAGE_KEYWORD = KtKeywordToken.keyword("package"); KtKeywordToken AS_KEYWORD = KtKeywordToken.keyword("as"); KtKeywordToken TYPE_ALIAS_KEYWORD = KtKeywordToken.keyword("typealias"); KtKeywordToken CLASS_KEYWORD = KtKeywordToken.keyword("class"); KtKeywordToken THIS_KEYWORD = KtKeywordToken.keyword("this"); KtKeywordToken SUPER_KEYWORD = KtKeywordToken.keyword("super"); KtKeywordToken VAL_KEYWORD = KtKeywordToken.keyword("val"); KtKeywordToken VAR_KEYWORD = KtKeywordToken.keyword("var"); KtKeywordToken FUN_KEYWORD = KtKeywordToken.keyword("fun"); KtKeywordToken FOR_KEYWORD = KtKeywordToken.keyword("for"); KtKeywordToken NULL_KEYWORD = KtKeywordToken.keyword("null"); KtKeywordToken TRUE_KEYWORD = KtKeywordToken.keyword("true"); KtKeywordToken FALSE_KEYWORD = KtKeywordToken.keyword("false"); KtKeywordToken IS_KEYWORD = KtKeywordToken.keyword("is"); KtModifierKeywordToken IN_KEYWORD = KtModifierKeywordToken.keywordModifier("in"); KtKeywordToken THROW_KEYWORD = KtKeywordToken.keyword("throw"); KtKeywordToken RETURN_KEYWORD = KtKeywordToken.keyword("return"); KtKeywordToken BREAK_KEYWORD = KtKeywordToken.keyword("break"); KtKeywordToken CONTINUE_KEYWORD = KtKeywordToken.keyword("continue"); KtKeywordToken OBJECT_KEYWORD = KtKeywordToken.keyword("object"); KtKeywordToken IF_KEYWORD = KtKeywordToken.keyword("if"); KtKeywordToken TRY_KEYWORD = KtKeywordToken.keyword("try"); KtKeywordToken ELSE_KEYWORD = KtKeywordToken.keyword("else"); KtKeywordToken WHILE_KEYWORD = KtKeywordToken.keyword("while"); KtKeywordToken DO_KEYWORD = KtKeywordToken.keyword("do"); KtKeywordToken WHEN_KEYWORD = KtKeywordToken.keyword("when"); KtKeywordToken INTERFACE_KEYWORD = KtKeywordToken.keyword("interface"); // Reserved for future use: KtKeywordToken TYPEOF_KEYWORD = KtKeywordToken.keyword("typeof");
...
KtKeywordToken FILE_KEYWORD = KtKeywordToken.softKeyword("file"); KtKeywordToken FIELD_KEYWORD = KtKeywordToken.softKeyword("field"); KtKeywordToken PROPERTY_KEYWORD = KtKeywordToken.softKeyword("property"); KtKeywordToken RECEIVER_KEYWORD = KtKeywordToken.softKeyword("receiver"); KtKeywordToken PARAM_KEYWORD = KtKeywordToken.softKeyword("param"); KtKeywordToken SETPARAM_KEYWORD = KtKeywordToken.softKeyword("setparam"); KtKeywordToken DELEGATE_KEYWORD = KtKeywordToken.softKeyword("delegate"); KtKeywordToken IMPORT_KEYWORD = KtKeywordToken.softKeyword("import"); KtKeywordToken WHERE_KEYWORD = KtKeywordToken.softKeyword("where"); KtKeywordToken BY_KEYWORD = KtKeywordToken.softKeyword("by"); KtKeywordToken GET_KEYWORD = KtKeywordToken.softKeyword("get"); KtKeywordToken SET_KEYWORD = KtKeywordToken.softKeyword("set"); KtKeywordToken CONSTRUCTOR_KEYWORD = KtKeywordToken.softKeyword("constructor"); KtKeywordToken INIT_KEYWORD = KtKeywordToken.softKeyword("init"); KtModifierKeywordToken ABSTRACT_KEYWORD = KtModifierKeywordToken.softKeywordModifier("abstract"); KtModifierKeywordToken ENUM_KEYWORD = KtModifierKeywordToken.softKeywordModifier("enum"); KtModifierKeywordToken OPEN_KEYWORD = KtModifierKeywordToken.softKeywordModifier("open"); KtModifierKeywordToken INNER_KEYWORD = KtModifierKeywordToken.softKeywordModifier("inner"); KtModifierKeywordToken OVERRIDE_KEYWORD = KtModifierKeywordToken.softKeywordModifier("override"); KtModifierKeywordToken PRIVATE_KEYWORD = KtModifierKeywordToken.softKeywordModifier("private"); KtModifierKeywordToken PUBLIC_KEYWORD = KtModifierKeywordToken.softKeywordModifier("public"); KtModifierKeywordToken INTERNAL_KEYWORD = KtModifierKeywordToken.softKeywordModifier("internal"); KtModifierKeywordToken PROTECTED_KEYWORD = KtModifierKeywordToken.softKeywordModifier("protected"); KtKeywordToken CATCH_KEYWORD = KtKeywordToken.softKeyword("catch"); KtModifierKeywordToken OUT_KEYWORD = KtModifierKeywordToken.softKeywordModifier("out"); KtModifierKeywordToken VARARG_KEYWORD = KtModifierKeywordToken.softKeywordModifier("vararg"); KtModifierKeywordToken REIFIED_KEYWORD = KtModifierKeywordToken.softKeywordModifier("reified"); KtKeywordToken DYNAMIC_KEYWORD = KtKeywordToken.softKeyword("dynamic"); KtModifierKeywordToken COMPANION_KEYWORD = KtModifierKeywordToken.softKeywordModifier("companion"); KtModifierKeywordToken SEALED_KEYWORD = KtModifierKeywordToken.softKeywordModifier("sealed"); KtModifierKeywordToken DEFAULT_VISIBILITY_KEYWORD = PUBLIC_KEYWORD; KtKeywordToken FINALLY_KEYWORD = KtKeywordToken.softKeyword("finally"); KtModifierKeywordToken FINAL_KEYWORD = KtModifierKeywordToken.softKeywordModifier("final"); KtModifierKeywordToken LATEINIT_KEYWORD = KtModifierKeywordToken.softKeywordModifier("lateinit"); KtModifierKeywordToken DATA_KEYWORD = KtModifierKeywordToken.softKeywordModifier("data"); KtModifierKeywordToken INLINE_KEYWORD = KtModifierKeywordToken.softKeywordModifier("inline"); KtModifierKeywordToken NOINLINE_KEYWORD = KtModifierKeywordToken.softKeywordModifier("noinline"); KtModifierKeywordToken TAILREC_KEYWORD = KtModifierKeywordToken.softKeywordModifier("tailrec"); KtModifierKeywordToken EXTERNAL_KEYWORD = KtModifierKeywordToken.softKeywordModifier("external"); KtModifierKeywordToken ANNOTATION_KEYWORD = KtModifierKeywordToken.softKeywordModifier("annotation"); KtModifierKeywordToken CROSSINLINE_KEYWORD = KtModifierKeywordToken.softKeywordModifier("crossinline"); KtModifierKeywordToken OPERATOR_KEYWORD = KtModifierKeywordToken.softKeywordModifier("operator"); KtModifierKeywordToken INFIX_KEYWORD = KtModifierKeywordToken.softKeywordModifier("infix"); KtModifierKeywordToken CONST_KEYWORD = KtModifierKeywordToken.softKeywordModifier("const"); KtModifierKeywordToken SUSPEND_KEYWORD = KtModifierKeywordToken.softKeywordModifier("suspend"); KtModifierKeywordToken HEADER_KEYWORD = KtModifierKeywordToken.softKeywordModifier("header"); KtModifierKeywordToken IMPL_KEYWORD = KtModifierKeywordToken.softKeywordModifier("impl");
#### `this` 關鍵字 `this`關鍵字持有當前對象的引用。咱們可使用`this`來引用變量或者成員函數,也可使用`return this`,來返回某個類的引用。 代碼示例
class ThisDemo {
val thisis = "THIS IS" fun whatIsThis(): ThisDemo { println(this.thisis) //引用變量 this.howIsThis()// 引用成員函數 return this // 返回此類的引用 } fun howIsThis(){ println("HOW IS THIS ?") }
}
測試代碼
@Test fun testThisDemo(){ val demo = ThisDemo() println(demo.whatIsThis()) }
輸出
THIS IS
HOW IS THIS ?
com.easy.kotlin.ThisDemo@475232fc
在類的成員中,this 指向的是該類的當前對象。 在擴展函數或者帶接收者的函數字面值中, this 表示在點左側傳遞的 接收者參數。 代碼示例:
val sum = fun Int.(x:Int):Int = this + x
sum
kotlin.Int.(kotlin.Int) -> kotlin.Int
1.sum(1)
2
val concat = fun String.(x:Any) = this + x
"abc".concat(123)
abc123
"abc".concat(true)
abctrue
若是 this 沒有限定符,它指的是最內層的包含它的做用域。若是咱們想要引用其餘做用域中的 this,可使用 this@label 標籤。 代碼示例:
class Outer {
val oh = "Oh!" inner class Inner { fun m() { val outer = this@Outer val inner = this@Inner val pthis = this println("outer=" + outer) println("inner=" + inner) println("pthis=" + pthis) println(this@Outer.oh) val fun1 = hello@ fun String.() { val d1 = this // fun1 的接收者 println("d1" + d1) } val fun2 = { s: String -> val d2 = this println("d2=" + d2) } "abc".fun1() fun2 } }
}
測試代碼:
@Test fun testThisKeyWord() { val outer = Outer() outer.Inner().m() }
輸出
outer=com.easy.kotlin.Outer@5114e183
inner=com.easy.kotlin.Outer$Inner@5aa8ac7f
pthis=com.easy.kotlin.Outer$Inner@5aa8ac7f
Oh!
d1abc
#### super 關鍵字 super關鍵字持有指向其父類的引用。 代碼示例:
open class Father {
open val firstName = "Chen" open val lastName = "Jason" fun ff() { println("FFF") }
}
class Son : Father {
override var firstName = super.firstName override var lastName = "Jack" constructor(lastName: String) { this.lastName = lastName } fun love() { super.ff() // 調用父類方法 println(super.firstName + " " + super.lastName + " Love " + this.firstName + " " + this.lastName) }
}
測試代碼
@Test fun testSuperKeyWord() { val son = Son("Harry") son.love() }
輸出
FFF
Chen Jason Love Chen Harry
### 3.7.3 操做符和操做符的重載 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", ",");
### 3.7.4 操做符優先級(Precedence) | 優先級 | 標題 | 符號 | |------------|-------|---------| | 最高 | 後綴(Postfix )| `++`, `--`, `.`, `?.`, `?` | | | 前綴(Prefix) | `-`, `+`, `++`, `--`, `!`, [`labelDefinition`](#IDENTIFIER)`@` | | | 右手類型運算(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)| `ll`| | 最低 | 賦值(Assignment) | `=`, `+=`, `-=`, `*=`, `/=`, `%=` | 注:Markdown表格語法:`ll`是`||`。 爲實現這些的操做符,Kotlin爲二元操做符左側的類型和一元操做符的參數類型,提供了相應的函數或擴展函數。 例如在kotlin/core/builtins/native/kotlin/Primitives.kt代碼中,對基本類型Int的操做符的實現代碼以下
public class Int private constructor() : Number(), Comparable<Int> {
... ...
}
從源代碼咱們能夠看出,重載操做符的函數須要用 `operator` 修飾符標記。中綴操做符的函數使用`infix`修飾符標記。 ### 3.7.5 一元操做符(unary operation) #### 前綴操做符 | 表達式 | 翻譯爲 | |------------|---------------| | `+a` | `a.unaryPlus()` | | `-a` | `a.unaryMinus()` | | `!a` | `a.not()` | 例如,當編譯器處理表達式 `+a` 時,它將執行如下步驟: * 肯定 `a` 的類型,令其爲 `T`。 * 爲接收者 `T` 查找一個帶有 `operator` 修飾符的無參函數 `unaryPlus()`,即成員函數或擴展函數。 * 若是函數不存在或不明確,則致使編譯錯誤。 * 若是函數存在且其返回類型爲 `R`,那就表達式 `+a` 具備類型 `R`。 編譯器對這些操做以及全部其餘操做都針對基本類型作了優化,不會引入函數調用的開銷。 如下是如何重載一元減運算符的示例:
package com.easy.kotlin
/**
class OperatorDemo {
}
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) }
}
#### 遞增和遞減 | 表達式 | 翻譯爲 | |------------|---------------| | `a++` | `a.inc()` 返回值是`a` | | `a--` | `a.dec()` 返回值是`a`| | `++a` | `a.inc()` 返回值是`a+1`| | `--a` | `a.dec()` 返回值是`a-1` | `inc()` 和 `dec()` 函數必須返回一個值,它用於賦值給使用 `++` 或 `--` 操做的變量。 編譯器執行如下步驟來解析*後綴*形式的操做符,例如 `a++`: * 肯定 `a` 的類型,令其爲 `T`。 * 查找一個適用於類型爲 `T` 的接收者的、帶有 `operator` 修飾符的無參數函數 `inc()`。 * 檢查函數的返回類型是 `T` 的子類型。 計算表達式的步驟是: * 把 `a` 的初始值存儲到臨時存儲 `a_` 中 * 把 `a.inc()` 結果賦值給 `a` * 把 `a_` 做爲表達式的結果返回 ( `a--` 同理分析)。 對於*前綴*形式 `++a` 和 `--a` 解析步驟相似,可是返回值是取的新值來返回: * 把 `a.inc()` 結果賦值給 `a` * 把 `a` 的新值`a+1`做爲表達式結果返回 ( `--a` 同理分析)。 ### 3.7.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`操做符 | 表達式 | 翻譯爲 | | -----------|-------------- | | `a in b` | `b.contains(a)` | | `a !in b` | `!b.contains(a)` | #### 索引訪問操做符 | 表達式 | 翻譯爲 | | -------|-------------- | | `a[i]` | `a.get(i)` | | `a[i] = b` | `a.set(i, b)` | 方括號轉換爲調用帶有適當數量參數的 `get` 和 `set`。 #### 調用操做符 | 表達式 | 翻譯爲 | |--------|---------------| | `a()` | `a.invoke()` | | `a(i)` | `a.invoke(i)` | 圓括號轉換爲調用帶有適當數量參數的 `invoke`。 #### 計算並賦值 | 表達式 | 翻譯爲 | |------------|---------------| | `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()` 判斷) | 表達式 | 翻譯爲 | |------------|---------------| | `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` *注意*:`===` 和 `!==`不可重載。 #### Elvis 操做符 `?:` 在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
yval x = "abc"
val y = x?:0
y
abc
#### 比較操做符 | 表達式 | 翻譯爲 | |--------|---------------| | `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函數自定義中綴操做符 咱們能夠經過自定義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)
## 3.8 函數擴展和屬性擴展(Extensions) Kotlin 支持 擴展函數 和 擴展屬性。其可以擴展一個類的新功能而無需繼承該類或使用像裝飾者這樣的設計模式等。 大多數時候咱們在頂層定義擴展,即直接在包裏:
package com.easy.kotlin
/**
val <T> List<T>.lastIndex: Int get() = size - 1
fun String.notEmpty(): Boolean {
return !this.isEmpty()
}
這樣咱們就能夠在整個包裏使用這些擴展。 要使用其餘包的擴展,咱們須要在調用方導入它:
package com.example.usage
import foo.bar.goo // 導入全部名爲「goo」的擴展
// 或者
import foo.bar.* // 從「foo.bar」導入一切
fun usage(baz: Baz) {
baz.goo()
}
### 3.8.1 擴展函數 聲明一個擴展函數,咱們須要用_被擴展的類型_來做爲前綴。 好比說,咱們不喜歡相似下面的_雙重否認_式的邏輯判斷(繞腦子):
!"123".isEmpty()
true
咱們就能夠爲`String`類型擴展一個`notEmpty()`函數:
fun String.notEmpty():Boolean{
... return !this.isEmpty()
... }
"".notEmpty()
false
"123".notEmpty()
true
下面代碼爲 `MutableList<Int>` 添加一個`swap` 函數:
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // this對應該列表 this[index1] = this[index2] this[index2] = tmp
}
這個 `this` 關鍵字在擴展函數內部對應到接收者對象(傳過來的在點`.`符號前的對象) 如今,咱們對任意 `MutableList<Int>` 調用該函數了。 固然,這個函數對任何 `MutableList<T>` 起做用,咱們能夠泛化它:
fun <T> MutableList<T>.mswap(index1: Int, index2: Int) { val tmp = this[index1] // 「this」對應該列表 this[index1] = this[index2] this[index2] = tmp }
爲了在接收者類型表達式中使用泛型,咱們要在函數名前聲明泛型參數。 完整代碼示例
package com.easy.kotlin
/**
val <T> List<T>.lastIndex: Int get() = size - 1
fun String.notEmpty(): Boolean {
return !this.isEmpty()
}
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // this對應該列表m this[index1] = this[index2] this[index2] = tmp
}
fun <T> MutableList<T>.mswap(index1: Int, index2: Int) {
val tmp = this[index1] // 「this」對應該列表 this[index1] = this[index2] this[index2] = tmp
}
class ExtensionsDemo {
fun useExtensions() { val a = "abc" println(a.notEmpty())//true val mList = mutableListOf<Int>(1, 2, 3, 4, 5) println("Before Swap:") println(mList)//[1, 2, 3, 4, 5] mList.swap(0, mList.size - 1) println("After Swap:") println(mList)//[5, 2, 3, 4, 1]
val mmList = mutableListOf<String>("a12", "b34", "c56", "d78") println("Before Swap:") println(mmList)//[a12, b34, c56, d78] mmList.mswap(1, 2) println("After Swap:") println(mmList)//[a12, c56, b34, d78]
val mmmList = mutableListOf<Int>(100, 200, 300, 400, 500) println("Before Swap:") println(mmmList) mmmList.mswap(0, mmmList.lastIndex) println("After Swap:") println(mmmList) }
class Inner { fun useExtensions() { val mmmList = mutableListOf<Int>(100, 200, 300, 400, 500) println(mmmList.lastIndex) } }
}
測試代碼
package com.easy.kotlin
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
/**
@RunWith(JUnit4::class)
class ExtensionsDemoTest {
@Test fun testExtensionsDemo() { val demo = ExtensionsDemo() demo.useExtensions() }
}
擴展不是真正的修改他們所擴展的類。咱們定義一個擴展,其實並無在一個類中插入新函數,僅僅是經過該類型的變量,用點`.`表達式去調用這個新函數。 ### 3.8.2 擴展屬性 和函數相似,Kotlin 支持擴展屬性:
val <T> List<T>.lastIndex: Int
get() = size - 1
注意:因爲擴展沒有實際的將成員插入類中,所以對擴展的屬性來講,它的行爲只能由顯式提供的 getters/setters 定義。 代碼示例:
package com.easy.kotlin
/**
val <T> List<T>.lastIndex: Int get() = size - 1
咱們能夠直接使用包`com.easy.kotlin`中擴展的屬性`lastIndex` :  ## 3.9 空指針安全(Null-safety) 咱們寫代碼的時候知道,在Java中NPE(NullPointerExceptions)是一件成程序員幾近崩潰的事情。不少時候,雖然費盡體力腦力,仍然防不勝防。 之前,當咱們不肯定一個DTO類中的字段是否已初始化時,可使用@Nullable和@NotNull註解來聲明,但功能頗有限。 如今好了,Kotlin在編譯器級別,把你以前在Java中須要寫的null check代碼完成了。 可是,當咱們的代碼 * 顯式調用 `throw NullPointerException()` * 使用了 `!!` 操做符 * 調用的外部 Java 代碼有NPE * 對於初始化,有一些數據不一致(如一個未初始化的 `this` 用於構造函數的某個地方) 也可能會發生NPE。 在Kotlin中`null`等同於空指針。咱們來經過代碼來看一下`null`的有趣的特性: 首先,一個非空引用不能直接賦值爲`null` :
var a="abc"
a=null
error: null can not be a value of a non-null type String
a=null
^
var one=1
one=null
error: null can not be a value of a non-null type Int
one=null
^
var arrayInts = intArrayOf(1,2,3)
arrayInts=null
error: null can not be a value of a non-null type IntArray
arrayInts=null
^
這樣,咱們就能夠放心地調用 `a` 的方法或者訪問它的屬性,不會致使 `NPE`:
val a="abc"
a.length
3
若是要容許爲空,咱們能夠在變量的類型後面加個問號`?`聲明一個變量爲可空的:
var a:String?="abc"
a=null
var one:Int?=1
one=null
var arrayInts:IntArray?=intArrayOf(1,2,3)
arrayInts=null
arrayInts
null
若是咱們聲明瞭一個可空`String?`類型變量`na` ,而後直接調用`length`屬性,這將是不安全的。編譯器會直接報錯:
var na:String?="abc"
na=null
na.length
error: only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?
na.length
^
咱們使用安全調用`?.` 和 非空斷言調用 `!!.`
na?.length
null
na!!.length
kotlin.KotlinNullPointerException
咱們能夠看出,代碼返回了`null` 和 `kotlin.KotlinNullPointerException`。 安全調用在鏈式調用中頗有用。在調用鏈中若是任意一個屬性(環節)爲空,這個鏈式調用就會安全返回 null。 若是要只對非空值執行某個操做,安全調用操做符能夠與 `let` (以調用者的值做爲參數來執行指定的函數塊,並返回其結果)一塊兒使用:
val listWithNulls: List<String?> = listOf("A", "B",null)
listWithNulls
[A, B, null]
listWithNulls.forEach{
... it?.let{println(it)}
... }
A
B
參考資料 === 1.https://www.kotlincn.net/docs/reference/grammar.html