Kotlin從入門到精通
一. Kotlin課程概述
1.1 課程安排:
- 課程介紹、Kotlin介紹、開發環境搭建
- 基本語法:基本類型、空安全類型、智能轉換、類與對象同步、數組與區間
- 程序結構:常量與變量、函數、Lambda、類成員、條件表達式、循環語句、運算符、異常捕獲
- 面向對象:抽象類和接口、Object、伴生對象、擴展方法、屬性代理、數據類、內部類、枚舉與密封類
- 高階函數:基本概念、常見高階函數、尾遞歸優化、閉包、函數複合、科裏化、偏函數
- DSL:基本概念、案例開發、Gradle腳本
- 協程:基本概念、協程的使用、封裝協程庫、協程原理分析;
- 與Java混編:基本互操作、正則表達式、集合框架、IO操作、裝箱與拆箱、NoArg插件、AllOpen插件、註解處理器
- 應用與展望:前景與展望、編寫腳本、服務端、前端、Android、Native
1.2 什麼是Kotlin?
- Kotlin就是一門可以運行在Java虛擬機、Android、瀏覽器上的靜態語言,它與Java 100%兼容,如果你對Java非常熟悉,那麼你就會發現Kotlin除了自己的標準庫之外,大多仍然使用經典的Java集合框架;
- 總結來說:
- Android官方開發語言
- 100%兼容Java
- Kotlin-Js 前端開發
- Kotlin-Jvm 服務端開發
- Kotlin-Native 本地執行程序
Kotlin 是一門全棧語言
1.3 Kotlin的發展歷程
- 2010年立項
- 2011.6對外公開
- 2012.2開源
- 2013.8 支持 Android Studio
- 2014.6全新的開源web站點和域名 Kotlinlang.org
- 2016.2 發佈1.0
- 2016.9 發佈1.0.4, 支持apt
1.4 學習目標
- 學會使用Kotlin
- 熟悉Java生態
- 瞭解一些特性的背後實現
1.5 必備知識
- 熟悉計算機基礎、操作系統相關的知識
- 瞭解Java及其生態
- 瞭解Java工程組織的常用工具
- 熟悉IntelliJ Idea
1.6 參考資料
- 官方文檔:https://kotlinlang.org/docs/reference
- Kotlin源碼:https://github.com/JetBrains/kotlin
- Kotlin官博:https://blog.jetbrains.com/kotlin/
- Kotlin微信公衆號:Kotlin
1.7 Hello,world
-
安裝Kotlin插件,如圖所示:
安裝後要重啓一次才能生效;
-
創建一個Kotlin工程,如圖所示:
3. 根據實際,填寫項目的groupId,ArtifactId,以及Version,如圖所示:
-
一路默認下去即可,然後成功創建項目,然後在此項目中創建Kotlin包,如圖所示:
-
緊接着創建包net.println.kotlin(根據實際來,此處可不一致),然後創建HelloWorld.kt,如圖所示:
-
編寫Hello,world代碼如下:
fun main(args:Array<String>){ println("Hello World") }
-
執行,如圖所示:
執行成功後,控制檯會打印Hello,world字樣,說明運行成功,如圖所示:
-
點擊println進去,可以看到源碼,其打印操作是調用的Java的System.out.println,如圖所示:
二. 數據類型
2.1 本章目標
- 認識基本類型
- 初步認識類及其相關概念
- 認識區間和數組
簡單來說就是看懂如圖例子:
2.2 Boolean類型
- Boolean 值 只有true或者false兩個值,它無處不在,相當於Java類型的boolean
- 示例代碼:
> var是可變變量(可讀可寫),val是可讀變量(只能讀);同時 : 後面的Boolean 是指它的類型,前面的aBoolean或anotherBoolean是它的變量名稱,=號後面的true或false是它的值;val aBoolean : Boolean = true val anotherBoolean : Boolean = false
2.3 Number類型
-
數字類型如下:
-
int,Long類型的最大值和最小值
// 2147483647 val maxInt: Int= Int.MAX_VALUE // -2147483648 val mintInt: Int=Int.MIN_VALUE val maxLong : Long=Long.MAX_VALUE val minLong: Long=Long.MIN_VALUE val aFolat: Float=20F val maxFolat: Float=Float.MAX_VALUE val minFolat: Float=- Float.MAX_VALUE val maxShort : Short=Short.MAX_VALUE val minShort : Short=Short.MIN_VALUE val maxByte: Byte= Byte.MAX_VALUE val minByte : Byte= Byte.MIN_VALUE
val後面的爲變量名,可替換爲實際名稱;Long類型的長整形後面可以加個L; Float類型後面必須加F,f;Float是浮點類型,有精度問題,計算錢相關的不要用這個;
-
裝箱和拆箱
- 在Kotlin中不區分裝箱和拆箱;
2.4 Char類型
-
特點:
- 字符對應Java的Character
- 佔兩個字節,表示一個16位的Unicode字符
- 字符用單引號’‘引起來,例如: ‘a’,‘0’,’\n’
-
Char類型轉義字符如圖:
-
不可隱式轉換
- 在java中,一個int類型與Long相加,原本的Int類型會自動隱式轉換爲Long類型,這在Kotlin中是不允許的;
-
比較相等
- ==表示equals 值得相等比較
- === 三個等號表示引用地址的比較,即比較兩個值是否是同一個引用地址;
-
字符串模板
- 代碼如圖:
val arg1: Int =0 val arg2: Int =1 // java款式的加法 println(""+arg1+"+"arg2+"="+(arg1+arg2)) // kotlin款式的加法 println("$arg1+ $arg2=${arg1+arg2}") // 如果要打印美元 $這個符號,則再加一個$ val salary: Int = 1000 println("$"+"$salary") // 或者使用轉義符號 println("\$salary") // 三個引號,轉義會失效,支持換行 val txt:String =""" hello , world """ // 打印它的字符數量 println(rawString.length)
- 代碼如圖:
2.5 類與對象
-
什麼是類?
- 類,一個抽象的概念
- 具有某些特徵的事物的概括
- 不特定指代任何一個具體的事物
- 舉例:
- 人、車、書
- 寫法:
class<類名>{<成員>}
-
什麼是對象?
- 是一個具體的概念,與類相對
- 描述某一種類的具體個體
- 舉例:
- 某些人、領導的車、你手裏的那本書
-
類和對象的關係?
- 一個類通常可以有很多個具體的對象
- 一個對象本質上只能從屬於一個類
- 某一個人,他是工程師,但本質上還是屬於人這一類
-
對象也經常被稱作“類的對象”或者“類的實例”
- 比如 類: 城市 --> 上海、深圳(對象)
-
類的繼承
- 提取多個類的共性得到一個更抽象的類,即父類;
- 子類擁有父類的一切特徵
- 子類也可以自定義自己的特徵
- 所有類都最終繼承自Any
-
類與對象的實例圖示:
-
如果在類中加了init字段,則在創建過程中會自動執行其中的方法,如圖所示:
-
當構造只有一個的時候可以進行省略,如果有多個則不可以,如圖所示:
-
子類繼承了父類中的一些方法,如圖所示:
-
Any是一切類的父類,它擁有equals,hashCode,toString方法,則說明在Kotlin中的其他所有類,都擁有這些方法,如圖所示:
2.6 空類型和智能類型轉換
- 任意類型都有可空和不可空兩種
- val notNull:String = null // 錯誤,不能爲空。 如果爲空會拋出異常
- val nullable:String ?= null // 正確,可以爲空。 如果爲空,則被賦值的nullable的值爲null
- notNull.length // 正確,不爲空的值可以直接使用
- nullable.length // 錯誤,可能爲空,不能直接獲取長度
- nullable!!.length // 正確,強制認定nullable不可空(如果在這段代碼前進行了if判斷,比如不爲空的時候才執行的這段代碼,就沒有問題。我們已經認定了這個nullable變量不爲空)
- nullable?.length // 正確,若nullable爲空,返回空
- Java Style類型轉換
val sub: SubClass = parent as SubClass
- 類似於Java 的類型漢族那換,失敗則拋出異常;
這裏的含義是,判斷parent變量爲SubClass的子類,若爲其子類,則sub是SubClass類型,如不爲則直接拋出異常;
- 安全類型轉換
- val sub: SubClass? = parent as? SubClass
- 如果轉換失敗,返回null,不拋異常
> 如果parent不是SubClass的子類,則類型轉換失敗,sub不能編程SubClass類型,則sub的值爲null;
2.7 包
- 概述:
- 包就是命名空間
- 包的聲明必須在非註釋代碼的第一行
- 類的全名:
- net.println.kotlin,chapter2.HelloWorld
- 包即類的全名,import字段進行導入,import同時可以對此包進行命名,可以以另外一個名稱進行替代和調用。如圖所示:
package net.println.koltin.DemoTest import net.println.kotlin.HelloWord as Hello fun main(args:Array<String>){ val sayHello:Hello=Hello(); }
這裏導入的Hello,就是HelloWorld類。通過創建Hello,就相當於創建了一個HelloWord,所以sayHello看似是對Hello實例化,實際上是對HelloWorld進行了實例化;
2.8 區間
- 概述:
- 一個數學上的概念,表示範圍
- ClosedRange的子類,IntRange最常用
- 基本寫法:
- 0…100表示[0,100]
- 0 until 100 表示 [0,100)
- i in 0…100判斷 i 是否在區間 [0,100]中
- 示例代碼:
// 前後都閉區間 [0,1024] val range:IntRange=0..1024 // 前閉後開的區間 [0,1024) = [0,1023] val range_exclusive:IntRange= 0 until 1024 val empty_Range:IntRange=0..-1 fun main(args:Array<String>){ // true 是爲空,因爲此範圍中沒有值 println(emptyRange.isEmpty()) // true 此範圍中包含了50 println(range.contains(50)) // true 檢查50是否在range的範圍中 println(50 in range) } // 下面打印出來的結果爲: 0,1,2,3,4,5,6,7,8,9,10... for(i in range_exclusive){ println("$i,") }
用i in IntRange可以作範圍判斷,以及輔助遍歷等操作
2.9 數組
-
數組是什麼?
- 對應英文單詞Array:
An impressive display or range of a particular type of thing or an ordered arrangement,in particular
- 跟數一點關係沒有
- 就是一系列對象,這個對象可以是各類型的數字,字符,字符串,或者自定義對象等;
- 對應英文單詞Array:
-
基本寫法
- val array:Array arrayOf(…)
-
基本操作:
- println array[i] 輸出第i個成員
- array[i] 指定數組的第i個成員值;我們可以通過此進行賦值或者獲取值
- array.length 數組的長度
-
爲了避免不必要的裝箱和拆箱,基本類型的數組是定製的,如圖所示:
-
數組示例代碼:
- 創建對象,如圖所示:
- 創建示例代碼如下:
// 創建一個int數組 val arrayOfint: IntArray = intArrayOf(1,3,5,7) // 創建一個Char數組 val arrayOfChar: CharArray = charArrayOf("H","e","l","o","W","o","r","l","d") // 創建一個自定義對象數組,根據上面創建的類 val arrayOf書記: Array<市委書記> = arrayOf(市委書記("張"),市委書記("趙"),市委書記("黃")) fun main(args: Array<String>){ // 打印 int數組 println(arrayOfInt.size) for(int in arrayOfInt){ println(int) } } println(arrayOf書記[1]) // 這裏將方書記賦值給了數組的1 索引處。此處1索引原來的對象會變成新替換的對象,所以重新打印1索引處的書記會變成新的方書記; arrayOf書記[1] = 市委書記("方") println(arrayOf書記[1]) // 這裏的joinToString("")表示每個字符之間不用什麼連接。 傳入制定參數就以指定值進行連接。如果不傳,默認以,號連接,比如: H,e,l,l,o... 此處代碼打印結果爲: HelloWorld println(arrayOfChar.joinToString(""))
- 創建對象,如圖所示:
三. 程序結構
3.1 常量與變量
-
什麼是常量?
- val= value, 值類型
- 類似Java 的final
- 不可能重複複製;
- 舉例:
- 運行時常量: val x=getX()
- 編譯期常量(Java中的靜態常量 final): const val x=2
-
什麼是變量:
- var = variable
- 舉例:
- var x =“HelloWorld” //定義變量
- x= “HiWorld” // 再次賦值
-
類型推導- 編譯器可以推導量的類型
- val String =“Hello” // 推導出String類型
- val int =5 // Int類型
- var x =getString() + 5 // String類型
-
var是可變量,val 是不可變,它是常量;
-
val雖然是不可變,但是它不是靜態的,如果需要在編譯期時就加在,可以在前面加一個const字段;
3.2 函數
- 什麼是函數?
- 任何函數都是以fun 開頭,然後後面爲它的名字,括號內爲它的參數若有範圍值,在後面用:Any ,Any代表返回值類型;
- 比如我們的Main函數:
fun main(args: Array<String>){ println("Hello,world") // 打印args數組的索引爲1 的值 println(${args[0]}) if (args.size !=2){ } }
如果沒有傳參,會報數組索引異常。這裏main函數沒有爲args賦值;如果啓動時,傳入了參數就可以打印了;同時如果有返回值,則需要在main(…)這個括號後面加 :返回值類型來定義返回值類型;
- 代碼示例:
fun main(args: Array<String>){ checkArgs(args) val arg1=args[0].toInt() val arg2=args[1].toInt() println("$arg1+$arg2=${sum(arg1,arg2)}") } fun checkArgs(args: Array<String>){ if(args.size != 2){ println("請傳入兩個整形參數,例如 1, 2") System.exit(-1) } } fun sum(arg1: Int ,arg2:Int):Int{ return arg1+arg2 }
3.3 Lambda表達式
-
什麼是lambda表達式?
- 匿名函數
- 寫法:
{[參數列表]->[函數體,最後一行是返回值]}
- 舉例:
val sum ={a:Int,b:Int-> a+b}
-
Lambda的類型表示舉例:
// 無參,返回值爲Unit ()->Unit // 傳入整型,返回一個整型 (Int)->Int // 傳入字符串、Lambda表達式,返回Boolean (String,(String)->String)->Boolean
-
Lambda表達式的調用
- 用()進行調用
- 等價於invoke()
- 舉例:
val sum ={a:Int,b:Int->a+b} sum(2,3) sum.invoke(2,3)
-
Lambda表達式的簡化
- 函數參數調用時最後一個Lambda可以移出去
- 函數參數只有一個Lambda,調用時小括號可省略
- Lambda只有一個參數可默認爲it
- 入參,返回值與形參一致的函數可以用函數引用的方式作爲實參傳入;
-
使用for 我們用foreach也可以進行遍歷;
3.4 類成員
-
什麼是類成員?
- 屬性:或者說成員變量,類範圍內的變量
- 方法:或者說成員函數,類範圍內的函數
-
函數和方法的區別?
- 函數強調功能本身,不考慮從屬
- 方法的稱呼通常是從類的角度出發
- 叫法不同而已,不要糾結;
-
函數如何定義方法?
- 寫法與普通函數一致,函數如果寫在類中,它就是方法
- 舉例:
class Hello{ fun sayHello(name:String)=println("Hello,$name") }
-
定義屬性
- 構造方法參數中val/var的都是屬性
- 類內部也可以定義屬性
- 舉例:
class Hello(val aField:Int,notAField:Int){ val anotherFIeld:Float=3f }
-
屬性的訪問規則:
- 屬性可以定義getter/setter
- 舉例如下:
val a:Int=0 get()=field var b:Float=0f set(value){field=value}
get(){return field} 其中field指代了此變量
-
屬性初始化
- 屬性的初始化儘量在構造方法中完成
- 無法在構造方法中初始化,嘗試降級爲局部變量
- val用lateinit延遲初始化,val用lazy
- 可空類型謹慎用null直接初始化;
- 舉例:
class X class A{ // 使用lateinit延遲初始化,不需要立即給值 lateinit var c: String val e: X by lazy{ X() } }
3.5 運算符
- 任何類可以定義或者重載父類的基本運算符
- 通過運算符對應的具名函數來定義
- 對參數個數作要求,對參數和返回值類型不作要求
- 不能像Scala一樣定義任意運算符
3.6 表達式
-
中綴表達式
- 只有一個參數,且用infix修飾的函數
- 舉例:
class Book{ infix fun on(place: String){ ... } } if(Book() on "My Desk"){...}
-
if 表達式
- 舉例:
if(a==b) ... else ...
在if表達式中,我們既可以用來做條件判斷,也可以類似java三元運算符一樣直接用來當一個值進行使用。比如
val b=2 // 當b等於2時,則a等於3 否則等於5 val a= if(b==2) 3 else 5
- 表達式完備性:
- 當我們用於類似三元運算符的操作時,必須要有else
- 舉例:
-
When 表達式
- 加強版Switch,支持任意類型
- 支持純表達式條件分支(類似if)
- 表達式與完備性
- 舉例:
fun main(args: Array<String>){ val x=5 when(x){ is Int-> 邏輯代碼... in 1..100-> 邏輯代碼... !in 1..100-> 邏輯代碼... args[0].toInt()-> 邏輯代碼... } }
這裏的when處傳入值,下面任意滿足條件且按先後只執行第一個符合條件的代碼邏輯;
3.7 循環
-
for循環
- 基本寫法
for(element in elements)...
- 代碼示例:
fun main(args: Array<String>){ // 遍歷一 for(arg in args){ println(arg) } // 遍歷二 for((index,value) in args.withIndex()){ println("$index-> $value") } // 遍歷三 for(indexedValue in args.withIndex()){ println("${indexedValue.index} -> ${indexedValue.value}") } }
- 給任意類實現Iterator方法
可網上找
- 基本寫法
-
While循環
- 古董級語法
- do … while(…)…
- while(…)…
- 代碼示例:
fun main(args:Array<String>){ var x=5 while(x>0){ println(x) x-- } do { println(x) x-- }while(x>0) }
-
跳過和終止循環
- 跳過當前循環用continue
- 終止循環用break
- 多層循環嵌套的終止結合標籤使用
3.8 捕獲異常
- catch 分支匹配異常類型
- 表達式,可以用來賦值
- finally: 無論代碼是否拋出異常都會執行
- 注意下面的寫法:
return try( x/y )catch(e:Exception){ 0 }finally{ ... }
異常的捕獲及處理與java類似,不過其可以作爲值進行使用;
3.9 各種類型參數
-
具名參數
- 給函數的實參附上形參
- 舉例:
fun sum(arg1:Int, arg2:Int)= arg1+arg2 sum (arg1=2 ,arg2=3)
-
變長參數
- 某個參數可以接收多個值
- 可以不爲最後一個參數
- 如果傳參時有歧義,需要使用具名參數
-
Spread Operator
- 只支持展開Array
- 只用於變長參數列表的實參
- 不能重載
-
默認參數:
- 爲函數參數指定默認值
- 可以爲任意位置的參數指定默認值
- 傳參時,如果有歧義,需要使用具名參數
-
代碼示例:
fun main(vararg args: String){ var array= intArrayOf(1,3,4,5) // string="Hello" 是一個默認參數 // *array表示將array數組展開,將一個個元素傳入,如: hello(3.0,1,3,4,5,string="Hello") hello(3.0,*array,string="Hello") } fun hello(double:Double, vararg ints:Int, string:String){ ints.forEach(::println) println(string) }
3.10 導出爲可執行程序
- 在可執行的類上加上如下字段:
apply plugin:'application'
mainClassName="net.println.kotlin.chapter3.CalcKt"
然後gradle會下載一些相關插件,完成後會在Gradle的窗口中,在Tasks-> distribution-> InstallDist 中執行InstallDist. 如圖所示:
- 會各生成一個windows和Linux下的腳本,如圖所示:
四. 面向對象
4.1 面向對象-抽象類與接口
-
面向對象的基本概念
- 本質上就是解決如何用程序描述世界的問題
- 討論如何把實際存在的東西映射成程序的類和對象
- 一種程序設計的思路、思想、方法
-
類實例:
// 定義一個類 class Demo{ // 定義一個可讀變量 val i=4 // 定義一個方法 fun out(i: int){ println(i) } } // 定義一個接口 interface chouxianglei{ // 定義一個接口方法 fun hello() }
-
繼承一個接口的時候,使用類名(),實現一個接口的時候,使用類名即可,在Kotlin 中是單繼承,多實現;如圖所示:
-
什麼是接口?
- 接口,直觀理解就是一種約定。 Kotlin的接口與Object-C的Protocol比較類似
- 舉例,輸入設備接口:
interface InputDevice{ fun input(event: Any) }
-
接口與抽象類的區別:
- 接口:
- 不能有狀態
- 必須由類對其進行實現後使用
- 抽象類:
- 實現了一部分協議的半成品
- 可以有狀態,可以有方法實現
- 必須由子類繼承後使用
- 共性:
- 比較抽象,不能直接實例化
- 有需要子類(實現類)實現的方法
- 父類(接口)變量可以接受子類(實現類)的實例賦值
- 接口:
4.2 繼承
-
繼承(實現)語法要點
- 父類需要open纔可以被繼承
- 父類方法、屬性需要open纔可以被覆寫
- 接口、接口方法、抽象類默認爲open
- 覆寫父類(接口)成員需要override關鍵字
-
Class D: A(),B,C
- 注意繼承類時實際上調用了父類的構造方法
- 類只能單繼承,接口可以多實現
-
class Manager(driver: Driver): Driver by driver
- 接口方法實現交給代理類實現
-
接口方法衝突
- 接口方法可以有默認實現
- 簽名一致且返回值相同的衝突
- 子類(實現類)必須覆寫衝突方法
4.3 可見性
- 可見性Java與Kotlin對比,如圖所示:
4.4 Object類
- 特點:
- 只有一個實例的類
- 不能自定義構造方法
- 可以實現接口、繼承父類
- 本質上就是單例模式最基本的實現
使用object類可以創建一個最簡單的單例類
4.5 伴生對象與靜態成員
- 伴生對象的特點:
- 每個類可以對應一個伴生對象
- 伴生對象的成員全局獨一份
- 伴生對象的成員類似Java的靜態成員
> 伴生對象就相當於靜態變量和靜態方法的整體;
- 使用伴生對象需要注意的地方:
- 靜態成員考慮用包級函數、變量替代
- @JvmField 和 @JvmStatic的使用
- 示例代碼:
class Latitude private constructor(val value: Double){ companion object{ @JvmStatic fun ofDouble(double:Double):Latitude{ return Latitude(double) } fun ofLatitude(latitude: Latitude): Latitude{ return Latitude(latitude.value) } @JvmField val TAG: String="Latitude" } }
這裏@JvmStatic和@JvmField不加此註解,在companion object代碼塊中均可作爲靜態方法或靜態屬性,但是如果不加不能被java代碼所識別。如果涉及到java代碼調用此靜態方法或靜態屬性,則需要加@JvmStatic或@JvmField註解;它們分別修飾方法和屬性;
4.5 方法重載與默認
-
方法重載的特點:
- Overloads
- 名稱相同、參數不同的方法
- Jvm函數簽名的概念: 函數名、參數列表
- 跟返回值沒有關係
-
默認參數的特點:
- 爲函數參數設定一個默認值
- 可以爲任意位置的參數設置默認值
- 函數調用產生混淆時用具名參數
-
重載與默認:
- 二者的相關性以及@JvmOverloads
- 避免定義關係不大的重載
- 代碼示例如圖:
方法重載能夠用默認參數來解決,所以能不使用就儘量不使用。@JvmOverloads能用於被java代碼所識別,如果只在Kotlin中使用可以不用加這個註解;
4.6 擴展成員
- 在Java中,一些java庫的一些方法不夠全面,往往我們會自己定義一些Utils,在Kotlin中,我們可以對一些現有的類進行擴展;
- 特點:
- 爲現有成員添加方法、屬性:
// 添加方法: fun X.y():Z{...} // 添加屬性 val X.m 注意擴展屬性不能初始化,類似接口屬性
- Java調用擴展成員類似調用靜態方法
- 代碼如圖所示:
- 爲現有成員添加方法、屬性:
4.7 屬性代理
-
如圖所示,此懶加載就是一個代理:
-
特點:
- 定義方法:
val/var <property name>: <Type> by <expression>
- 代理者需要實現相應的setValue/getValue方法
實際上就是說,當我們使用了 val/var xxx by yyy()的屬性代理時,其代碼執行邏輯主要看yyy()了。如果是var方法我們需要實現setValue和getValue,如果是val則只需要實現getValue方法即可,因爲val是可讀常量;
- 定義方法:
-
代碼示例:
其中定義了存值和賦值的get,setValue方法;當我們創建時則會自動執行setValue,當取值時則會調用getValue;
4.8 數據類
-
特點:
- 再見,JavaBean
- 默認實現的copy、toString等方法
- componentN方法
- allOpen和noArg插件
-
代碼示例:
-
同時,我們可以直接在類中直接定義構造參數值,如圖所示:
-
此時不需要傳入構造參數,便可將構造參數值外傳並執行其他操作,如打印,如圖所示:
-
數據類有一個問題,就是它沒有空的默認的構造方法。編譯生成的類是一個final的且無空構造方法,所以不是JavaBean,在有些時候使用是有問題的。我們可以通過noarg和allopen插件解決:
- 引入依賴:
- 定義並應用插件:
- 對指定的數據類使用@PoKo註解,如圖所示:
- 引入依賴:
4.9 內部類與匿名內部類
-
特點:
- 定義在類內部的類
- 與類成員有相似的訪問機制
- 默認是靜態內部類,非靜態用inner關鍵字
- this@Outter,this@Inner的用法
-
匿名內部類:
- 沒有定義名字的內部類
- 類名編譯時生成,類似Outter$1.class
- 可繼承父類、實現多個接口,與Java注意區別
-
定義一個內部類(非靜態):
非靜態內部類,需要在 類的class 前面加inner關鍵字
-
定義一個內部類(靜態):
靜態內部類。默認內部類爲靜態內部類;調用時,外部類.內部類()即可;靜態內部類無法獲取非靜態的外部類的屬性和方法,因爲它是先被編譯加載的;而非靜態內部類可以持有外部類的非靜態屬性和方法;
-
當調用外部類的屬性時,我們可以直接調用,也可以用this.@外部類名.屬性或方法來執行,如圖所示:
-
匿名內部類實現方法示例:
4.10 枚舉
-
特點:
- 實例可數的類,注意枚舉也是類
- 可以修改構造,添加成員
- 可以提升代碼的表現力,也有一定的性能開銷
- 枚舉的屬性和方法之間必須要用 ; 號隔開,這裏可能是Kotlin中唯一強制要求使用 ; 號的地方
-
定義一個枚舉類,如圖所示:
$name 是它的名稱 $ordinal 是它括號裏的值
-
調用示例:
// 打印指定的枚舉類 1,DEBUG println(LogLevel.DEBUG.getTag()) // 打印指定枚舉類的序號 1 println(LogLevel.DEBUG.ordinal) // 打印指定枚舉類的實例 ERROR,4 println(ERROR,4)
4.11 密封類
-
特點:
- 子類可數:
- Kotlin版本小於1.1時,子類必須定義爲密封類的內部類
- 在1.1之後,子類只需要與密封類在同一個文件中
- 仔細體會與枚舉的不同
- 子類可數:
-
密封類代碼示例:
使用枚舉適用於沒有參數的情況下,而使用密封類可以用在多參數的情況下,每個類的參數都不盡然相同,同時又想保護此類,不讓其他fun方法返回此類,就可以使用密封類;
-
密封類在class前面加 sealed關鍵字