Kotlin官方參考整理——01.開始
前言
這是前段時間學習Kotlin官方參考(中 / 英)時所做的筆記,相比官方參考,這些筆記:
- 為了方便根據筆記去查閱官方參考,筆記的整體結構與官方參考基本保持一致
- 對官方參考中一些零散分佈的知識點進行了整合
- 對官方參考中個別講的比較籠統的知識點做了詳細展開
- 將官方參考中一些比較晦澀的知識點,使用個人認為更容易理解的方式進行了闡述
- 筆記並未100%覆蓋官方參考中的內容
這份筆記基本涵蓋了Kotlin的大部分知識點,可以作為 Kotlin快速入門 或是 官方參考的導讀 來使用。
另外,因為個人也正處於學習Kotlin的過程中,因此筆記中的理解和論述難免會有錯誤,希望讀者朋友不吝指正,大家共同學習進步。
1開始
1.1 基本語法
1.1.1 定義包
package foo.bar
//kotlin中語句結尾不需要寫分號
var aaa = 1
fun baz() {
}
class Goo {
}
……
包的宣告應處於原始檔頂部。不同於Java,Kotlin中包名可以不用與原始檔所在的目錄結構對應,也就是說原始檔可以隨便移動而不用修改其包名。
Kotlin原始檔的結構與C++類似,即變數、函式、類可以同級(如上面程式碼所示),而Java中,變數和函式都在類中。
原始檔所有內容都包含在宣告的包內。所以上例中 baz() 的全名是 foo.bar.baz 、Goo 的全名是foo.bar.Goo。如果沒有指明包,該檔案的內容屬於無名字的預設包。
1.1.2 訪問許可權修飾符(又稱為可見性修飾符)
Kotlin中變數、函式、類有三種位置,分別是:
- 頂層位置:即在原始檔中頂層
- 類中
- 區域性位置:即在程式碼塊中或函式中
Kotlin中有四種可見性修飾符,分別為public(預設)、internal、protected、private。可以修飾頂層位置和類中的變數、函式、類,不能修飾區域性位置的變數、函式、類。
頂層位置的可見性修飾符:
- public:隨處可見,預設。
- internal:本模組內可見。
- protected:不可用於頂層位置的變數、函式、類。
- private:本檔案內可見。
類中的可見性修飾符:
- public:能見到類的任何人都可見。
- internal:在本模組內,能見到類的任何人都可見。
- protected:本類和子類中可見。
- private:本類中可見。
關於可見性的應用,舉一個簡單直觀的例子:
/*
*檔案File1.kt
*/
package cn.szx.kotlindemo
//預設可見性為public
fun function1(){
print("function1 in File1")
}
//---------------------------------------------------------------------------------------
/*
*檔案File2.kt
*/
package cn.szx.kotlindemo
fun function2(){
//File2中可以訪問File1中定義的函式function1()
//並且因為File2和File1在同一個包內,因此訪問function1()時不用加包名字首
function1()
}
//編譯報錯,不能在同一個包內再定義一個function1()了
//因為此處的function1()和File1中的function1(),其全名都是cn.szx.kotlindemo.function1()
//系統無法區分二者
//fun function1(){
//
//}
//function1(a:Int)與File1中的function1()形參列表不同,可以區分,因此可以定義
fun function1(a:Int){
print("function1 in File2"+a)
}
//---------------------------------------------------------------------------------------
/*
*檔案File3.kt
*/
package cn.szx.kotlindemo2
//import cn.szx.kotlindemo.function1
fun function3(){
//File3和File1不在同一個包內,因此訪問function1()時需要加包名字首
cn.szx.kotlindemo.function1()
//使用“import cn.szx.kotlindemo.function1”將function1匯入後,則可以省略字首
//“import cn.szx.kotlindemo.function1”代表將cn.szx.kotlindemo包內所有名為function1的函式都匯入了
//function1()
}
//允許定義function1()。與File1中的function1()的包名字首不同,因此係統可以區分
fun function1(){
print("function1 in File3")
}
1.1.3 定義函式
基本規則:使用fun關鍵字來定義函式,形參的定義格式為“形參名:形參型別”。
//帶有兩個 Int 引數、返回 Int 的函式
fun sum(a: Int, b: Int): Int {
return a + b
}
//將表示式作為函式體、返回值型別⾃動推斷的函式
fun sum(a: Int, b: Int) = a + b
//沒有返回值的函式
fun printSum(a: Int, b: Int) {
println("sum of $a and $b is ${a + b}")
}
1.1.4 定義變數
注意:abstract只能修飾成員位置(即類中)的變數和函式,不能修飾頂層位置和區域性位置的變數和函式。
定義變數的基本格式:
var a:Int = 1//可變變數
val b:Int = 2//不可變變數
var c = 3//根據等號右邊的值自動推斷c的型別
var d:Int//定義時不進行初始化,則Int不能省略
var定義的是可變變數,即一般的變數,val定義的是不可變變數,即符號常量,類似於Java中的final變數或者C++中的const變數。
此外,處於不同位置的變數,其初始化要求是不同的:
- 頂層變數必須進行初始化。(除非其沒有幕後欄位,詳見《03類和物件1.md》->“屬性”)
- 類中的變數要麼進行初始化(可以直接後接等號進行初始化,也可以在建構函式或初始化塊(init塊)中初始化),要麼宣告為abstract。(除非其沒有幕後欄位,詳見《03類和物件1.md》->“屬性”)
- 區域性變數可以不初始化。
package cn.szx.kotlindemo
var a: Int = 1 //頂層變數,必須進行初始化
private class Test{
//abstract var a:Int //類中的變數,宣告為抽象
var b: Int = 2 //類中的變數,初始化
}
private fun baz() {
var a: Int //區域性變數,可以不初始化
}
1.1.5 字串
字串用String型別表示。字串是不可變的。字串的元素——字元可以使用索引運算子訪問: s[i] 。可以用for迴圈迭代字串:
for (c in str) {
println(c)
}
Kotlin有兩種型別的字串字面值: 轉義字串和原生字串。
轉義字串支援轉義字元,很像Java中的字串:val s = "Hello, world!\n"
原生字串使用三個引號( “”” )分界符括起來,不支援轉義,任何字元都會被原樣展示(包括換行、空格、tab等):
val text = """
for (c in "foo")
print(c)
"""
字串模板
字串可以包含模板表示式,使用時會用模板表示式的值來替換掉模板表示式。模板表示式以美元符( $ )開頭,由一個簡單的名字構成,如:
val i = 10
val s = "i = $i" // s為"i = 10"
或者用花括號擴起來的任意表達式:
val s = "abc"
val str = "$s.length is ${s.length}" // str為"abc.length is 3"
轉義字串和原生字串都支援模板表示式,那如果我們想要的就是$怎麼辦呢——使用${'$'}
1.1.6 條件表示式
Java中的很多語句,在kotlin中都既是語句,也是表示式(即本身可以代表一個值,可以放在等號右邊)。例如if表示式:
//if的分支可以是程式碼塊,最後的表示式的值作為該塊的值
val max = if (a > b) {
print("Choose a")
a
} else {
print("Choose b")
b
}
//也可以像下面這樣,類似於Java中的?:表示式
val max = if (a > b) a else b
1.1.7 空安全
1.1.7.1 非空型別與可空型別
預設情況下,所有型別都是非空的,即我們告訴編譯系統,該型別變數的值不可能是null,因此我們也不能將null賦值給該型別的變數:
var a: String = null //非空型別取值不能為null,編譯報錯
var b: String = "hello"
val l = b.length //因為b是非空型別的,所以b.length必然是安全的,不可能報出NPE(Null Pointer Exception)
在型別關鍵字後加上?
,那麼就得到了一個可空型別,如String?
:
var b: String? = null //可空型別,取值可以為空
val l = b.length //編譯報錯,因為b是可空型別,這樣呼叫是不安全的
那麼我們想要獲得可空的b的length屬性該怎麼辦呢?有兩種方法:
(1) 先進行空檢測
val l = if (b != null) b.length else -1
//也支援更復雜(更智慧的檢測)
if (b != null && b.length > 0) {
print("String of length ${b.length}")
} else {
print("Empty string")
}
(2) 使用安全呼叫操作符“?.”
val l = b?.length
如果b非空,就返回b.length,否則返回null,這個表示式的型別是Int?。
安全呼叫操作符在鏈式呼叫中很有用,例如:
bob?.department?.head?.name
只要任何一個環節為null,那麼該鏈式呼叫就會返回null。
如果要只對非空值執行某個操作,可以將安全呼叫操作符與let一起使用:
val listWithNulls: List<String?> = listOf("A", null)
for (item in listWithNulls) {
item?.let { println(it) } //輸出A並忽略null
}
1.1.7.2 Elvis操作符
表示式1 ?: 表示式2
含義:如果表示式1不是null,那麼就返回表示式1的值,否則就返回表示式2的值。注意,當且僅當表示式1的值為null時,才會對錶達式2進行求值。
throw和return在Kotlin中也都是表示式,所以它們也可以用在elvis操作符右側。這可能會非常方便:
fun foo(node: Node): String? {
val parent = node.getParent() ?: return null
val name = node.getName() ?: throw IllegalArgumentException("name expected")
……
}
1.1.7.3 !!操作符
這是為NPE愛好者準備的:
var a: String? = null //a可空
val l = a!!.length //如果引用a為null,則丟擲NPE,如果不為null,則返回a.length
1.1.7.4 過濾集合中的非空元素
如果你有一個可空型別元素的集合,並且想要過濾得到非空元素,你可以使用filterNotNull來實現:
val nullableList: List<Int?> = listOf(1, 2, null, 4)
val intList: List<Int> = nullableList.filterNotNull()
1.1.8 型別檢測與轉換
1.1.8.1 型別檢測與智慧型別轉換
型別檢測使用is或!is操作符。在多數情況下,不需要在Kotlin中使用顯式轉換操作符,因為編譯器會跟蹤不可變值的is檢查,並在需要時自動進行型別轉換:
if (obj is String) {
print(obj.length) //obj被自動轉換為String
}
if (obj !is String) {
print("Not a String")
}else {
print(obj.length) //obj被自動轉換為String
}
if (x !is String) return
print(x.length) //x被轉換為字串,可見編譯器是十分智慧的
//||右側的x被自動轉換為字串
if (x !is String || x.length == 0) return
//&&右側的x被自動轉換為字串
if (x is String && x.length > 0) {
print(x.length)
}
但是需要注意,當編譯器不能保證變數在檢查和使用之間不可改變時,智慧轉換不能用(詳見官方參考)。
1.1.8.2 型別轉換操作符as
val b:String = a as String
當無法轉換時會丟擲型別轉換異常。
1.1.8.3 安全的型別轉換操作符as?
val b:String? = a as? String
當無法轉換時不會丟擲型別轉換異常,而是返回null。
1.1.9 For迴圈
Java中有普通的for迴圈和foreach迴圈(也稱為高階for迴圈),但kotlin中不支援普通的for迴圈,即你不能像這樣使用for迴圈:for(var i=0;i<100;i++){}
。
kotlin中的for迴圈都是高階for迴圈。for迴圈可以對任何提供迭代器(iterator)的物件進行遍歷,語法如下:
for(item in collection){
print(item)
}
例如:
val strs = listOf("1", "2", "3")//List集合
val ints = arrayOf(1, 2, 3)//陣列
for (item in strs) {
print(item)
}
for (item in ints) {
print(item)
}
/*
* 也可以通過索引來訪問
*/
for (index in strs.indices) {
print(strs[index])
}
for (index in ints.indices) {
print(ints[index])
}
1.1.10 while迴圈
while迴圈和do…while迴圈與Java中的用法一樣
val items = listOf("apple", "banana", "kiwi")
var index = 0
while (index < items.size) {
println("item at $index is ${items[index]}")
index++
}
1.1.11 when
when即可以作為語句,也可以作為表示式,它的作用類似於C++和Java中的switch語句,不過使用起來更加靈活。
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> {
print("x is neither 1 nor 2")
}
}
//0,1兩個分支執行一樣的操作
when (x) {
0, 1 -> print("x == 0 or x == 1")
else -> print("otherwise")
}
//我們可以用任意表達式(而不只是常量)作為分支條件
when (x) {
parseInt(s) -> print("s encodes x")//當x等於parseInt(s)時,執行此分支
else -> print("s does not encode x")
}
//我們也可以檢測一個值在(in)或者不在(!in)某個區間或者集合中
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")
}
//另一種可能性是檢測某個值是(is)或者不是(!is)某個特定型別的值
val hasPrefix = when(x) {
is String -> x.startsWith("prefix")//會將x智慧轉換為String型別
else -> false
}
//when也可以用來取代if-else if鏈。如果不提供引數,所有的分支條件都是簡單的布林表示式,而當一個分支的條件為真時則執行該分支
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}
1.1.12 使用區間(range)
使用..操作符來定義一個區間,如1..10
。任何可比較的型別都可以定義為一個區間。區間常與in和!in配合使用。
if (i in 1..10) { //檢測i是否在區間[1,10]之內
println(i)
}
整型區間(IntRange、LongRange、CharRange )有一個額外的特性:它們可以迭代。
前面講for迴圈時提到過,kotlin中的for迴圈類似於Java中的高階for迴圈,那麼如果我們非要使用Java中普通for迴圈那樣的for迴圈呢,答案就是使用區間:當我們使用for迴圈來迭代區間時,編譯器會將其轉換為類似於Java中的普通for迴圈。
for (i in 1..4) print(i) //輸出“1234”
等價於Java中的:
for(int i=1;i<=4;i++){...}
使用downTo操作符來定義一個逆向的區間:for (i in 4 downTo 1) print(i)
,輸出“4321”
指定迭代的步長:
for (i in 1..4 step 2) print(i) // 輸出“13”
for (i in 4 downTo 1 step 2) print(i) // 輸出“42”
使用util操作符定義一個不含尾元素的區間:
for (i in 1 until 10) { // i in [1, 10),排除了10
println(i)
}
1.1.13 使用集合
對集合進行迭代:
for (item in items) {
println(item)
}
使用in運算子來判斷集合內是否包含某例項:
when {
"orange" in items -> println("juicy")
"apple" in items -> println("apple is fine too")
}
使用lambda表示式來過濾(filter)和對映(map)集合:
fruits
.filter { it.startsWith("a") }
.sortedBy { it }
.map { it.toUpperCase() }
.forEach { println(it) }
1.2 習慣用法
詳見官方參考。建議暫時擱置,最後再看。
1.3 編碼規範
詳見官方參考。建議暫時擱置,最後再看。