Kotlin的優勢
代碼簡潔高效、強大的when語法,不用寫分號結尾,findViewById光榮退休,空指針安全、強大的擴展功能、函數式編程、支持lambda表達式、流式API等等
Kotlin基本語法
基本用法
類型和函數定義
在Kotlin中,常量用val
聲明,變量用var
聲明,關鍵字在前面,類型在后面以冒號:隔開,也可以省略直接賦值(自動進行類型推斷):
var str: String = "hello" //字符串
var a: Int = 10 //整形
var array: Array<Int> = arrayOf(1, 2, 3) //數組
var str2: String? = null //可空字符串變量
定義一個函數接受兩個 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): Unit { //一般,Unit可以省略不寫
println("sum of $a and $b is ${a + b}")
}
//也可簡寫為:
fun printSum(a: Int, b: Int) = println("sum of $a and $b is ${a + b}")
函數參數可以設置默認值,當參數被忽略時會使用默認值。這樣相比其他語言可以減少重載:
fun sum(a: Int, b: Int = 1) = a + b
另外Kotlin還支持擴展函數和中綴表達式,下面是簡單的例子:
//擴展函數
fun String.isLetter(): Boolean {
return matches(Regex("^[a-z|A-Z]$"))
}
//中綴表達式只能是成員函數或者擴展函數,而且函數只有一個參數
infix fun String.isEqual(that: String): Boolean{
return this == that
}
fun main(args: Array) {
var s = "a".isLetter()
var a = "aa" isEqual "bb"
}
String對象中本沒有判斷是否是字母的方法,在Java中我們一般會定義一些Utils方法,而在Kotlin中可以定義類的擴展函數。
第二個例子是給String類定義了一個擴展函數,并且該拓展函數以中綴表達式表示,給予了開發者定義類似于關鍵字的權利。
再比如,我們一般這樣創建一個map對象:
val kv = mapOf("a" to 1, "b" to 2)
這里的to就是一個中綴表達式,定義如下:
public infix fun<A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
Pair就是Map中存的對象,所以你也可以這樣創建:
val kv = mapOf(Pair("a", 1), Pair("b", 2))
流程控制語句
流程控制語句是編程語言的核心之一。跟java類似,Kotlin有以下的語句。
- 分支語句 if、when
- 循環語句 for、while
- 跳轉語句 return、break、continue、throw
if表達式
在Kotlin中,if是一個表達式,即它會返回一個值。if作為代碼塊時,最后一行作為返回值。
fun max(x: Int, y: Int): Int {
return if (x > y) {
println("max is $x")
x
}else{
println("max is $y")
y
}
}
注意:Kotlin中沒有java的中的2>1?2:1這樣的三元表達式。
when表達式
Kotlin中沒有java中的switch-case表達式,when表達式就是用來代替switch-case的。when會對所有的分支進行檢查直到有一個條件滿足。但相比switch而言,when語句要更加的強大,靈活:
fun cases(obj: Any) {
when (obj) {
1 -> print("第一項")
"hello" -> print("這個是字符串hello")
is Long -> print("這是一個Long類型數據")
!is String -> print("這不是String類型的數據")
else -> print("else類似于Java中的default")
}
}
如果我們有很多分支需要用相同的方式處理,則可以把多個分支條件放在一起,用逗號分隔,
也可以用任意表達式(而不只是常量)作為分支條件,也可以檢測一個值在 in 或者不在 !in 一個區間或者集合中:
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
3, 4 -> print("x == 3 or x == 4")
in 5..9 -> print("x in [5..9]")
is Long -> print("x is Long")
!in 10..20 -> print("x is outside the range")
parseInt(s) -> print("s encodes x")
else -> {
print("x is funny")
}
}
其他for,while,break,continue等,跟Java中用法基本一樣。
NPE空指針的處理
Kotlin的空指針處理相比于java有著極大的提高,可以說是不用擔心出現NullPointerException的錯誤,kotlin對于對象為null的情況有嚴格的界定,編碼的階段就需要用代碼表明引用是否可以為null,為null的情況需要強制性的判斷處理。
可選型定義
非空類型
先說可選型的定義,當我們在Kotlin中定義一個變量時,默認就是非空類型的,當你將一個非空類型置空的時候,編譯器會告訴你這不可行。例如:
var a: String=null //編譯器直接報錯 Null can not be value of a non null type string
這里注意:Kotlin的成員變量(全局變量)必須要初始化,甚至是基本數據類型都要手動給一個初始值,局部變量可以不用初始化,上面的例子是成員變量的聲明。
編譯器直接表示a是一個non null type, 你不可以直接賦一個null值。對于我們java原住民來說聲明變量時如果不去賦值,編譯器會默認賦null(除去基本數據類型),但在Kotlin這是不允許的。
可選型(可空類型)
在定義可選型的時候,我們只要在非空類型的后面添加一個 ? 就可以了。
var b: String? = null //ok
在使用可選型變量的時候,這個變量就有可能為空,所以在使用前我們應該對其進行空判斷(在 Java 中我們經常這樣做),這樣往往帶來帶來大量非業務相關的工作,這些空判斷代碼本身沒有什么實際意義,并且讓代碼的可讀性和簡潔性帶來了巨大的挑戰。
Kotlin 為了解決這個問題,它并不允許我們直接使用一個可選型的變量去調用方法或者屬性。例如:
var b: String? = "abc"
val l = b.length // compilation error
你可以和 Java 中一樣,在使用變量之前先進行空判斷,然后再去調用。如果使用這種方法,那么空判斷是必須的。
val length = if (b != null) b.length else -1
注意: 如果你定義的變量是全局變量,即使你做了空判斷,依然不能使用變量去調用方法或者屬性。
Kotlin 為可選型提供了一個安全調用操作符 ?.,使用該操作符可以方便調用可選型的方法或者屬性。
var length = b?.length //length類型是可選型Int?
Kotlin還提供了一個強轉的操作符!!,這個操作符能夠強行調用變量的方法或者屬性,而不管這個變量是否為空,如果這個時候該變量為空時,那么就會發生 NPE。所以如果不想繼續陷入NPE 的困境無法自拔,請盡量不要選用該操作符。
var length = b!!.length //可能會報NPE錯誤(Caused by: kotlin.KotlinNullPointerException)
Elvis 操作符?:
回到?.
的調用上來,這個調用方式存在一個讓人不安的處理,就是在變量為null的情況下,會直接返回null,這樣空指針的隱患還在。
var b: String? = "abc"
var length : Int = b?.length //報錯類型不匹配 Int? 和 Int
修正的話,需要通過if判斷來進行判空處理
val length = if (b != null) b?.length else -1
這種寫法可以簡化成Elvis 操作符?:
,例如
val length = b?.length ?: -1
b?.length ?: -1
和 if (b != null) b.length else -1
完全等價的。
其實你還可以在?:
后面添加任何表達式,比如你可以在后面用return
和throw
(在 Kotlin 中它們都是表達式)。
Kotlin函數式編程
下面是維基百科上對于函數式編程的定義:
函數式編程(英語:functional programming)或稱函數程序設計,又稱泛函編程,是一種編程范型,它將電腦運算視為數學上的函數計算,并且避免使用程序狀態以及易變對象。函數編程語言最重要的基礎是λ演算(lambda calculus)。而且λ演算的函數可以接受函數當作輸入(引數)和輸出(傳出值)。
下面是關于高階函數的定義:
在數學和計算機科學中,高階函數是至少滿足下列一個條件的函數:接受一個或多個函數作為輸入,輸出一個函數
f(x) = x^2
g(x) = x + 1
g(f(x))就是一個高階函數
不難推斷出函數式編程最重要的基礎是高階函數。也就是支持函數可以接受函數當作輸入(引數)和輸出(傳出值)。
函數式編程的精髓在于函數本身。在函數式編程中函數是第一等公民,與其他數據類型一樣,處于平等地位,可以賦值給其他變量,也可以作為參數,傳入另一個函數,或者作為別的函數的返回值。
從Lambda表達式說起
Lambda 表達式俗稱匿名函數,熟悉Java的大家應該也明白這是個什么概念。Kotlin 的 Lambda表達式更“純粹”一點, 因為它是真正把Lambda抽象為了一種類型,而 Java 8 的 Lambda 只是單方法匿名接口實現的語法糖罷了。
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(v.getContext(), "Click", Toast.LENGTH_SHORT).show();
}
});
這可以轉換為Kotlin代碼(使用Anko庫函數toast):
view.setOnClickListener(object : View.onClickListener {
override fun onClick(v: View) {
toast("Click")
}
}
Lambda表達式由箭頭左側函數的參數(小括號里面的內容)定義的,將值返回給箭頭右側。在這里,將得到的View返回給Unit,這樣可以對上述代碼稍做簡化:
view.setOnClickListener({ view -> toast("Click")})
在定義函數時,必須在箭頭左邊使用中括號并指定參數值,而函數執行代碼在右邊。
如果左邊沒有使用參數,甚至可以省去左邊部分:
view.setOnClickListener({ toast("Click") })
如果被執行的函數是當前函數的最后一個參數的話,也可以將這個作為參數的函數放到圓括號外面:
view.setOnClickListener() { toast("Click") }
最后,如果函數是唯一的參數,還可以去掉原來的小括號:
view.setOnClickListener { toast("Click") }
比起起初的Java代碼,目前的代碼量小于原來的五分之一,且更容易理解。令人印象深刻。Anko給出一個(本質上說是函數名的)簡化版本,由之前展示過的擴展函數組成,該函數也由上述形式實現:
view.onClick { toast("Click") }
以上是Java中的接口映射到Kotlin中Lambda表達式實例。
Kotlin中Lambda表達式
下面詳細介紹Kotlin中Lambda表達式。
var add = {x: Int, y: Int -> x + y}
我們可以這樣使用:
fun main(args: Array) {
val add = {x: Int, y: Int -> x + y}
add.invoke(1, 2)
//或者簡寫為
add(1, 2)
}
它可以像函數一樣使用()調用,在kotlin中操作符是可以重載的,()操作符對應的就是類的重載函數invoke()。
還可以想下面這樣定義一個變量:
val numFun: (a: Int, b: Int) -> Int
它不是一個普通的變量,它必須指向一個函數,并且函數簽名必須一致:
fun main(args: Array) {
val sumLambda = {a: Int, b: Int -> a + b}
var numFun: (a: Int, b: Int) -> Int
numFun = {a: Int, b: Int -> a + b}
numFun = sumLambda
numFun = ::sum
numFun(1,2)
}
fun sum(a: Int, b: Int): Int {
return a + b
}
可以看到這個變量可以等于一個lambda表達式,也可以等于另一個lambda表達式變量,還可以等于一個普通函數,但是在函數名前需要加上(::)來獲取函數引用,有點類似于C++中的函數指針。
我們還可以將一個函數傳遞給另一個函數,比如:
fun <T> doMap(list: List<T>, function: (it: T) -> Unit) {
for (item in list) {
function(item)
}
}
第一個參數是一個List,第二個參數是一個函數,目的就是將List中的每一個元素都執行一次第二個函數。使用方法如下:
val strList = listOf("a" ,"b", "c", "d")
doMap(strList, {item -> println("item: $item, ") })
第二個參數直接傳進去了一個lambda表達式,當然也可以傳一個函數引用:
doMap(strList, ::printLetter)
fun printLetter(item: String) {
println("item: $item, ")
}
效果和上面的代碼一樣。
另外Kotlin還支持局部函數和函數作為返回值,看下面的代碼:
fun main(args: Array) {
val addResult = lateAdd(2, 4)
addResult()
}
//局部函數,函數引用
fun lateAdd(a: Int, b: Int): ()->Int {
fun add(): Int {
return a + b
}
return ::add
}
在lateAdd內部定義了一個局部函數,最后返回了該局部函數的引用,對結果使用()操作符拿到最終的結果,達到延遲計算的目的。
基于以上函數式編程的特性,Kotlin可以像RxJava一樣很方便的進行響應式編程,比如:
計算二維數組每一個子列表的乘積,再求和,可以這樣寫:
val list = listOf(
listOf(1, 2, 3),
listOf(4, 5, 6),
listOf(7, 8, 9)
)
list.map {it: List<Int> -> it.fold(1){ a, b -> a * b } }
.fold(0){ a, b -> a + b }
.log()
Kotlin函數式編程中常用函數 forEach,filter,map,reduce(fold)
forEach
遍歷函數,循環遍歷所有元素,元素是it,可對每個元素進行相關操作;
假設我們現在需要打印列表中每個的名字:
val nameList = arrayOf("Jim","Tom", "Marry","Lin")
nameList.forEach { println(it) }
filter
過濾函數將用戶給定的布爾邏輯作用于集合,返回由原集合中符合條件的元素組合的一個子集。假設一個邏輯,將數組中是2的倍數的數篩選出來,使用Kotlin和Java的實現做一個簡單的對比。
int[] all = {1, 2, 3, 4, 5, 6, 7, 8, 9};
List<Integer> filters = new ArrayList<>();
for (int a : all) {
if (a % 2 == 0) {
filters.add(a);
}
}
Kotlin代碼的實現:
val all = arrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
val filters = all.filter { it % 2 == 0 }
Kotlin 還提供一系列類似的過濾函數:
- filterIndexed, 同 filter,不過在邏輯判斷的方法塊中可以拿到當前item的index
- filterNot,與filter相反,只返回不符合條件的元素組合
- 針對 Map 類型數據集合,提供了 filterKeys 和 filterValues 方法,方便只做key或者value的判斷
map
映射函數也是一個高階函數,將一個集合經過一個傳入的變換函數映射成另外一種集合。
假設我們現在需要將一系列的名字的字符串長度保存到另一個數組:
使用Java實現過程如下:
String[] names = {"James", "Tommy", "Jim", "Andy"};
int[] namesLength = new int[names.length];
for (int i = 0; i < names.length ; i ++) {
namesLength[i] = names[i].length();
}
使用Kotlin實現如下:
val names = arrayOf("James", "Tommy", "Jim", "Andy");
val namesLength = names.map { it.length }
同 filter 相似,Kotlin 也提供的 mapIndexed 的類似方法方便使用,針對 Map 類型的集合也有 mapKeys 和 mapValues 的封裝。
reduce
歸納函數將一個數據集合的所有元素通過傳入的操作函數實現數據集合的積累疊加。同fold一樣,不過fold可以帶初始值。
假設我們現在需要計算一系列的名字的總字符串長度,使用Java實現如下:
String[] names = {"James", "Tommy", "Jim", "Andy"};
int totalLength = 0;
for (int i = 0; i < names.length ; i ++) {
totalLength =+ names[i].length();
}
使用Kotlin實現如下:
val names = arrayOf("James", "Tommy", "Jim", "Andy");
val namesLength = names.map { it.length }.reduce { r, s -> r + s }
關于Anko
Jetbrains給Android帶來的不僅是Kotlin,還有Anko。從Anko的官方說明來看這是一個雄心勃勃的要代替XML寫Layout的新的開發方式。Anko最重要的一點是引入了DSL(Domain Specific Language 領域相關語言)的方式開發Android界面布局。當然,本質是代碼實現布局。不過使用Anko完全不用經歷Java純代碼寫Android的痛苦。
然而,這個不是我們能在這個庫中得到的唯一一個功能。Anko包含了很多的非常有幫助的函數和屬性來避免讓你寫很多的模版代碼。多看看Anko源碼的實現方式對學習Kotlin語言是非常有幫助的。
Anko提供了非常簡單的DSL來處理異步任務,它能夠滿足大部分的需求。它提供了一個基本的doAsync函數用于在子線程執行代碼,可以選擇通過調用uiThread的方式回到主線程。在子線程中執行請求如下這么簡單:
doAsync {
Thread.sleep(3000) //耗時操作
uiThread {
toast("background task finish")
}
}
UIThread有一個很不錯的一點就是可以依賴于調用者。如果它是被一個Activity調用的,那么如果activity.isFinishing()返回true,則uiThread不會執行,這樣就不會在Activity銷毀的時候遇到崩潰的情況了。
Kotlin中的類和對象
Kotlin 的類特性
Kotlin中的類與接口和Java中的有些區別:
- Kotlin中接口可以包含屬性申明
- Kotlin的類申明,默認是final和public的
- Kotlin的嵌套類并不是默認在內部的。它們不包含外部類的隱式引用
- Kotlin的構造函數,分為主構造函數和次構造函數
- Kotlin中可以使用data關鍵字來申明一個數據類
- Kotlin中可以使用object關鍵字來表示單例對象、伴生對象等
Kotlin類的成員可以包含:
- 構造函數和初始化塊
- 屬性
- 函數
- 嵌套類和內部類
- 對象聲明
構造函數
在Kotlin中可以有一個主構造函數,一個或者多個次構造函數。
主構造函數
主構造函數直接跟在類名后面,如下:
open class Person constructor(var name: String, var age: Int) : Any() {
...
}
主構造函數中的屬性可以是可變的(var)也可以是不變的(val)。如果主構造函數沒有任何注解或者可見性修飾符,可以省略constructor關鍵字(屬性默認是val),而且Koltin中的類默認就是繼承Any的,也可以省略。所以可以簡化成如下:
open class Person(name: String, age: Int) {
...
}
主構造函數不能包括任何代碼。初始化代碼可以放到以init關鍵字作為前綴的初始化塊中:
open class Person constructor(var name: String, var age: Int){
init {
println("Student(name = $name, age = $age) created")
}
}
主構造函數的參數可以在初始化塊中使用,也可以在類體內申明的屬性初始化器中使用。
次構造函數
我們也可以在類體中使用constructor申明次構造函數,次構造函數的參數不能使用val或者var申明。
class Student public constructor(name: String, age: Int) : Person(name, age) {
var grade: Int = 1
init {
println("Student(name = $name, age = $age) created")
}
constructor(name: String, age: Int, grade: Int) : this(name, age){
this.grade = grade
}
}
如果一個類有主構造函數,那么每個次構造函數都需要委托給主構造函數,委托到同一個類的另一個構造函數可以使用this關鍵字,如上面這個例子this(name, age)
如果一個非抽象類沒有申明任何構造函數(包括主或者次),它會生成一個不帶參數的主構造函數。構造函數的可見性是public。
抽象類
下面就是一個抽象類,類需要用abstract修飾,其中也有抽象方法,跟Java有區別的是Kotlin的抽象類可以包含抽象屬性:
abstract class Person(var name: String, var age: Int){
abstract var addr: String
abstract val weight: Float
abstract fun doEat()
abstract fun doWalk()
fun doSwim() {
println("I am Swimming ... ")
}
open fun doSleep() {
println("I am Sleeping ... ")
}
}
在上面這個抽象類中,有doEat和doWalk抽象函數,同時還有具體的實現函數doSwim,在Kotlin中如果類和方法沒有修飾符的化,默認就是final的。這個跟Java是不一樣的。所以doSwim其實是final的,也就是說Person的子類不能覆蓋這個方法。如果一個類或者類的方法想要設計成被覆蓋(override)的,那么就需要加上open修飾符。下面是一個Person的子類:
class Teacher(name: String, age: Int) : Person(name, age) {
override var addr:String = "Guangzhou"
override val weight: Float = 100.0F
override fun doEat() {
println("Teacher is Eating ... ")
}
override fun doWalk() {
println("Teacher is Walking ... ")
}
override fun doSleep() {
super.doSleep()
println("Teacher is Sleeping ... ")
}
// 編譯錯誤,doSwim函數默認是final的,需要加上open修飾符才能重寫
// override fun doSwim() {
// println("Teacher is Swimming ... ")
// }
}
如果子類覆蓋了父類的方法或者屬性,需要使用override關鍵字修飾。如果子類沒有實現父類的抽象方法,或者沒有給抽象屬性賦值,則必須把子類也定義成抽象類。
抽象函數的特征有以下幾點:
- 抽象函數、抽象屬性必須使用abstract關鍵字修飾
- 抽象函數或者抽象類不用手動添加open關鍵字,默認就是open類型
- 抽象函數沒有具體的實現,抽象屬性不用賦值
- 含有抽象函數或者抽象屬性的類,必須要使用abstract關鍵字修飾。抽象類可以有具體實現的函數,這樣的函數默認是final的(不能被覆蓋)。如果想要被覆蓋,需要手工加上open關鍵字
接口
和Java類似,Kotlin使用interface作為接口的關鍵詞,Kotlin 的接口與 Java 8 的接口類似。與抽象類相比,他們都可以包含抽象的方法以及方法的實現:
interface ProjectService {
val name: String
val owner: String
fun save(project: Project)
fun print() {
println("I am project")
}
}
接口是沒有構造函數的,和繼承一樣,我們也是使用冒號:來實現一個接口,如果要實現多個接口,使用逗號,分開。
繼承
在Kotlin中,所有的類都默認繼承Any這個類,Any并不是跟Java的java.lang.Object一樣,因為它只有equals(),hashCode()和toString()這三個方法。
除了抽象類和接口默認是可被繼承外,其他類默認是不可以被繼承的(相當于默認都帶有final修飾符)。而類中的方法也是默認不可以被繼承的。
- 如果你要繼承一個類,你需要使用open關鍵字修飾這個類
- 如果你要重寫一個類的某個方法,這個方法也需要使用open關鍵字修飾
open class Base(type: String){
open fun canBeOverride() {
println("I can be override.")
}
fun cannotBeOverride() {
println("I can't be override.")
}
}
class SubClass(type: String) :Base(type){
override fun canBeOverride() {
super.canBeOverride()
println("Override!!!")
}
// override fun cannotBeOverride() { 編譯錯誤。
// super.cannotBeOverride()
// println("Override!!!")
// }
}
如果Base有構造函數,那么子類的主構造函數必須繼承。
單例模式
Kotlin中沒有靜態屬性和方法,但是也提供了實現類似單例的功能,使用object關鍵字聲明一個object對象。
object StringUtils{
val separator: String = """\"""
fun isDigit(value: String): Boolean{
for (c in value) {
if (!c.isDigit()) {
return false
}
}
return true
}
}
fun main(args: Array<String>) {
println("C:${StringUtils.separator}Users${StringUtils.separator}Denny") //打印c:\Users\Denny
println(StringUtils.isDigit("12321231231")) //打印true
}
我們反編譯后可以知道StringUtils轉成了Java代碼:
public final class StringUtils {
@NotNull
private static final String separator = "\\";
public static final StringUtils INSTANCE;
@NotNull
public final String getSeparator() {
return separator;
}
public final boolean isDigit(@NotNull String value) {
Intrinsics.checkParameterIsNotNull(value, "value");
String var4 = value;
int var5 = value.length();
for(int var3 = 0; var3 < var5; ++var3) {
char c = var4.charAt(var3);
if (!Character.isDigit(c)) {
return false;
}
}
return true;
}
static {
StringUtils var0 = new StringUtils();
INSTANCE = var0;
separator = "\\";
}
}
object對象只能通過對象名字來訪問,不能使用構造函數。
我們在Java中通常會寫一些Utils類,這樣的類我們在Kotlin中就可以直接使用object對象。
伴生對象(companion object)
Kotlin中還提供了伴生對象 ,跟java中的static關鍵字有些相似,用companion object關鍵字聲明:
class DataProcessor {
fun process() {
println("Process Data")
}
companion object StringUtils { //StringUtils可以省略
val TAG = "DataProcessor"
fun isEmpty(s: String): Boolean {
return s.isEmpty()
}
}
}
一個類只能有一個伴生對象,伴生對象的初始化是在相應的類被加載解析時,即使伴生對象的成員看起來像其他語言的靜態成員,在運行時他們仍然是真實的對象的實例成員。
另外,如果想使用Java中的靜態成員和靜態方法的話,我們可以用:
- @JvmField注解:生成與該屬性相同的靜態字段
- @JvmStatic注解:在單例對象和伴生對象中生成對應的靜態方法
嵌套類(Nested Class)
class NestedClassesDemo {
class Outer {
private val zero: Int = 0
val one: Int = 1
class Nested {
fun getTwo() = 2
class Nested1 {
val three = 3
fun getFour() = 4
}
}
}
}
我們可以這樣來訪問內部類:
val one = NestedClassesDemo.Outer().one
val two = NestedClassesDemo.Outer.Nested().getTwo()
val three = NestedClassesDemo.Outer.Nested.Nested1().thre
但是普通的嵌套類,并不會持有外部類的引用,如果要持有外部類的引用,那么我們需要把嵌套類標記為內部類,使用inner關鍵字即可:
class NestedClassesDemo {
class Outer {
private val zero: Int = 0
val one: Int = 1
inner class Inner {
fun accessOuter() = {
println(zero) // works
println(one) // works
}
}
}
匿名內部類(Annonymous Inner Class)
匿名內部類,就是沒有名字的內部類。既然是內部類,那么它自然也是可以訪問外部類的變量的。
我們使用對象表達式創建一個匿名內部類實例:
button.setOnClickListener(object : View.OnClickListener{
override fun onClick(v: View) {
println("Clicked")
}
})
如果對象實例是一個函數接口(Java中只有一個抽象方法的接口),可以使用lambda表達式:
button.setOnClickListener({view -> println("Clicked")})
對象表達式(Object expressions)
如果父類型有構造函數,則必須將構造函數的參數賦值;多個父類通過“,”分割:
open class A(x: Int) {
public open val y: Int = x
}
interface B {...}
val ab: A = object : A(1), B {
override val y = 15
}
有時,只需要一個對象表達式,不想繼承任何的父類型,實現如下:
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print(adHoc.x + adHoc.y)
對象表達式和對象聲明的區別
- 對象表達式(Object expressions),在它們使用的地方,是立即(immediately)執行(或初始化)
- 對象聲明(Object declarations),會延遲(lazily)初始化;但第一次訪問該對象時才執行
- 伴生對象(Companion Objects),當外部類被加載時初始化,跟Java靜態代碼框初始化相似
總結
以上簡單介紹了一下Kotlin的基本語法,函數式編程和Kotlin類和對象的入門知識。由于這只是一篇入門的文章,對于Kolin中更多的知識點,比如泛型,協程,與Java的互調等更深層的內容并沒有介紹。各位讀者如果想要深入學習Kotlin編程,可以參考下面的Kotlin學習進行學習。
- 路漫漫其修遠兮,吾將上下而求索。
- 怕什么真理無窮,進一寸有一寸的歡喜。